internal BlockHeader GetBlockHeader() { string timestampAsString = Timestamp.ToString( BlockHeader.TimestampFormat, CultureInfo.InvariantCulture ); ImmutableArray <byte> previousHashAsArray = PreviousHash?.ToByteArray().ToImmutableArray() ?? ImmutableArray <byte> .Empty; ImmutableArray <byte> actionsHashAsArray = EvaluationDigest?.ToByteArray().ToImmutableArray() ?? ImmutableArray <byte> .Empty; if (PreviousHash.Equals(EvaluationDigest)) { Console.WriteLine(); } // FIXME: When hash is not assigned, should throw an exception. return(new BlockHeader( index: Index, timestamp: timestampAsString, nonce: Nonce.ToByteArray().ToImmutableArray(), miner: Miner?.ToByteArray().ToImmutableArray() ?? ImmutableArray <byte> .Empty, difficulty: Difficulty, totalDifficulty: TotalDifficulty, previousHash: previousHashAsArray, txHash: TxHash?.ToByteArray().ToImmutableArray() ?? ImmutableArray <byte> .Empty, hash: Hash.ToByteArray().ToImmutableArray(), preEvaluationHash: PreEvaluationHash.ToByteArray().ToImmutableArray(), evaluationDigest: actionsHashAsArray )); }
/// <summary> /// Gets <see cref="Bencodex.Types.Dictionary"/> representation of /// <see cref="BlockHeader"/>. /// </summary> /// <returns><see cref="Bencodex.Types.Dictionary"/> representation of /// <see cref="BlockHeader"/>.</returns> public Bencodex.Types.Dictionary ToBencodex() { var dict = Bencodex.Types.Dictionary.Empty .Add(IndexKey, Index) .Add(TimestampKey, Timestamp) .Add(DifficultyKey, Difficulty) .Add(TotalDifficultyKey, (IValue)(Bencodex.Types.Integer)TotalDifficulty) .Add(NonceKey, Nonce.ToArray()) .Add(HashKey, Hash.ToArray()); if (ProtocolVersion != 0) { dict = dict.Add(ProtocolVersionKey, ProtocolVersion); } if (Miner.Any()) { dict = dict.Add(MinerKey, Miner.ToArray()); } if (PreviousHash.Any()) { dict = dict.Add(PreviousHashKey, PreviousHash.ToArray()); } if (TxHash.Any()) { dict = dict.Add(TxHashKey, TxHash.ToArray()); } if (PreEvaluationHash.Any()) { dict = dict.Add(PreEvaluationHashKey, PreEvaluationHash.ToArray()); } if (StateRootHash.Any()) { dict = dict.Add(StateRootHashKey, StateRootHash.ToArray()); } return(dict); }
internal void Validate(DateTimeOffset currentTime) { DateTimeOffset ts = DateTimeOffset.ParseExact( Timestamp, TimestampFormat, CultureInfo.InvariantCulture ); if (currentTime + TimestampThreshold < ts) { throw new InvalidBlockTimestampException( $"The block #{Index}'s timestamp ({Timestamp}) is " + $"later than now ({currentTime}, " + $"threshold: {TimestampThreshold})." ); } if (Index < 0) { throw new InvalidBlockIndexException( $"index must be 0 or more, but its index is {Index}." ); } if (Difficulty > TotalDifficulty) { var msg = $"A Block.Difficulty ({Difficulty}) must be less than" + $"its TotalDifficulty ({TotalDifficulty})."; throw new InvalidBlockTotalDifficultyException( Difficulty, TotalDifficulty, msg ); } if (Index == 0) { if (Difficulty != 0) { throw new InvalidBlockDifficultyException( "difficulty must be 0 for the genesis block, " + $"but its difficulty is {Difficulty}." ); } if (TotalDifficulty != 0) { var msg = "Total difficulty must be 0 for the genesis block, " + $"but its total difficulty is {TotalDifficulty}."; throw new InvalidBlockTotalDifficultyException( Difficulty, TotalDifficulty, msg ); } if (!PreviousHash.IsEmpty) { throw new InvalidBlockPreviousHashException( "previous hash must be empty for the genesis block." ); } } else { if (Difficulty < 1) { throw new InvalidBlockDifficultyException( "difficulty must be more than 0 (except of " + "the genesis block), but its difficulty is " + $"{Difficulty}." ); } if (PreviousHash.IsEmpty) { throw new InvalidBlockPreviousHashException( "previous hash must be present except of " + "the genesis block." ); } } if (!new HashDigest <SHA256>(PreEvaluationHash.ToArray()).Satisfies(Difficulty)) { throw new InvalidBlockNonceException( $"hash ({PreEvaluationHash}) with the nonce ({Nonce}) does not " + $"satisfy its difficulty level {Difficulty}." ); } }
/// <summary> /// Creates a <see cref="Block{T}"/> instance by manually filling all field values. /// For a more automated way, see also <see cref="Mine"/> method. /// </summary> /// <param name="index">The height of the block to create. Goes to the <see cref="Index"/>. /// </param> /// <param name="difficulty">The mining difficulty that <paramref name="nonce"/> has to /// satisfy. Goes to the <see cref="Difficulty"/>.</param> /// <param name="totalDifficulty">The total mining difficulty until this block. /// See also <see cref="Difficulty"/>.</param> /// <param name="nonce">The nonce which satisfy the given <paramref name="difficulty"/> with /// any other field values. Goes to the <see cref="Nonce"/>.</param> /// <param name="miner">An optional address refers to who mines this block. /// Goes to the <see cref="Miner"/>.</param> /// <param name="previousHash">The previous block's <see cref="Hash"/>. If it's a genesis /// block (i.e., <paramref name="index"/> is 0) this should be <c>null</c>. /// Goes to the <see cref="PreviousHash"/>.</param> /// <param name="timestamp">The time this block is created. Goes to /// the <see cref="Timestamp"/>.</param> /// <param name="transactions">The transactions to be mined together with this block. /// Transactions become sorted in an unpredicted-before-mined order and then go to /// the <see cref="Transactions"/> property. /// </param> /// <param name="preEvaluationHash">The hash derived from the block <em>except of</em> /// <paramref name="stateRootHash"/> (i.e., without action evaluation). /// Automatically determined if <c>null</c> is passed (which is default).</param> /// <param name="stateRootHash">The <see cref="ITrie.Hash"/> of the states on the block. /// </param> /// <param name="protocolVersion">The protocol version. <see cref="CurrentProtocolVersion"/> /// by default.</param> /// <seealso cref="Mine"/> public Block( long index, long difficulty, BigInteger totalDifficulty, Nonce nonce, Address?miner, HashDigest <SHA256>?previousHash, DateTimeOffset timestamp, IEnumerable <Transaction <T> > transactions, HashDigest <SHA256>?preEvaluationHash = null, HashDigest <SHA256>?stateRootHash = null, int protocolVersion = CurrentProtocolVersion) { ProtocolVersion = protocolVersion; Index = index; Difficulty = difficulty; TotalDifficulty = totalDifficulty; Nonce = nonce; Miner = miner; PreviousHash = previousHash; Timestamp = timestamp; Transactions = transactions.OrderBy(tx => tx.Id).ToArray(); TxHash = CalcualteTxHashes(Transactions); PreEvaluationHash = preEvaluationHash ?? Hashcash.Hash(Header.SerializeForHash()); StateRootHash = stateRootHash; // FIXME: This does not need to be computed every time? Hash = Hashcash.Hash(Header.SerializeForHash()); // As the order of transactions should be unpredictable until a block is mined, // the sorter key should be derived from both a block hash and a txid. var hashInteger = new BigInteger(PreEvaluationHash.ToByteArray()); // If there are multiple transactions for the same signer these should be ordered by // their tx nonces. So transactions of the same signer should have the same sort key. // The following logic "flattens" multiple tx ids having the same signer into a single // txid by applying XOR between them. IImmutableDictionary <Address, IImmutableSet <Transaction <T> > > signerTxs = Transactions .GroupBy(tx => tx.Signer) .ToImmutableDictionary( g => g.Key, g => (IImmutableSet <Transaction <T> >)g.ToImmutableHashSet() ); IImmutableDictionary <Address, BigInteger> signerTxIds = signerTxs .ToImmutableDictionary( pair => pair.Key, pair => pair.Value .Select(tx => new BigInteger(tx.Id.ToByteArray())) .OrderBy(txid => txid) .Aggregate((a, b) => a ^ b) ); // Order signers by values derivied from both block hash and their "flatten" txid: IImmutableList <Address> signers = signerTxIds .OrderBy(pair => pair.Value ^ hashInteger) .Select(pair => pair.Key) .ToImmutableArray(); // Order transactions for each signer by their tx nonces: Transactions = signers .SelectMany(signer => signerTxs[signer].OrderBy(tx => tx.Nonce)) .ToImmutableArray(); }
internal void Validate(DateTimeOffset currentTime) { if (ProtocolVersion < 0) { throw new InvalidBlockProtocolVersionException( ProtocolVersion, $"A block's protocol version cannot be less than zero: {ProtocolVersion}." ); } else if (ProtocolVersion > CurrentProtocolVersion) { string message = $"Unknown protocol version: {ProtocolVersion}; " + $"the highest known version is {CurrentProtocolVersion}."; throw new InvalidBlockProtocolVersionException(ProtocolVersion, message); } DateTimeOffset ts = DateTimeOffset.ParseExact( Timestamp, TimestampFormat, CultureInfo.InvariantCulture ); HashDigest <SHA256> hash = new HashDigest <SHA256>(Hash); if (currentTime + TimestampThreshold < ts) { throw new InvalidBlockTimestampException( $"The block #{Index} {hash}'s timestamp ({Timestamp}) is " + $"later than now ({currentTime}, threshold: {TimestampThreshold})." ); } if (Index < 0) { throw new InvalidBlockIndexException( $"Block #{Index} {hash}'s index must be 0 or more." ); } if (Difficulty > TotalDifficulty) { var msg = $"Block #{Index} {hash}'s difficulty ({Difficulty}) " + $"must be less than its TotalDifficulty ({TotalDifficulty})."; throw new InvalidBlockTotalDifficultyException( Difficulty, TotalDifficulty, msg ); } if (Index == 0) { if (Difficulty != 0) { throw new InvalidBlockDifficultyException( $"Difficulty must be 0 for the genesis block {hash}, " + $"but its difficulty is {Difficulty}." ); } if (TotalDifficulty != 0) { var msg = "Total difficulty must be 0 for the genesis block " + $"{hash}, but its total difficulty is " + $"{TotalDifficulty}."; throw new InvalidBlockTotalDifficultyException( Difficulty, TotalDifficulty, msg ); } if (!PreviousHash.IsEmpty) { throw new InvalidBlockPreviousHashException( $"Previous hash must be empty for the genesis block " + $"{hash}, but its value is {ByteUtil.Hex(PreviousHash)}." ); } } else { if (Difficulty < 1) { throw new InvalidBlockDifficultyException( $"Block #{Index} {hash}'s difficulty must be more than 0 " + $"(except of the genesis block), but its difficulty is {Difficulty}." ); } if (PreviousHash.IsEmpty) { throw new InvalidBlockPreviousHashException( $"Block #{Index} {hash}'s previous hash " + "must be present since it's not the genesis block." ); } } if (!new HashDigest <SHA256>(PreEvaluationHash.ToArray()).Satisfies(Difficulty)) { throw new InvalidBlockNonceException( $"Block #{Index} {hash}'s pre-evaluation hash " + $"({ByteUtil.Hex(PreEvaluationHash)}) with the nonce " + $"({ByteUtil.Hex(Nonce)}) does not satisfy its difficulty level {Difficulty}." ); } HashDigest <SHA256> calculatedHash = Hashcash.Hash(SerializeForHash()); if (!hash.Equals(calculatedHash)) { throw new InvalidBlockHashException( $"The block #{Index} {hash}'s isn't matched its content, " + $"caculcated: {calculatedHash}"); } }