From 88a05d537da0876ac974c2d476bb2a8aa7a7dd0f Mon Sep 17 00:00:00 2001 From: Timur Badretdinov Date: Tue, 13 Nov 2018 04:22:13 +0300 Subject: [PATCH 1/7] Fix merge of CliqueBlockProducer --- .../Nethermind.Clique/CliqueBlockProducer.cs | 109 +++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-) diff --git a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs index f882506ff47..8cfbe2b818c 100644 --- a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs +++ b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs @@ -20,39 +20,144 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Numerics; +using System.Text; using System.Threading.Tasks; using Nethermind.Blockchain; +using Nethermind.Blockchain.TransactionPools; using Nethermind.Core; +using Nethermind.Core.Crypto; +using Nethermind.Core.Logging; using Nethermind.Dirichlet.Numerics; +using Nethermind.Evm; using Nethermind.Store; namespace Nethermind.Clique { public class CliqueBlockProducer : IBlockProducer { + private static readonly BigInteger MinGasPriceForMining = 1; + private readonly IBlockTree _blockTree; + private readonly ITimestamp _timestamp; + private readonly ILogger _logger; + + private readonly IBlockchainProcessor _processor; + private readonly ITransactionPool _transactionPool; private CliqueSealEngine _sealEngine; private CliqueConfig _config; - private BlockTree _blockTree; private Address _address; private Dictionary _proposals = new Dictionary(); - public CliqueBlockProducer(CliqueSealEngine cliqueSealEngine, CliqueConfig config, BlockTree blockTree, Address address) + public CliqueBlockProducer( + ITransactionPool transactionPool, + IBlockchainProcessor devProcessor, + IBlockTree blockTree, + ITimestamp timestamp, + CliqueSealEngine cliqueSealEngine, + CliqueConfig config, + Address address, + ILogManager logManager) { + _transactionPool = transactionPool ?? throw new ArgumentNullException(nameof(transactionPool)); + _processor = devProcessor ?? throw new ArgumentNullException(nameof(devProcessor)); + _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); + _timestamp = timestamp; _sealEngine = cliqueSealEngine; _config = config; _blockTree = blockTree; _address = address; + _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); } public void Start() { + _transactionPool.NewPending += OnNewPendingTx; } public async Task StopAsync() { + _transactionPool.NewPending -= OnNewPendingTx; await Task.CompletedTask; } + private Block PrepareBlock() + { + BlockHeader parentHeader = _blockTree.Head; + if (parentHeader == null) return null; + + Block parent = _blockTree.FindBlock(parentHeader.Hash, false); + UInt256 timestamp = _timestamp.EpochSeconds; + + BlockHeader header = new BlockHeader( + parent.Hash, + Keccak.OfAnEmptySequenceRlp, + Address.Zero, + 1, + parent.Number + 1, + parent.GasLimit, + timestamp > parent.Timestamp ? timestamp : parent.Timestamp + 1, + Encoding.UTF8.GetBytes("Nethermind")); + + header.TotalDifficulty = parent.TotalDifficulty + header.Difficulty; + if (_logger.IsDebug) _logger.Debug($"Setting total difficulty to {parent.TotalDifficulty} + {header.Difficulty}."); + + var transactions = _transactionPool.GetPendingTransactions().OrderBy(t => t?.Nonce); // by nonce in case there are two transactions for the same account + + var selectedTxs = new List(); + 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; + foreach (Transaction transaction in transactions) + { + total++; + if (transaction == null) throw new InvalidOperationException("Block transaction is null"); + + if (transaction.GasPrice < MinGasPriceForMining) + { + if (_logger.IsTrace) _logger.Trace($"Rejecting transaction - gas price ({transaction.GasPrice}) too low (min gas price: {MinGasPriceForMining}."); + continue; + } + + if (transaction.GasLimit > gasRemaining) + { + if (_logger.IsTrace) _logger.Trace($"Rejecting transaction - gas limit ({transaction.GasPrice}) more than remaining gas ({gasRemaining})."); + break; + } + + selectedTxs.Add(transaction); + 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(); + return block; + } + + private void OnNewPendingTx(object sender, TransactionEventArgs e) + { + Block block = PrepareBlock(); + if (block == null) + { + if (_logger.IsError) _logger.Error("Failed to prepare block for mining."); + return; + } + + Block processedBlock = _processor.Process(block, ProcessingOptions.NoValidation | ProcessingOptions.ReadOnlyChain | ProcessingOptions.WithRollback, NullTraceListener.Instance); + if (processedBlock == null) + { + if (_logger.IsError) _logger.Error("Block prepared by block producer was rejected by processor"); + return; + } + + if (_logger.IsInfo) _logger.Info($"Suggesting newly mined block {processedBlock.ToString(Block.Format.HashAndNumber)}"); + _blockTree.SuggestBlock(processedBlock); + } + private void Prepare(BlockHeader header) { // If the block isn't a checkpoint, cast a random vote (good enough for now) From cd060aae3a386b76529fd7101a4fd2a2072011da Mon Sep 17 00:00:00 2001 From: Timur Badretdinov Date: Tue, 13 Nov 2018 04:58:19 +0300 Subject: [PATCH 2/7] Move Clique block production into PrepareBlock --- .../Nethermind.Clique/CliqueBlockProducer.cs | 125 ++++++++---------- 1 file changed, 54 insertions(+), 71 deletions(-) diff --git a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs index 8cfbe2b818c..e7ea14970ae 100644 --- a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs +++ b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs @@ -101,65 +101,6 @@ private Block PrepareBlock() header.TotalDifficulty = parent.TotalDifficulty + header.Difficulty; if (_logger.IsDebug) _logger.Debug($"Setting total difficulty to {parent.TotalDifficulty} + {header.Difficulty}."); - var transactions = _transactionPool.GetPendingTransactions().OrderBy(t => t?.Nonce); // by nonce in case there are two transactions for the same account - - var selectedTxs = new List(); - 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; - foreach (Transaction transaction in transactions) - { - total++; - if (transaction == null) throw new InvalidOperationException("Block transaction is null"); - - if (transaction.GasPrice < MinGasPriceForMining) - { - if (_logger.IsTrace) _logger.Trace($"Rejecting transaction - gas price ({transaction.GasPrice}) too low (min gas price: {MinGasPriceForMining}."); - continue; - } - - if (transaction.GasLimit > gasRemaining) - { - if (_logger.IsTrace) _logger.Trace($"Rejecting transaction - gas limit ({transaction.GasPrice}) more than remaining gas ({gasRemaining})."); - break; - } - - selectedTxs.Add(transaction); - 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(); - return block; - } - - private void OnNewPendingTx(object sender, TransactionEventArgs e) - { - Block block = PrepareBlock(); - if (block == null) - { - if (_logger.IsError) _logger.Error("Failed to prepare block for mining."); - return; - } - - Block processedBlock = _processor.Process(block, ProcessingOptions.NoValidation | ProcessingOptions.ReadOnlyChain | ProcessingOptions.WithRollback, NullTraceListener.Instance); - if (processedBlock == null) - { - if (_logger.IsError) _logger.Error("Block prepared by block producer was rejected by processor"); - return; - } - - if (_logger.IsInfo) _logger.Info($"Suggesting newly mined block {processedBlock.ToString(Block.Format.HashAndNumber)}"); - _blockTree.SuggestBlock(processedBlock); - } - - private void Prepare(BlockHeader header) - { // 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 @@ -225,28 +166,70 @@ private void Prepare(BlockHeader header) } // Mix digest is reserved for now, set to empty + header.MixHash = Keccak.Zero; // Ensure the timestamp has the correct delay - BlockHeader parent = _blockTree.FindHeader(header.ParentHash); - if (parent == null) - { - throw new InvalidOperationException("Unknown ancestor"); - } - header.Timestamp = parent.Timestamp + _config.BlockPeriod; long currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); if (header.Timestamp < currentTimestamp) { header.Timestamp = new UInt256(currentTimestamp); } + + var transactions = _transactionPool.GetPendingTransactions().OrderBy(t => t?.Nonce); // by nonce in case there are two transactions for the same account + + var selectedTxs = new List(); + 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; + foreach (Transaction transaction in transactions) + { + total++; + if (transaction == null) throw new InvalidOperationException("Block transaction is null"); + + if (transaction.GasPrice < MinGasPriceForMining) + { + if (_logger.IsTrace) _logger.Trace($"Rejecting transaction - gas price ({transaction.GasPrice}) too low (min gas price: {MinGasPriceForMining}."); + continue; + } + + if (transaction.GasLimit > gasRemaining) + { + if (_logger.IsTrace) _logger.Trace($"Rejecting transaction - gas limit ({transaction.GasPrice}) more than remaining gas ({gasRemaining})."); + break; + } + + selectedTxs.Add(transaction); + 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(); + return block; } - private Block Finalize(StateProvider state, BlockHeader header, Transaction[] txs, BlockHeader[] uncles, TransactionReceipt[] receipts) + private void OnNewPendingTx(object sender, TransactionEventArgs e) { - // No block rewards in PoA, so the state remains as is and uncles are dropped - header.StateRoot = state.StateRoot; - header.OmmersHash = BlockHeader.CalculateHash((BlockHeader)null); - // Assemble and return the final block for sealing - return new Block(header, txs, null); + Block block = PrepareBlock(); + if (block == null) + { + if (_logger.IsError) _logger.Error("Failed to prepare block for mining."); + return; + } + + Block processedBlock = _processor.Process(block, ProcessingOptions.NoValidation | ProcessingOptions.ReadOnlyChain | ProcessingOptions.WithRollback, NullTraceListener.Instance); + if (processedBlock == null) + { + if (_logger.IsError) _logger.Error("Block prepared by block producer was rejected by processor"); + return; + } + + if (_logger.IsInfo) _logger.Info($"Suggesting newly mined block {processedBlock.ToString(Block.Format.HashAndNumber)}"); + _blockTree.SuggestBlock(processedBlock); } private UInt256 CalculateDifficulty(ulong time, BlockHeader parent) From c620528af22aa4d8383fdc98e4e141c97cbe3786 Mon Sep 17 00:00:00 2001 From: Timur Badretdinov Date: Tue, 13 Nov 2018 06:21:53 +0300 Subject: [PATCH 3/7] Remove unused CalculateDifficulty --- src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs index e7ea14970ae..f1d2991451d 100644 --- a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs +++ b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs @@ -232,12 +232,6 @@ private void OnNewPendingTx(object sender, TransactionEventArgs e) _blockTree.SuggestBlock(processedBlock); } - private UInt256 CalculateDifficulty(ulong time, BlockHeader parent) - { - Snapshot snapshot = _sealEngine.GetOrCreateSnapshot(parent.Number, parent.Hash); - return CalculateDifficulty(snapshot, _address); - } - private UInt256 CalculateDifficulty(Snapshot snapshot, Address signer) { if (snapshot.InTurn(snapshot.Number + 1, signer)) From 3f331e8572d1fb343714411e04cd3b59669d240a Mon Sep 17 00:00:00 2001 From: Timur Badretdinov Date: Tue, 13 Nov 2018 07:39:04 +0300 Subject: [PATCH 4/7] Fix ExtraData --- src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs index f1d2991451d..c797d75104c 100644 --- a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs +++ b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs @@ -140,9 +140,9 @@ private Block PrepareBlock() // Ensure the extra data has all it's components if (header.ExtraData.Length < CliqueSealEngine.ExtraVanityLength) { - for (int i = 0; i < CliqueSealEngine.ExtraVanityLength - header.ExtraData.Length; i++) + for (int i = 0; i < CliqueSealEngine.ExtraVanityLength; i++) { - header.ExtraData.Append((byte)0); + header.ExtraData = header.ExtraData.Append((byte)0).ToArray(); } } @@ -154,15 +154,14 @@ private Block PrepareBlock() { foreach (byte addressByte in signer.Bytes) { - header.ExtraData.Append(addressByte); + header.ExtraData = header.ExtraData.Append(addressByte).ToArray(); } } } - byte[] extraSeal = new byte[CliqueSealEngine.ExtraSealLength]; for (int i = 0; i < CliqueSealEngine.ExtraSealLength; i++) { - header.ExtraData.Append((byte)0); + header.ExtraData = header.ExtraData.Append((byte)0).ToArray(); } // Mix digest is reserved for now, set to empty From a4cdae3abc27450fae3ffcf24e744c51ed0c708a Mon Sep 17 00:00:00 2001 From: Timur Badretdinov Date: Tue, 13 Nov 2018 07:59:50 +0300 Subject: [PATCH 5/7] Specify client name in extra data --- src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs index c797d75104c..6c2b8d0ad39 100644 --- a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs +++ b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs @@ -96,7 +96,7 @@ private Block PrepareBlock() parent.Number + 1, parent.GasLimit, timestamp > parent.Timestamp ? timestamp : parent.Timestamp + 1, - Encoding.UTF8.GetBytes("Nethermind")); + new byte[0]); header.TotalDifficulty = parent.TotalDifficulty + header.Difficulty; if (_logger.IsDebug) _logger.Debug($"Setting total difficulty to {parent.TotalDifficulty} + {header.Difficulty}."); @@ -148,6 +148,9 @@ private Block PrepareBlock() header.ExtraData = header.ExtraData.Take(CliqueSealEngine.ExtraVanityLength).ToArray(); + byte[] clientName = Encoding.UTF8.GetBytes("Nethermind"); + Array.Copy(clientName, header.ExtraData, clientName.Length); + if ((ulong)number % _config.Epoch == 0) { foreach (Address signer in snapshot.Signers.Keys) From e59588e278548c168c7930d4447f07168545652b Mon Sep 17 00:00:00 2001 From: Timur Badretdinov Date: Tue, 13 Nov 2018 08:42:40 +0300 Subject: [PATCH 6/7] Set total difficulty after block difficulty --- src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs index 6c2b8d0ad39..2c8e049a725 100644 --- a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs +++ b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs @@ -98,9 +98,6 @@ private Block PrepareBlock() timestamp > parent.Timestamp ? timestamp : parent.Timestamp + 1, new byte[0]); - header.TotalDifficulty = parent.TotalDifficulty + header.Difficulty; - if (_logger.IsDebug) _logger.Debug($"Setting total difficulty to {parent.TotalDifficulty} + {header.Difficulty}."); - // 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 @@ -137,6 +134,9 @@ private Block PrepareBlock() // Set the correct difficulty header.Difficulty = CalculateDifficulty(snapshot, _address); + header.TotalDifficulty = parent.TotalDifficulty + header.Difficulty; + if (_logger.IsDebug) _logger.Debug($"Setting total difficulty to {parent.TotalDifficulty} + {header.Difficulty}."); + // Ensure the extra data has all it's components if (header.ExtraData.Length < CliqueSealEngine.ExtraVanityLength) { From 4dd31ad4ed27ca0977b869808b8b47b0fba7601d Mon Sep 17 00:00:00 2001 From: Timur Badretdinov Date: Tue, 13 Nov 2018 09:18:26 +0300 Subject: [PATCH 7/7] Rewrite extra data initialization --- .../Nethermind.Clique/CliqueBlockProducer.cs | 34 +++++++------------ 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs index 2c8e049a725..a1d77c826d3 100644 --- a/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs +++ b/src/Nethermind/Nethermind.Clique/CliqueBlockProducer.cs @@ -102,7 +102,8 @@ private Block PrepareBlock() UInt256 number = header.Number; // Assemble the voting snapshot to check which votes make sense Snapshot snapshot = _sealEngine.GetOrCreateSnapshot(number - 1, header.ParentHash); - if ((ulong)number % _config.Epoch != 0) + bool isEpochBlock = (ulong)number % 30000 == 0; + if (!isEpochBlock) { // Gather all the proposals that make sense voting on List
addresses = new List
(); @@ -137,36 +138,25 @@ private Block PrepareBlock() header.TotalDifficulty = parent.TotalDifficulty + header.Difficulty; if (_logger.IsDebug) _logger.Debug($"Setting total difficulty to {parent.TotalDifficulty} + {header.Difficulty}."); - // Ensure the extra data has all it's components - if (header.ExtraData.Length < CliqueSealEngine.ExtraVanityLength) - { - for (int i = 0; i < CliqueSealEngine.ExtraVanityLength; i++) - { - header.ExtraData = header.ExtraData.Append((byte)0).ToArray(); - } - } - - header.ExtraData = header.ExtraData.Take(CliqueSealEngine.ExtraVanityLength).ToArray(); + // Set extra data + int mainBytesLength = CliqueSealEngine.ExtraVanityLength + CliqueSealEngine.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 ((ulong)number % _config.Epoch == 0) + if (isEpochBlock) { - foreach (Address signer in snapshot.Signers.Keys) + for (int i = 0; i < snapshot.Signers.Keys.Count; i++) { - foreach (byte addressByte in signer.Bytes) - { - header.ExtraData = header.ExtraData.Append(addressByte).ToArray(); - } + Address signer = snapshot.Signers.Keys[i]; + int index = CliqueSealEngine.ExtraVanityLength + 20 * i; + Array.Copy(signer.Bytes, 0, header.ExtraData, index, signer.Bytes.Length); } } - for (int i = 0; i < CliqueSealEngine.ExtraSealLength; i++) - { - header.ExtraData = header.ExtraData.Append((byte)0).ToArray(); - } - // Mix digest is reserved for now, set to empty header.MixHash = Keccak.Zero; // Ensure the timestamp has the correct delay