Coverage Summary for Class: BlockToMineBuilder (co.rsk.mine)
Class |
Class, %
|
Method, %
|
Line, %
|
BlockToMineBuilder |
0%
(0/1)
|
0%
(0/7)
|
0%
(0/78)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2018 RSK Labs Ltd.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Lesser General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 package co.rsk.mine;
20
21 import co.rsk.config.MiningConfig;
22 import co.rsk.core.BlockDifficulty;
23 import co.rsk.core.Coin;
24 import co.rsk.core.DifficultyCalculator;
25 import co.rsk.core.RskAddress;
26 import co.rsk.core.bc.BlockExecutor;
27 import co.rsk.core.bc.BlockHashesHelper;
28 import co.rsk.core.bc.BlockResult;
29 import co.rsk.core.bc.FamilyUtils;
30 import co.rsk.db.RepositoryLocator;
31 import co.rsk.db.RepositorySnapshot;
32 import co.rsk.panic.PanicProcessor;
33 import co.rsk.remasc.RemascTransaction;
34 import co.rsk.validators.BlockValidationRule;
35 import com.google.common.annotations.VisibleForTesting;
36 import org.ethereum.config.blockchain.upgrades.ActivationConfig;
37 import org.ethereum.config.blockchain.upgrades.ConsensusRule;
38 import org.ethereum.core.*;
39 import org.ethereum.crypto.HashUtil;
40 import org.ethereum.db.BlockStore;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import java.math.BigInteger;
45 import java.util.*;
46
47 /**
48 * This component helps build a new block to mine.
49 * It can also be used to generate a new block from the pending state, which is useful
50 * in places like Web3 with the 'pending' parameter.
51 */
52 public class BlockToMineBuilder {
53 private static final Logger logger = LoggerFactory.getLogger("blocktominebuilder");
54
55 private final ActivationConfig activationConfig;
56 private final MiningConfig miningConfig;
57 private final RepositoryLocator repositoryLocator;
58 private final BlockStore blockStore;
59 private final TransactionPool transactionPool;
60 private final DifficultyCalculator difficultyCalculator;
61 private final GasLimitCalculator gasLimitCalculator;
62 private final BlockValidationRule validationRules;
63 private final MinerClock clock;
64 private final BlockFactory blockFactory;
65 private final BlockExecutor executor;
66 private static final PanicProcessor panicProcessor = new PanicProcessor();
67
68 private final MinimumGasPriceCalculator minimumGasPriceCalculator;
69 private final MinerUtils minerUtils;
70
71 private final ForkDetectionDataCalculator forkDetectionDataCalculator;
72
73 public BlockToMineBuilder(
74 ActivationConfig activationConfig,
75 MiningConfig miningConfig,
76 RepositoryLocator repositoryLocator,
77 BlockStore blockStore,
78 TransactionPool transactionPool,
79 DifficultyCalculator difficultyCalculator,
80 GasLimitCalculator gasLimitCalculator,
81 ForkDetectionDataCalculator forkDetectionDataCalculator,
82 BlockValidationRule validationRules,
83 MinerClock clock,
84 BlockFactory blockFactory,
85 BlockExecutor blockExecutor,
86 MinimumGasPriceCalculator minimumGasPriceCalculator,
87 MinerUtils minerUtils) {
88 this.activationConfig = Objects.requireNonNull(activationConfig);
89 this.miningConfig = Objects.requireNonNull(miningConfig);
90 this.repositoryLocator = Objects.requireNonNull(repositoryLocator);
91 this.blockStore = Objects.requireNonNull(blockStore);
92 this.transactionPool = Objects.requireNonNull(transactionPool);
93 this.difficultyCalculator = Objects.requireNonNull(difficultyCalculator);
94 this.gasLimitCalculator = Objects.requireNonNull(gasLimitCalculator);
95 this.forkDetectionDataCalculator = Objects.requireNonNull(forkDetectionDataCalculator);
96 this.validationRules = Objects.requireNonNull(validationRules);
97 this.clock = Objects.requireNonNull(clock);
98 this.blockFactory = blockFactory;
99 this.executor = blockExecutor;
100 this.minimumGasPriceCalculator = minimumGasPriceCalculator;
101 this.minerUtils = minerUtils;
102 }
103
104 /**
105 * Creates a new block to mine based on the previous mainchain blocks.
106 *
107 * @param mainchainHeaders last best chain blocks where 0 index is the best block and so on.
108 * @param extraData extra data to pass to the block being built.
109 */
110 public BlockResult build(List<BlockHeader> mainchainHeaders, byte[] extraData) {
111 BlockHeader newBlockParentHeader = mainchainHeaders.get(0);
112 List<BlockHeader> uncles = FamilyUtils.getUnclesHeaders(
113 blockStore,
114 newBlockParentHeader.getNumber() + 1,
115 newBlockParentHeader.getHash(),
116 miningConfig.getUncleGenerationLimit()
117 );
118
119
120 if (uncles.size() > miningConfig.getUncleListLimit()) {
121 uncles = uncles.subList(0, miningConfig.getUncleListLimit());
122 }
123
124 Coin minimumGasPrice = minimumGasPriceCalculator.calculate(newBlockParentHeader.getMinimumGasPrice());
125
126 final List<Transaction> txsToRemove = new ArrayList<>();
127 final List<Transaction> txs = getTransactions(txsToRemove, newBlockParentHeader, minimumGasPrice);
128 final Block newBlock = createBlock(mainchainHeaders, uncles, txs, minimumGasPrice, extraData);
129
130 removePendingTransactions(txsToRemove);
131 return executor.executeAndFill(newBlock, newBlockParentHeader);
132 }
133
134 private List<Transaction> getTransactions(List<Transaction> txsToRemove, BlockHeader parentHeader, Coin minGasPrice) {
135 logger.debug("getting transactions from pending state");
136 List<Transaction> txs = minerUtils.getAllTransactions(transactionPool);
137 logger.debug("{} transaction(s) collected from pending state", txs.size());
138
139 Transaction remascTx = new RemascTransaction(parentHeader.getNumber() + 1);
140 txs.add(remascTx);
141
142 Map<RskAddress, BigInteger> accountNonces = new HashMap<>();
143
144 RepositorySnapshot originalRepo = repositoryLocator.snapshotAt(parentHeader);
145
146 return minerUtils.filterTransactions(txsToRemove, txs, accountNonces, originalRepo, minGasPrice);
147 }
148
149 private void removePendingTransactions(List<Transaction> transactions) {
150 transactionPool.removeTransactions(transactions);
151 }
152
153 @VisibleForTesting
154 public Block createBlock(
155 List<BlockHeader> mainchainHeaders,
156 List<BlockHeader> uncles,
157 List<Transaction> txs,
158 Coin minimumGasPrice,
159 byte[] extraData) {
160 BlockHeader newHeader = createHeader(mainchainHeaders, uncles, txs, minimumGasPrice, extraData);
161 Block newBlock = blockFactory.newBlock(newHeader, txs, uncles, false);
162
163 // TODO(nacho): The validation rules should accept a list of uncles and we should never build invalid blocks.
164 if (validationRules.isValid(newBlock)) {
165 return newBlock;
166 }
167
168 // Some validation rule failed (all validations run are related with uncles rules),
169 // log the panic, and create again the block without uncles to avoid fail abruptly.
170 panicProcessor.panic("buildBlock", "some validation failed trying to create a new block");
171
172 newHeader = createHeader(mainchainHeaders, Collections.emptyList(), txs, minimumGasPrice, extraData);
173 return blockFactory.newBlock(newHeader, txs, Collections.emptyList(), false);
174 }
175
176 private BlockHeader createHeader(
177 List<BlockHeader> mainchainHeaders,
178 List<BlockHeader> uncles,
179 List<Transaction> txs,
180 Coin minimumGasPrice,
181 byte[] extraData) {
182 final byte[] unclesListHash = HashUtil.keccak256(BlockHeader.getUnclesEncodedEx(uncles));
183
184 BlockHeader newBlockParentHeader = mainchainHeaders.get(0);
185 final long timestampSeconds = clock.calculateTimestampForChild(newBlockParentHeader);
186
187 // Set gas limit before executing block
188 BigInteger minGasLimit = BigInteger.valueOf(miningConfig.getGasLimit().getMininimum());
189 BigInteger targetGasLimit = BigInteger.valueOf(miningConfig.getGasLimit().getTarget());
190 BigInteger parentGasLimit = new BigInteger(1, newBlockParentHeader.getGasLimit());
191 BigInteger gasUsed = BigInteger.valueOf(newBlockParentHeader.getGasUsed());
192 boolean forceLimit = miningConfig.getGasLimit().isTargetForced();
193 BigInteger gasLimit = gasLimitCalculator.calculateBlockGasLimit(parentGasLimit,
194 gasUsed, minGasLimit, targetGasLimit, forceLimit);
195 byte[] forkDetectionData = forkDetectionDataCalculator.calculateWithBlockHeaders(mainchainHeaders);
196
197 long blockNumber = newBlockParentHeader.getNumber() + 1;
198
199 // ummRoot can not be set to a value yet since the UMM contracts are not yet implemented
200 byte[] ummRoot = activationConfig.isActive(ConsensusRule.RSKIPUMM, blockNumber) ? new byte[0] : null;
201
202 final BlockHeader newHeader = blockFactory
203 .getBlockHeaderBuilder()
204 .setParentHash(newBlockParentHeader.getHash().getBytes())
205 .setUnclesHash(unclesListHash)
206 .setCoinbase(miningConfig.getCoinbaseAddress())
207 .setTxTrieRoot(BlockHashesHelper.getTxTrieRoot(
208 txs, activationConfig.isActive(ConsensusRule.RSKIP126, blockNumber)
209 )
210 )
211 .setDifficulty(BlockDifficulty.ONE)
212 .setNumber(blockNumber)
213 .setGasLimit(gasLimit.toByteArray())
214 .setGasUsed(0)
215 .setTimestamp(timestampSeconds)
216 .setExtraData(extraData)
217 .setMergedMiningForkDetectionData(forkDetectionData)
218 .setMinimumGasPrice(minimumGasPrice)
219 .setUncleCount(uncles.size())
220 .setUmmRoot(ummRoot)
221 .build();
222
223 newHeader.setDifficulty(difficultyCalculator.calcDifficulty(newHeader, newBlockParentHeader));
224 return newHeader;
225 }
226 }