private Block PrepareBlock(Block parentBlock)
        {
            BlockHeader parentHeader = parentBlock.Header;

            if (parentHeader == null)
            {
                if (_logger.IsError)
                {
                    _logger.Error($"Preparing new block on top of {parentBlock.ToString(Block.Format.Short)} - parent header is null");
                }
                return(null);
            }

            if (_recentNotAllowedParent == parentBlock.Hash)
            {
                return(null);
            }

            if (!_sealer.CanSeal(parentHeader.Number + 1, parentHeader.Hash))
            {
                if (_logger.IsInfo)
                {
                    _logger.Info($"Not allowed to sign block ({parentBlock.Number + 1})");
                }
                _recentNotAllowedParent = parentHeader.Hash;
                return(null);
            }

            if (_logger.IsInfo)
            {
                _logger.Info($"Preparing new block on top of {parentBlock.ToString(Block.Format.Short)}");
            }

            UInt256 timestamp = _timestamp.EpochSeconds;

            BlockHeader header = new BlockHeader(
                parentBlock.Hash,
                Keccak.OfAnEmptySequenceRlp,
                Address.Zero,
                1,
                parentBlock.Number + 1,
                parentBlock.GasLimit,
                timestamp > parentBlock.Timestamp ? timestamp : parentBlock.Timestamp + 1,
                new byte[0]);

            // If the block isn't a checkpoint, cast a random vote (good enough for now)
            UInt256 number = header.Number;
            // Assemble the voting snapshot to check which votes make sense
            Snapshot snapshot     = _snapshotManager.GetOrCreateSnapshot(number - 1, header.ParentHash);
            bool     isEpochBlock = (ulong)number % 30000 == 0;

            if (!isEpochBlock)
            {
                // Gather all the proposals that make sense voting on
                List <Address> addresses = new List <Address>();
                foreach (var proposal in _proposals)
                {
                    Address address   = proposal.Key;
                    bool    authorize = proposal.Value;
                    if (_snapshotManager.IsValidVote(snapshot, address, authorize))
                    {
                        addresses.Add(address);
                    }
                }

                // If there's pending proposals, cast a vote on them
                if (addresses.Count > 0)
                {
                    header.Beneficiary = addresses[_cryptoRandom.NextInt(addresses.Count)];
                    header.Nonce       = _proposals[header.Beneficiary] ? Clique.NonceAuthVote : Clique.NonceDropVote;
                }
            }

            // Set the correct difficulty
            header.Difficulty      = CalculateDifficulty(snapshot, _address);
            header.TotalDifficulty = parentBlock.TotalDifficulty + header.Difficulty;
            if (_logger.IsDebug)
            {
                _logger.Debug($"Setting total difficulty to {parentBlock.TotalDifficulty} + {header.Difficulty}.");
            }

            // Set extra data
            int mainBytesLength   = Clique.ExtraVanityLength + Clique.ExtraSealLength;
            int signerBytesLength = isEpochBlock ? 20 * snapshot.Signers.Count : 0;
            int extraDataLength   = mainBytesLength + signerBytesLength;

            header.ExtraData = new byte[extraDataLength];

            byte[] clientName = Encoding.UTF8.GetBytes("Nethermind");
            Array.Copy(clientName, header.ExtraData, clientName.Length);

            if (isEpochBlock)
            {
                for (int i = 0; i < snapshot.Signers.Keys.Count; i++)
                {
                    Address signer = snapshot.Signers.Keys[i];
                    int     index  = Clique.ExtraVanityLength + 20 * i;
                    Array.Copy(signer.Bytes, 0, header.ExtraData, index, signer.Bytes.Length);
                }
            }

            // Mix digest is reserved for now, set to empty
            header.MixHash = Keccak.Zero;
            // Ensure the timestamp has the correct delay
            header.Timestamp = parentBlock.Timestamp + _config.BlockPeriod;
            if (header.Timestamp < _timestamp.EpochSeconds)
            {
                header.Timestamp = new UInt256(_timestamp.EpochSeconds);
            }

            var transactions = _transactionPool.GetPendingTransactions().OrderBy(t => t.Nonce).ThenByDescending(t => t.GasPrice).ThenBy(t => t.GasLimit); // by nonce in case there are two transactions for the same account

            var        selectedTxs  = new List <Transaction>();
            BigInteger gasRemaining = header.GasLimit;

            if (_logger.IsDebug)
            {
                _logger.Debug($"Collecting pending transactions at min gas price {MinGasPriceForMining} and block gas limit {gasRemaining}.");
            }

            int total = 0;

            _stateProvider.StateRoot = parentHeader.StateRoot;

            Dictionary <Address, UInt256> nonces = new Dictionary <Address, UInt256>();

            foreach (Transaction transaction in transactions)
            {
                total++;
                if (transaction.SenderAddress == null)
                {
                    if (_logger.IsError)
                    {
                        _logger.Error("Rejecting null sender pending transaction.");
                    }
                    continue;
                }

                if (transaction.GasPrice < MinGasPriceForMining)
                {
                    if (_logger.IsTrace)
                    {
                        _logger.Trace($"Rejecting transaction - gas price ({transaction.GasPrice}) too low (min gas price: {MinGasPriceForMining}.");
                    }
                    continue;
                }

                if (transaction.Nonce != _stateProvider.GetNonce(transaction.SenderAddress) && (!nonces.ContainsKey(transaction.SenderAddress) || nonces[transaction.SenderAddress] + 1 != transaction.Nonce))
                {
                    if (_logger.IsTrace)
                    {
                        _logger.Trace($"Rejecting transaction based on nonce.");
                    }
                    continue;
                }

                if (transaction.GasLimit > gasRemaining)
                {
                    if (_logger.IsTrace)
                    {
                        _logger.Trace($"Rejecting transaction - gas limit ({transaction.GasPrice}) more than remaining gas ({gasRemaining}).");
                    }
                    continue;
                }

                UInt256 requiredBalance = transaction.GasLimit + transaction.Value;
                UInt256 balance         = _stateProvider.GetBalance(transaction.SenderAddress);
                if (transaction.GasLimit + transaction.Value > balance)
                {
                    if (_logger.IsTrace)
                    {
                        _logger.Trace($"Rejecting transaction - insufficient balance - required {requiredBalance}, actual {balance}.");
                    }
                    continue;
                }

                selectedTxs.Add(transaction);
                nonces[transaction.SenderAddress] = transaction.Nonce;
                gasRemaining -= transaction.GasLimit;
            }

            if (_logger.IsDebug)
            {
                _logger.Debug($"Collected {selectedTxs.Count} out of {total} pending transactions.");
            }

            Block block = new Block(header, selectedTxs, new BlockHeader[0]);

            header.TransactionsRoot = block.CalculateTransactionsRoot();
            block.Author            = _address;
            return(block);
        }
