Пример #1
0
        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
                       ));
        }
Пример #2
0
        /// <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);
        }
Пример #3
0
        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}."
                          );
            }
        }
Пример #4
0
        /// <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();
        }
Пример #5
0
        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}");
            }
        }