Coverage Summary for Class: BlockExecutor (co.rsk.core.bc)
Class |
Class, %
|
Method, %
|
Line, %
|
BlockExecutor |
100%
(1/1)
|
63.2%
(12/19)
|
73.1%
(128/175)
|
1 /*
2 * This file is part of RskJ
3 * Copyright (C) 2017 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.core.bc;
20
21 import co.rsk.core.Coin;
22 import co.rsk.core.RskAddress;
23 import co.rsk.core.TransactionExecutorFactory;
24 import co.rsk.crypto.Keccak256;
25 import co.rsk.db.RepositoryLocator;
26 import co.rsk.db.StateRootHandler;
27 import co.rsk.metrics.profilers.Metric;
28 import co.rsk.metrics.profilers.Profiler;
29 import co.rsk.metrics.profilers.ProfilerFactory;
30 import com.google.common.annotations.VisibleForTesting;
31 import org.ethereum.config.blockchain.upgrades.ActivationConfig;
32 import org.ethereum.config.blockchain.upgrades.ConsensusRule;
33 import org.ethereum.core.*;
34 import org.ethereum.vm.DataWord;
35 import org.ethereum.vm.PrecompiledContracts;
36 import org.ethereum.vm.program.ProgramResult;
37 import org.ethereum.vm.trace.ProgramTraceProcessor;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41 import javax.annotation.Nullable;
42 import java.util.*;
43
44 import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP126;
45 import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP85;
46
47 /**
48 * This is a stateless class with methods to execute blocks with its transactions.
49 * There are two main use cases:
50 * - execute and validate the block final state
51 * - execute and complete the block final state
52 *
53 * Note that this class IS NOT guaranteed to be thread safe because its dependencies might hold state.
54 */
55 public class BlockExecutor {
56 private static final Logger logger = LoggerFactory.getLogger("blockexecutor");
57 private static final Profiler profiler = ProfilerFactory.getInstance();
58
59 private final RepositoryLocator repositoryLocator;
60 private final TransactionExecutorFactory transactionExecutorFactory;
61 private final StateRootHandler stateRootHandler;
62 private final ActivationConfig activationConfig;
63
64 private final Map<Keccak256, ProgramResult> transactionResults = new HashMap<>();
65 private boolean registerProgramResults;
66
67 public BlockExecutor(
68 ActivationConfig activationConfig,
69 RepositoryLocator repositoryLocator,
70 StateRootHandler stateRootHandler,
71 TransactionExecutorFactory transactionExecutorFactory) {
72 this.repositoryLocator = repositoryLocator;
73 this.transactionExecutorFactory = transactionExecutorFactory;
74 this.stateRootHandler = stateRootHandler;
75 this.activationConfig = activationConfig;
76 }
77
78 /**
79 * Execute and complete a block.
80 *
81 * @param block A block to execute and complete
82 * @param parent The parent of the block.
83 */
84 public BlockResult executeAndFill(Block block, BlockHeader parent) {
85 BlockResult result = execute(block, parent, true, false);
86 fill(block, result);
87 return result;
88 }
89
90 @VisibleForTesting
91 public void executeAndFillAll(Block block, BlockHeader parent) {
92 BlockResult result = execute(block, parent, false, true);
93 fill(block, result);
94 }
95
96 @VisibleForTesting
97 public void executeAndFillReal(Block block, BlockHeader parent) {
98 BlockResult result = execute(block, parent, false, false);
99 if (result != BlockResult.INTERRUPTED_EXECUTION_BLOCK_RESULT) {
100 fill(block, result);
101 }
102 }
103
104 private void fill(Block block, BlockResult result) {
105 Metric metric = profiler.start(Profiler.PROFILING_TYPE.FILLING_EXECUTED_BLOCK);
106 BlockHeader header = block.getHeader();
107 block.setTransactionsList(result.getExecutedTransactions());
108 boolean isRskip126Enabled = activationConfig.isActive(RSKIP126, block.getNumber());
109 header.setTransactionsRoot(BlockHashesHelper.getTxTrieRoot(block.getTransactionsList(), isRskip126Enabled));
110 header.setReceiptsRoot(BlockHashesHelper.calculateReceiptsTrieRoot(result.getTransactionReceipts(), isRskip126Enabled));
111 header.setGasUsed(result.getGasUsed());
112 header.setPaidFees(result.getPaidFees());
113 header.setStateRoot(stateRootHandler.convert(header, result.getFinalState()).getBytes());
114 header.setLogsBloom(calculateLogsBloom(result.getTransactionReceipts()));
115
116 block.flushRLP();
117 profiler.stop(metric);
118 }
119
120 /**
121 * Execute and validate the final state of a block.
122 *
123 * @param block A block to execute and complete
124 * @param parent The parent of the block.
125 * @return true if the block final state is equalBytes to the calculated final state.
126 */
127 @VisibleForTesting
128 public boolean executeAndValidate(Block block, BlockHeader parent) {
129 BlockResult result = execute(block, parent, false, false);
130
131 return this.validate(block, result);
132 }
133
134 /**
135 * Validate the final state of a block.
136 *
137 * @param block A block to validate
138 * @param result A block result (state root, receipts root, etc...)
139 * @return true if the block final state is equalBytes to the calculated final state.
140 */
141 public boolean validate(Block block, BlockResult result) {
142 Metric metric = profiler.start(Profiler.PROFILING_TYPE.BLOCK_FINAL_STATE_VALIDATION);
143 if (result == BlockResult.INTERRUPTED_EXECUTION_BLOCK_RESULT) {
144 logger.error("Block {} [{}] execution was interrupted because of an invalid transaction", block.getNumber(), block.getPrintableHash());
145 profiler.stop(metric);
146 return false;
147 }
148
149 boolean isValidStateRoot = validateStateRoot(block.getHeader(), result);
150 if (!isValidStateRoot) {
151 logger.error("Block {} [{}] given State Root is invalid", block.getNumber(), block.getPrintableHash());
152 profiler.stop(metric);
153 return false;
154 }
155
156 boolean isValidReceiptsRoot = validateReceiptsRoot(block.getHeader(), result);
157 if (!isValidReceiptsRoot) {
158 logger.error("Block {} [{}] given Receipt Root is invalid", block.getNumber(), block.getPrintableHash());
159 profiler.stop(metric);
160 return false;
161 }
162
163 boolean isValidLogsBloom = validateLogsBloom(block.getHeader(), result);
164 if (!isValidLogsBloom) {
165 logger.error("Block {} [{}] given Logs Bloom is invalid", block.getNumber(), block.getPrintableHash());
166 profiler.stop(metric);
167 return false;
168 }
169
170 if (result.getGasUsed() != block.getGasUsed()) {
171 logger.error("Block {} [{}] given gasUsed doesn't match: {} != {}", block.getNumber(), block.getPrintableHash(), block.getGasUsed(), result.getGasUsed());
172 profiler.stop(metric);
173 return false;
174 }
175
176 Coin paidFees = result.getPaidFees();
177 Coin feesPaidToMiner = block.getFeesPaidToMiner();
178
179 if (!paidFees.equals(feesPaidToMiner)) {
180 logger.error("Block {} [{}] given paidFees doesn't match: {} != {}", block.getNumber(), block.getPrintableHash(), feesPaidToMiner, paidFees);
181 profiler.stop(metric);
182 return false;
183 }
184
185 List<Transaction> executedTransactions = result.getExecutedTransactions();
186 List<Transaction> transactionsList = block.getTransactionsList();
187
188 if (!executedTransactions.equals(transactionsList)) {
189 logger.error("Block {} [{}] given txs doesn't match: {} != {}", block.getNumber(), block.getPrintableHash(), transactionsList, executedTransactions);
190 profiler.stop(metric);
191 return false;
192 }
193
194 profiler.stop(metric);
195 return true;
196 }
197
198 private boolean validateStateRoot(BlockHeader header, BlockResult result) {
199 boolean isRskip85Enabled = activationConfig.isActive(RSKIP85, header.getNumber());
200 if (!isRskip85Enabled) {
201 return true;
202 }
203
204 boolean isRskip126Enabled = activationConfig.isActive(RSKIP126, header.getNumber());
205 if (!isRskip126Enabled) {
206 byte[] orchidStateRoot = stateRootHandler.convert(header, result.getFinalState()).getBytes();
207 return Arrays.equals(orchidStateRoot, header.getStateRoot());
208 }
209
210 // we only validate state roots of blocks newer than 0.5.0 activation
211 return Arrays.equals(result.getFinalState().getHash().getBytes(), header.getStateRoot());
212 }
213
214 private boolean validateReceiptsRoot(BlockHeader header, BlockResult result) {
215 boolean isRskip126Enabled = activationConfig.isActive(RSKIP126, header.getNumber());
216 byte[] receiptsTrieRoot = BlockHashesHelper.calculateReceiptsTrieRoot(result.getTransactionReceipts(), isRskip126Enabled);
217 return Arrays.equals(receiptsTrieRoot, header.getReceiptsRoot());
218 }
219
220 private boolean validateLogsBloom(BlockHeader header, BlockResult result) {
221 return Arrays.equals(calculateLogsBloom(result.getTransactionReceipts()), header.getLogsBloom());
222 }
223
224 @VisibleForTesting
225 public BlockResult execute(Block block, BlockHeader parent, boolean discardInvalidTxs) {
226 return execute(block, parent, discardInvalidTxs, false);
227 }
228
229 public BlockResult execute(Block block, BlockHeader parent, boolean discardInvalidTxs, boolean ignoreReadyToExecute) {
230 return executeInternal(null, 0, block, parent, discardInvalidTxs, ignoreReadyToExecute);
231 }
232
233 /**
234 * Execute a block while saving the execution trace in the trace processor
235 */
236 public void traceBlock(
237 ProgramTraceProcessor programTraceProcessor,
238 int vmTraceOptions,
239 Block block,
240 BlockHeader parent,
241 boolean discardInvalidTxs,
242 boolean ignoreReadyToExecute) {
243 executeInternal(
244 Objects.requireNonNull(programTraceProcessor), vmTraceOptions, block, parent, discardInvalidTxs, ignoreReadyToExecute
245 );
246 }
247
248 private BlockResult executeInternal(
249 @Nullable ProgramTraceProcessor programTraceProcessor,
250 int vmTraceOptions,
251 Block block,
252 BlockHeader parent,
253 boolean discardInvalidTxs,
254 boolean acceptInvalidTransactions) {
255 boolean vmTrace = programTraceProcessor != null;
256 logger.trace("Start executeInternal.");
257 logger.trace("applyBlock: block: [{}] tx.list: [{}]", block.getNumber(), block.getTransactionsList().size());
258
259 // Forks the repo, does not change "repository". It will have a completely different
260 // image of the repo, where the middle caches are immediately ignored.
261 // In fact, while cloning everything, it asserts that no cache elements remains.
262 // (see assertNoCache())
263 // Which means that you must commit changes and save them to be able to recover
264 // in the next block processed.
265 // Note that creating a snapshot is important when the block is executed twice
266 // (e.g. once while building the block in tests/mining, and the other when trying
267 // to conect the block). This is because the first execution will change the state
268 // of the repository to the state post execution, so it's necessary to get it to
269 // the state prior execution again.
270 Metric metric = profiler.start(Profiler.PROFILING_TYPE.BLOCK_EXECUTE);
271
272 Repository track = repositoryLocator.startTrackingAt(parent);
273
274 maintainPrecompiledContractStorageRoots(track, activationConfig.forBlock(block.getNumber()));
275
276 int i = 1;
277 long totalGasUsed = 0;
278 Coin totalPaidFees = Coin.ZERO;
279 List<TransactionReceipt> receipts = new ArrayList<>();
280 List<Transaction> executedTransactions = new ArrayList<>();
281 Set<DataWord> deletedAccounts = new HashSet<>();
282
283 int txindex = 0;
284
285 for (Transaction tx : block.getTransactionsList()) {
286 logger.trace("apply block: [{}] tx: [{}] ", block.getNumber(), i);
287
288 TransactionExecutor txExecutor = transactionExecutorFactory.newInstance(
289 tx,
290 txindex++,
291 block.getCoinbase(),
292 track,
293 block,
294 totalGasUsed,
295 vmTrace,
296 vmTraceOptions,
297 deletedAccounts);
298 boolean transactionExecuted = txExecutor.executeTransaction();
299
300 if (!acceptInvalidTransactions && !transactionExecuted) {
301 if (discardInvalidTxs) {
302 logger.warn("block: [{}] discarded tx: [{}]", block.getNumber(), tx.getHash());
303 continue;
304 } else {
305 logger.warn("block: [{}] execution interrupted because of invalid tx: [{}]",
306 block.getNumber(), tx.getHash());
307 profiler.stop(metric);
308 return BlockResult.INTERRUPTED_EXECUTION_BLOCK_RESULT;
309 }
310 }
311
312 executedTransactions.add(tx);
313
314 if (this.registerProgramResults) {
315 this.transactionResults.put(tx.getHash(), txExecutor.getResult());
316 }
317
318 if (vmTrace) {
319 txExecutor.extractTrace(programTraceProcessor);
320 }
321
322 logger.trace("tx executed");
323
324 // No need to commit the changes here. track.commit();
325
326 logger.trace("track commit");
327
328 long gasUsed = txExecutor.getGasUsed();
329 totalGasUsed += gasUsed;
330 Coin paidFees = txExecutor.getPaidFees();
331 if (paidFees != null) {
332 totalPaidFees = totalPaidFees.add(paidFees);
333 }
334
335 deletedAccounts.addAll(txExecutor.getResult().getDeleteAccounts());
336
337 TransactionReceipt receipt = new TransactionReceipt();
338 receipt.setGasUsed(gasUsed);
339 receipt.setCumulativeGas(totalGasUsed);
340
341 receipt.setTxStatus(txExecutor.getReceipt().isSuccessful());
342 receipt.setTransaction(tx);
343 receipt.setLogInfoList(txExecutor.getVMLogs());
344 receipt.setStatus(txExecutor.getReceipt().getStatus());
345
346 logger.trace("block: [{}] executed tx: [{}]", block.getNumber(), tx.getHash());
347
348 logger.trace("tx[{}].receipt", i);
349
350 i++;
351
352 receipts.add(receipt);
353
354 logger.trace("tx done");
355 }
356
357 logger.trace("End txs executions.");
358 if (!vmTrace) {
359 logger.trace("Saving track.");
360 track.save();
361 logger.trace("End saving track.");
362 }
363
364 logger.trace("Building execution results.");
365 BlockResult result = new BlockResult(
366 block,
367 executedTransactions,
368 receipts,
369 totalGasUsed,
370 totalPaidFees,
371 vmTrace ? null : track.getTrie()
372 );
373 profiler.stop(metric);
374 logger.trace("End executeInternal.");
375 return result;
376 }
377
378 /**
379 * Precompiled contracts storage is setup like any other contract for consistency. Here, we apply this logic on the
380 * exact activation block.
381 * This method is called automatically for every block except for the Genesis (which makes an explicit call).
382 */
383 public static void maintainPrecompiledContractStorageRoots(Repository track, ActivationConfig.ForBlock activations) {
384 if (activations.isActivating(RSKIP126)) {
385 for (RskAddress addr : PrecompiledContracts.GENESIS_ADDRESSES) {
386 if (!track.isExist(addr)) {
387 track.createAccount(addr);
388 }
389 track.setupContract(addr);
390 }
391 }
392
393 for (Map.Entry<RskAddress, ConsensusRule> e : PrecompiledContracts.CONSENSUS_ENABLED_ADDRESSES.entrySet()) {
394 ConsensusRule contractActivationRule = e.getValue();
395 if (activations.isActivating(contractActivationRule)) {
396 RskAddress addr = e.getKey();
397 track.createAccount(addr);
398 track.setupContract(addr);
399 }
400 }
401 }
402
403 @VisibleForTesting
404 public static byte[] calculateLogsBloom(List<TransactionReceipt> receipts) {
405 Bloom logBloom = new Bloom();
406
407 for (TransactionReceipt receipt : receipts) {
408 logBloom.or(receipt.getBloomFilter());
409 }
410
411 return logBloom.getData();
412 }
413
414 public ProgramResult getProgramResult(Keccak256 txhash) {
415 return this.transactionResults.get(txhash);
416 }
417
418 public void setRegisterProgramResults(boolean value) {
419 this.registerProgramResults = value;
420 this.transactionResults.clear();
421 }
422 }