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); }
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); }