|
20 | 20 | using System; |
21 | 21 | using System.Collections.Generic; |
22 | 22 | using System.Linq; |
| 23 | +using System.Numerics; |
| 24 | +using System.Text; |
23 | 25 | using System.Threading.Tasks; |
24 | 26 | using Nethermind.Blockchain; |
| 27 | +using Nethermind.Blockchain.TransactionPools; |
25 | 28 | using Nethermind.Core; |
| 29 | +using Nethermind.Core.Crypto; |
| 30 | +using Nethermind.Core.Logging; |
26 | 31 | using Nethermind.Dirichlet.Numerics; |
| 32 | +using Nethermind.Evm; |
27 | 33 | using Nethermind.Store; |
28 | 34 |
|
29 | 35 | namespace Nethermind.Clique |
30 | 36 | { |
31 | 37 | public class CliqueBlockProducer : IBlockProducer |
32 | 38 | { |
| 39 | + private static readonly BigInteger MinGasPriceForMining = 1; |
| 40 | + private readonly IBlockTree _blockTree; |
| 41 | + private readonly ITimestamp _timestamp; |
| 42 | + private readonly ILogger _logger; |
| 43 | + |
| 44 | + private readonly IBlockchainProcessor _processor; |
| 45 | + private readonly ITransactionPool _transactionPool; |
33 | 46 | private CliqueSealEngine _sealEngine; |
34 | 47 | private CliqueConfig _config; |
35 | | - private BlockTree _blockTree; |
36 | 48 | private Address _address; |
37 | 49 | private Dictionary<Address, bool> _proposals = new Dictionary<Address, bool>(); |
38 | 50 |
|
39 | | - public CliqueBlockProducer(CliqueSealEngine cliqueSealEngine, CliqueConfig config, BlockTree blockTree, Address address) |
| 51 | + public CliqueBlockProducer( |
| 52 | + ITransactionPool transactionPool, |
| 53 | + IBlockchainProcessor devProcessor, |
| 54 | + IBlockTree blockTree, |
| 55 | + ITimestamp timestamp, |
| 56 | + CliqueSealEngine cliqueSealEngine, |
| 57 | + CliqueConfig config, |
| 58 | + Address address, |
| 59 | + ILogManager logManager) |
40 | 60 | { |
| 61 | + _transactionPool = transactionPool ?? throw new ArgumentNullException(nameof(transactionPool)); |
| 62 | + _processor = devProcessor ?? throw new ArgumentNullException(nameof(devProcessor)); |
| 63 | + _blockTree = blockTree ?? throw new ArgumentNullException(nameof(blockTree)); |
| 64 | + _timestamp = timestamp; |
41 | 65 | _sealEngine = cliqueSealEngine; |
42 | 66 | _config = config; |
43 | 67 | _blockTree = blockTree; |
44 | 68 | _address = address; |
| 69 | + _logger = logManager?.GetClassLogger() ?? throw new ArgumentNullException(nameof(logManager)); |
45 | 70 | } |
46 | 71 |
|
47 | 72 | public void Start() |
48 | 73 | { |
| 74 | + _transactionPool.NewPending += OnNewPendingTx; |
49 | 75 | } |
50 | 76 |
|
51 | 77 | public async Task StopAsync() |
52 | 78 | { |
| 79 | + _transactionPool.NewPending -= OnNewPendingTx; |
53 | 80 | await Task.CompletedTask; |
54 | 81 | } |
55 | 82 |
|
56 | | - private void Prepare(BlockHeader header) |
| 83 | + private Block PrepareBlock() |
57 | 84 | { |
| 85 | + BlockHeader parentHeader = _blockTree.Head; |
| 86 | + if (parentHeader == null) return null; |
| 87 | + |
| 88 | + Block parent = _blockTree.FindBlock(parentHeader.Hash, false); |
| 89 | + UInt256 timestamp = _timestamp.EpochSeconds; |
| 90 | + |
| 91 | + BlockHeader header = new BlockHeader( |
| 92 | + parent.Hash, |
| 93 | + Keccak.OfAnEmptySequenceRlp, |
| 94 | + Address.Zero, |
| 95 | + 1, |
| 96 | + parent.Number + 1, |
| 97 | + parent.GasLimit, |
| 98 | + timestamp > parent.Timestamp ? timestamp : parent.Timestamp + 1, |
| 99 | + new byte[0]); |
| 100 | + |
58 | 101 | // If the block isn't a checkpoint, cast a random vote (good enough for now) |
59 | 102 | UInt256 number = header.Number; |
60 | 103 | // Assemble the voting snapshot to check which votes make sense |
61 | 104 | Snapshot snapshot = _sealEngine.GetOrCreateSnapshot(number - 1, header.ParentHash); |
62 | | - if ((ulong)number % _config.Epoch != 0) |
| 105 | + bool isEpochBlock = (ulong)number % 30000 == 0; |
| 106 | + if (!isEpochBlock) |
63 | 107 | { |
64 | 108 | // Gather all the proposals that make sense voting on |
65 | 109 | List<Address> addresses = new List<Address>(); |
@@ -91,63 +135,93 @@ private void Prepare(BlockHeader header) |
91 | 135 |
|
92 | 136 | // Set the correct difficulty |
93 | 137 | header.Difficulty = CalculateDifficulty(snapshot, _address); |
94 | | - // Ensure the extra data has all it's components |
95 | | - if (header.ExtraData.Length < CliqueSealEngine.ExtraVanityLength) |
96 | | - { |
97 | | - for (int i = 0; i < CliqueSealEngine.ExtraVanityLength - header.ExtraData.Length; i++) |
98 | | - { |
99 | | - header.ExtraData.Append((byte)0); |
100 | | - } |
101 | | - } |
| 138 | + header.TotalDifficulty = parent.TotalDifficulty + header.Difficulty; |
| 139 | + if (_logger.IsDebug) _logger.Debug($"Setting total difficulty to {parent.TotalDifficulty} + {header.Difficulty}."); |
| 140 | + |
| 141 | + // Set extra data |
| 142 | + int mainBytesLength = CliqueSealEngine.ExtraVanityLength + CliqueSealEngine.ExtraSealLength; |
| 143 | + int signerBytesLength = isEpochBlock ? 20 * snapshot.Signers.Count : 0; |
| 144 | + int extraDataLength = mainBytesLength + signerBytesLength; |
| 145 | + header.ExtraData = new byte[extraDataLength]; |
102 | 146 |
|
103 | | - header.ExtraData = header.ExtraData.Take(CliqueSealEngine.ExtraVanityLength).ToArray(); |
| 147 | + byte[] clientName = Encoding.UTF8.GetBytes("Nethermind"); |
| 148 | + Array.Copy(clientName, header.ExtraData, clientName.Length); |
104 | 149 |
|
105 | | - if ((ulong)number % _config.Epoch == 0) |
| 150 | + if (isEpochBlock) |
106 | 151 | { |
107 | | - foreach (Address signer in snapshot.Signers.Keys) |
| 152 | + for (int i = 0; i < snapshot.Signers.Keys.Count; i++) |
108 | 153 | { |
109 | | - foreach (byte addressByte in signer.Bytes) |
110 | | - { |
111 | | - header.ExtraData.Append(addressByte); |
112 | | - } |
| 154 | + Address signer = snapshot.Signers.Keys[i]; |
| 155 | + int index = CliqueSealEngine.ExtraVanityLength + 20 * i; |
| 156 | + Array.Copy(signer.Bytes, 0, header.ExtraData, index, signer.Bytes.Length); |
113 | 157 | } |
114 | 158 | } |
115 | 159 |
|
116 | | - byte[] extraSeal = new byte[CliqueSealEngine.ExtraSealLength]; |
117 | | - for (int i = 0; i < CliqueSealEngine.ExtraSealLength; i++) |
118 | | - { |
119 | | - header.ExtraData.Append((byte)0); |
120 | | - } |
121 | | - |
122 | 160 | // Mix digest is reserved for now, set to empty |
| 161 | + header.MixHash = Keccak.Zero; |
123 | 162 | // Ensure the timestamp has the correct delay |
124 | | - BlockHeader parent = _blockTree.FindHeader(header.ParentHash); |
125 | | - if (parent == null) |
126 | | - { |
127 | | - throw new InvalidOperationException("Unknown ancestor"); |
128 | | - } |
129 | | - |
130 | 163 | header.Timestamp = parent.Timestamp + _config.BlockPeriod; |
131 | 164 | long currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); |
132 | 165 | if (header.Timestamp < currentTimestamp) |
133 | 166 | { |
134 | 167 | header.Timestamp = new UInt256(currentTimestamp); |
135 | 168 | } |
136 | | - } |
137 | 169 |
|
138 | | - private Block Finalize(StateProvider state, BlockHeader header, Transaction[] txs, BlockHeader[] uncles, TransactionReceipt[] receipts) |
139 | | - { |
140 | | - // No block rewards in PoA, so the state remains as is and uncles are dropped |
141 | | - header.StateRoot = state.StateRoot; |
142 | | - header.OmmersHash = BlockHeader.CalculateHash((BlockHeader)null); |
143 | | - // Assemble and return the final block for sealing |
144 | | - return new Block(header, txs, null); |
| 170 | + var transactions = _transactionPool.GetPendingTransactions().OrderBy(t => t?.Nonce); // by nonce in case there are two transactions for the same account |
| 171 | + |
| 172 | + var selectedTxs = new List<Transaction>(); |
| 173 | + BigInteger gasRemaining = header.GasLimit; |
| 174 | + |
| 175 | + if (_logger.IsDebug) _logger.Debug($"Collecting pending transactions at min gas price {MinGasPriceForMining} and block gas limit {gasRemaining}."); |
| 176 | + |
| 177 | + int total = 0; |
| 178 | + foreach (Transaction transaction in transactions) |
| 179 | + { |
| 180 | + total++; |
| 181 | + if (transaction == null) throw new InvalidOperationException("Block transaction is null"); |
| 182 | + |
| 183 | + if (transaction.GasPrice < MinGasPriceForMining) |
| 184 | + { |
| 185 | + if (_logger.IsTrace) _logger.Trace($"Rejecting transaction - gas price ({transaction.GasPrice}) too low (min gas price: {MinGasPriceForMining}."); |
| 186 | + continue; |
| 187 | + } |
| 188 | + |
| 189 | + if (transaction.GasLimit > gasRemaining) |
| 190 | + { |
| 191 | + if (_logger.IsTrace) _logger.Trace($"Rejecting transaction - gas limit ({transaction.GasPrice}) more than remaining gas ({gasRemaining})."); |
| 192 | + break; |
| 193 | + } |
| 194 | + |
| 195 | + selectedTxs.Add(transaction); |
| 196 | + gasRemaining -= transaction.GasLimit; |
| 197 | + } |
| 198 | + |
| 199 | + if (_logger.IsDebug) _logger.Debug($"Collected {selectedTxs.Count} out of {total} pending transactions."); |
| 200 | + |
| 201 | + |
| 202 | + Block block = new Block(header, selectedTxs, new BlockHeader[0]); |
| 203 | + header.TransactionsRoot = block.CalculateTransactionsRoot(); |
| 204 | + return block; |
145 | 205 | } |
146 | 206 |
|
147 | | - private UInt256 CalculateDifficulty(ulong time, BlockHeader parent) |
| 207 | + private void OnNewPendingTx(object sender, TransactionEventArgs e) |
148 | 208 | { |
149 | | - Snapshot snapshot = _sealEngine.GetOrCreateSnapshot(parent.Number, parent.Hash); |
150 | | - return CalculateDifficulty(snapshot, _address); |
| 209 | + Block block = PrepareBlock(); |
| 210 | + if (block == null) |
| 211 | + { |
| 212 | + if (_logger.IsError) _logger.Error("Failed to prepare block for mining."); |
| 213 | + return; |
| 214 | + } |
| 215 | + |
| 216 | + Block processedBlock = _processor.Process(block, ProcessingOptions.NoValidation | ProcessingOptions.ReadOnlyChain | ProcessingOptions.WithRollback, NullTraceListener.Instance); |
| 217 | + if (processedBlock == null) |
| 218 | + { |
| 219 | + if (_logger.IsError) _logger.Error("Block prepared by block producer was rejected by processor"); |
| 220 | + return; |
| 221 | + } |
| 222 | + |
| 223 | + if (_logger.IsInfo) _logger.Info($"Suggesting newly mined block {processedBlock.ToString(Block.Format.HashAndNumber)}"); |
| 224 | + _blockTree.SuggestBlock(processedBlock); |
151 | 225 | } |
152 | 226 |
|
153 | 227 | private UInt256 CalculateDifficulty(Snapshot snapshot, Address signer) |
|
0 commit comments