Exemple #2
0
        public Snapshot Apply(List <BlockHeader> headers)
        {
            // Allow passing in no headers for cleaner code
            if (headers.Count == 0)
            {
                return(this);
            }

            // Sanity check that the headers can be applied
            for (int i = 0; i < headers.Count - 1; i++)
            {
                if (headers[i].Number != Number + (UInt256)i + 1)
                {
                    throw new InvalidOperationException("Invalid voting chain");
                }
            }

            // Iterate through the headers and create a new snapshot
            Snapshot snapshot = Clone();

            foreach (BlockHeader header in headers)
            {
                // Remove any votes on checkpoint blocks
                UInt256 number = header.Number;
                if ((ulong)number % Config.Epoch == 0)
                {
                    snapshot.Votes.Clear();
                    snapshot.Tally = new Dictionary <Address, Tally>();
                }

                // Resolve the authorization key and check against signers
                Address signer = header.Author;
                if (!snapshot.Signers.ContainsKey(signer))
                {
                    throw new InvalidOperationException("Unauthorized signer");
                }

                if (HasSignedRecently(snapshot, number, signer))
                {
                    throw new InvalidOperationException("Recently signed");
                }

                snapshot.Signers[signer] = number;

                // Header authorized, discard any previous votes from the signer
                for (int i = 0; i < snapshot.Votes.Count; i++)
                {
                    Vote vote = snapshot.Votes[i];
                    if (vote.Signer == signer && vote.Address == header.Beneficiary)
                    {
                        // Uncast the vote from the cached tally
                        snapshot.Uncast(vote.Address, vote.Authorize);
                        // Uncast the vote from the chronological list
                        snapshot.Votes.RemoveAt(i);
                        break;
                    }
                }

                // Tally up the new vote from the signer
                bool authorize = header.Nonce == Clique.NonceAuthVote;
                if (snapshot.Cast(header.Beneficiary, authorize))
                {
                    Vote vote = new Vote(signer, number, header.Beneficiary, authorize);
                    snapshot.Votes.Add(vote);
                }

                // If the vote passed, update the list of signers
                Tally tally = snapshot.Tally[header.Beneficiary];
                if (tally.Votes > snapshot.Signers.Count / 2)
                {
                    if (tally.Authorize)
                    {
                        snapshot.Signers.Add(header.Beneficiary, 0);
                    }
                    else
                    {
                        snapshot.Signers.Remove(header.Beneficiary);
                    }

                    // Discard any previous votes the deauthorized signer cast
                    for (int i = 0; i < snapshot.Votes.Count; i++)
                    {
                        if (snapshot.Votes[i].Signer == header.Beneficiary)
                        {
                            // Uncast the vote from the cached tally
                            snapshot.Uncast(snapshot.Votes[i].Address, snapshot.Votes[i].Authorize);

                            // Uncast the vote from the chronological list
                            snapshot.Votes.RemoveAt(i);
                            i--;
                        }
                    }

                    // Discard any previous votes around the just changed account
                    for (int i = 0; i < snapshot.Votes.Count; i++)
                    {
                        if (snapshot.Votes[i].Address == header.Beneficiary)
                        {
                            snapshot.Votes.RemoveAt(i);
                            i--;
                        }
                    }

                    snapshot.Tally.Remove(header.Beneficiary);
                }
            }

            snapshot.Number += (ulong)headers.Count;
            snapshot.Hash    = BlockHeader.CalculateHash(headers[headers.Count - 1]);
            return(snapshot);
        }
        internal Snapshot GetOrCreateSnapshot(UInt256 number, Keccak hash)
        {
            // Search for a snapshot in memory or on disk for checkpoints
            List <BlockHeader> headers = new List <BlockHeader>();
            Snapshot           snapshot;

            while (true)
            {
                snapshot = GetSnapshot(number, hash);
                if (snapshot != null)
                {
                    break;
                }

                // If we're at an checkpoint block, make a snapshot if it's known
                BlockHeader header = _blockTree.FindHeader(hash);
                if (header == null)
                {
                    throw new InvalidOperationException("Unknown ancestor");
                }

                if (header.Hash == null)
                {
                    throw new InvalidOperationException("Block tree block without hash set");
                }

                Keccak parentHash = header.ParentHash;
                if (number == 0 || (IsEpochTransition(number) && _blockTree.FindHeader(parentHash) == null))
                {
                    int signersCount = header.CalculateSignersCount();
                    SortedList <Address, UInt256> signers = new SortedList <Address, UInt256>(signersCount, CliqueAddressComparer.Instance);
                    for (int i = 0; i < signersCount; i++)
                    {
                        Address signer = new Address(header.ExtraData.Slice(Clique.ExtraVanityLength + i * AddressLength, AddressLength));
                        signers.Add(signer, UInt256.Zero);
                    }

                    snapshot = new Snapshot(_signatures, number, header.Hash, signers);
                    snapshot.Store(_blocksDb);
                    break;
                }

                // No snapshot for this header, gather the header and move backward
                headers.Add(header);
                number = number - 1;
                hash   = header.ParentHash;
            }

            // Previous snapshot found, apply any pending headers on top of it
            for (int i = 0; i < headers.Count / 2; i++)
            {
                BlockHeader temp = headers[headers.Count - 1 - i];
                headers[headers.Count - 1 - i] = headers[i];
                headers[i] = temp;
            }

            for (int i = 0; i < headers.Count; i++)
            {
                headers[i].Author = headers[i].Author ?? GetBlockSealer(headers[i]);
            }

            snapshot = snapshot.Apply(headers, _config.Epoch);

            _recent.Set(snapshot.Hash, snapshot);
            // If we've generated a new checkpoint snapshot, save to disk
            if ((ulong)snapshot.Number % Clique.CheckpointInterval == 0 && headers.Count > 0)
            {
                snapshot.Store(_blocksDb);
            }

            return(snapshot);
        }
        public bool ValidateParams(BlockHeader parent, BlockHeader header)
        {
            long number = header.Number;
            // Retrieve the snapshot needed to validate this header and cache it
            Snapshot snapshot = _snapshotManager.GetOrCreateSnapshot(number - 1, header.ParentHash);

            // Resolve the authorization key and check against signers
            header.Author = header.Author ?? _snapshotManager.GetBlockSealer(header);
            Address signer = header.Author;

            if (!snapshot.Signers.ContainsKey(signer))
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Invalid block signer {signer} - not authorized to sign a block");
                }
                return(false);
            }

            if (_snapshotManager.HasSignedRecently(snapshot, number, signer))
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Invalid block signer {signer} - the signer is among recents");
                }
                return(false);
            }

            // Ensure that the difficulty corresponds to the turn-ness of the signer
            bool inTurn = _snapshotManager.IsInTurn(snapshot, header.Number, signer);

            if (inTurn && header.Difficulty != Clique.DifficultyInTurn)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Invalid block difficulty {header.Difficulty} - should be in-turn {Clique.DifficultyInTurn}");
                }
                return(false);
            }

            if (!inTurn && header.Difficulty != Clique.DifficultyNoTurn)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Invalid block difficulty {header.Difficulty} - should be no-turn {Clique.DifficultyNoTurn}");
                }
                return(false);
            }

            bool isEpochTransition = IsEpochTransition(header.Number);

            // Checkpoint blocks need to enforce zero beneficiary
            if (isEpochTransition && header.Beneficiary != Address.Zero)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Invalid block beneficiary ({header.Beneficiary}) - should be empty on checkpoint");
                }
                return(false);
            }

            if (isEpochTransition && header.Nonce != Clique.NonceDropVote)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Invalid block nonce ({header.Nonce}) - should be zeroes on checkpoints");
                }
                return(false);
            }

            // Ensure that the extra-data contains a signer list on checkpoint, but none otherwise
            int singersBytes = header.ExtraData.Length - Clique.ExtraVanityLength - Clique.ExtraSealLength;

            if (!isEpochTransition && singersBytes != 0)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Invalid block extra-data ({header.ExtraData}) - should be empty on non-checkpoints");
                }
                return(false);
            }

            if (isEpochTransition && singersBytes % Address.ByteLength != 0)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Invalid block nonce ({header.ExtraData}) - should contain a list of signers on checkpoints");
                }
                return(false);
            }

            // Nonce must be 0x00..0 or 0xff..f, zeroes enforced on checkpoints
            if (header.Nonce != Clique.NonceAuthVote && header.Nonce != Clique.NonceDropVote)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Invalid block nonce ({header.Nonce})");
                }
                return(false);
            }

            if (header.ExtraData.Length < Clique.ExtraVanityLength)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn("Invalid block extra data length - missing vanity");
                }
                return(false);
            }

            if (header.ExtraData.Length < Clique.ExtraVanityLength + Clique.ExtraSealLength)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn("Invalid block extra data length - missing seal");
                }
                return(false);
            }

            // Ensure that the mix digest is zero as we don't have fork protection currently
            if (header.MixHash != Keccak.Zero)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Invalid block mix hash ({header.MixHash}) - should be zeroes");
                }
                return(false);
            }

            // Ensure that the block doesn't contain any uncles which are meaningless in PoA
            if (header.OmmersHash != Keccak.OfAnEmptySequenceRlp)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Invalid block ommers hash ({header.OmmersHash}) - ommers are meaningless in Clique");
                }
                return(false);
            }

            if (header.Difficulty != Clique.DifficultyInTurn && header.Difficulty != Clique.DifficultyNoTurn)
            {
                if (_logger.IsWarn)
                {
                    _logger.Warn($"Invalid block difficulty ({header.Difficulty}) - should be {Clique.DifficultyInTurn} or {Clique.DifficultyNoTurn}");
                }
                return(false);
            }

            return(ValidateCascadingFields(parent, header));
        }
 public bool IsInTurn(Snapshot snapshot, long number, Address signer)
 {
     return((long)number % snapshot.Signers.Count == snapshot.Signers.IndexOfKey(signer));
 }
        public bool IsValidVote(Snapshot snapshot, Address address, bool authorize)
        {
            bool signer = snapshot.Signers.ContainsKey(address);

            return(signer && !authorize || !signer && authorize);
        }
        public Snapshot GetOrCreateSnapshot(long number, Keccak hash)
        {
            Snapshot snapshot = GetSnapshot(number, hash);

            if (snapshot != null)
            {
                return(snapshot);
            }

            var headers = new List <BlockHeader>();

            lock (_snapshotCreationLock)
            {
                // Search for a snapshot in memory or on disk for checkpoints
                while (true)
                {
                    snapshot = GetSnapshot(number, hash);
                    if (snapshot != null)
                    {
                        break;
                    }

                    // If we're at an checkpoint block, make a snapshot if it's known
                    BlockHeader header = _blockTree.FindHeader(hash);
                    if (header == null)
                    {
                        throw new InvalidOperationException("Unknown ancestor");
                    }

                    if (header.Hash == null)
                    {
                        throw new InvalidOperationException("Block tree block without hash set");
                    }

                    Keccak parentHash = header.ParentHash;
                    if (IsEpochTransition(number))
                    {
                        Snapshot parentSnapshot = GetSnapshot(number - 1, parentHash);

                        if (_logger.IsInfo)
                        {
                            _logger.Info($"Creating epoch snapshot at block {number}");
                        }
                        int     signersCount = CalculateSignersCount(header);
                        var     signers      = new SortedList <Address, long>(signersCount, CliqueAddressComparer.Instance);
                        Address epochSigner  = GetBlockSealer(header);
                        for (int i = 0; i < signersCount; i++)
                        {
                            Address signer = new Address(header.ExtraData.Slice(Clique.ExtraVanityLength + i * Address.ByteLength, Address.ByteLength));
                            signers.Add(signer, signer == epochSigner ? number : parentSnapshot == null ? 0L : parentSnapshot.Signers.ContainsKey(signer) ? parentSnapshot.Signers[signer] : 0L);
                        }

                        snapshot = new Snapshot(number, header.Hash, signers);
                        Store(snapshot);
                        break;
                    }

                    // No snapshot for this header, gather the header and move backward
                    headers.Add(header);
                    number = number - 1;
                    hash   = header.ParentHash;
                }

                if (headers.Count > 0)
                {
                    // Previous snapshot found, apply any pending headers on top of it
                    headers.Reverse();

                    for (int i = 0; i < headers.Count; i++)
                    {
                        headers[i].Author = headers[i].Author ?? GetBlockSealer(headers[i]);
                    }

                    int countBefore = snapshot.Signers.Count;
                    snapshot = Apply(snapshot, headers, _cliqueConfig.Epoch);

                    int countAfter = snapshot.Signers.Count;
                    if (countAfter != countBefore && _logger.IsInfo)
                    {
                        int    signerIndex = 0;
                        string word        = countAfter > countBefore ? "added to" : "removed from";
                        _logger.Info($"At block {number } a signer has been {word} the signer list:{Environment.NewLine}{string.Join(Environment.NewLine, snapshot.Signers.OrderBy(s => s.Key, CliqueAddressComparer.Instance).Select(s => $"  Signer {signerIndex++}: " + (KnownAddresses.GoerliValidators.ContainsKey(s.Key) ? KnownAddresses.GoerliValidators[s.Key] : s.Key.ToString())))}");
                    }
                }

                _snapshotCache.Set(snapshot.Hash, snapshot);
                // If we've generated a new checkpoint snapshot, save to disk
            }

            if ((ulong)snapshot.Number % Clique.CheckpointInterval == 0 && headers.Count > 0)
            {
                Store(snapshot);
            }

            return(snapshot);
        }