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 }