Coverage Summary for Class: TransactionExecutor (org.ethereum.core)

Class Class, % Method, % Line, %
TransactionExecutor 100% (1/1) 87% (20/23) 66% (190/288)


1 /* 2  * This file is part of RskJ 3  * Copyright (C) 2017 RSK Labs Ltd. 4  * (derived from ethereumJ library, Copyright (c) 2016 <ether.camp>) 5  * 6  * This program is free software: you can redistribute it and/or modify 7  * it under the terms of the GNU Lesser General Public License as published by 8  * the Free Software Foundation, either version 3 of the License, or 9  * (at your option) any later version. 10  * 11  * This program is distributed in the hope that it will be useful, 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14  * GNU Lesser General Public License for more details. 15  * 16  * You should have received a copy of the GNU Lesser General Public License 17  * along with this program. If not, see <http://www.gnu.org/licenses/>. 18  */ 19  20 package org.ethereum.core; 21  22 import co.rsk.config.VmConfig; 23 import co.rsk.core.Coin; 24 import co.rsk.core.RskAddress; 25 import co.rsk.metrics.profilers.Metric; 26 import co.rsk.metrics.profilers.Profiler; 27 import co.rsk.metrics.profilers.ProfilerFactory; 28 import co.rsk.panic.PanicProcessor; 29 import co.rsk.rpc.modules.trace.ProgramSubtrace; 30 import org.ethereum.config.Constants; 31 import org.ethereum.config.blockchain.upgrades.ActivationConfig; 32 import org.ethereum.config.blockchain.upgrades.ConsensusRule; 33 import org.ethereum.db.BlockStore; 34 import org.ethereum.db.ReceiptStore; 35 import org.ethereum.vm.*; 36 import org.ethereum.vm.exception.VMException; 37 import org.ethereum.vm.program.Program; 38 import org.ethereum.vm.program.ProgramResult; 39 import org.ethereum.vm.program.invoke.ProgramInvoke; 40 import org.ethereum.vm.program.invoke.ProgramInvokeFactory; 41 import org.ethereum.vm.program.invoke.TransferInvoke; 42 import org.ethereum.vm.trace.ProgramTrace; 43 import org.ethereum.vm.trace.ProgramTraceProcessor; 44 import org.ethereum.vm.trace.SummarizedProgramTrace; 45 import org.slf4j.Logger; 46 import org.slf4j.LoggerFactory; 47  48 import java.math.BigInteger; 49 import java.util.*; 50 import java.util.stream.Collectors; 51  52 import static co.rsk.util.ListArrayUtil.getLength; 53 import static co.rsk.util.ListArrayUtil.isEmpty; 54 import static org.ethereum.config.blockchain.upgrades.ConsensusRule.RSKIP174; 55 import static org.ethereum.util.BIUtil.*; 56 import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; 57  58 /** 59  * @author Roman Mandeleil 60  * @since 19.12.2014 61  */ 62 public class TransactionExecutor { 63  64  private static final Logger logger = LoggerFactory.getLogger("execute"); 65  private static final Profiler profiler = ProfilerFactory.getInstance(); 66  private static final PanicProcessor panicProcessor = new PanicProcessor(); 67  68  private final Constants constants; 69  private final ActivationConfig.ForBlock activations; 70  private final Transaction tx; 71  private final int txindex; 72  private final Repository track; 73  private final Repository cacheTrack; 74  private final BlockStore blockStore; 75  private final ReceiptStore receiptStore; 76  private final BlockFactory blockFactory; 77  private final VmConfig vmConfig; 78  private final PrecompiledContracts precompiledContracts; 79  private final boolean enableRemasc; 80  private String executionError = ""; 81  private final long gasUsedInTheBlock; 82  private Coin paidFees; 83  84  private final ProgramInvokeFactory programInvokeFactory; 85  private final RskAddress coinbase; 86  87  private TransactionReceipt receipt; 88  private ProgramResult result = new ProgramResult(); 89  private final Block executionBlock; 90  91  private VM vm; 92  private Program program; 93  private List<ProgramSubtrace> subtraces; 94  95  private PrecompiledContracts.PrecompiledContract precompiledContract; 96  97  private long mEndGas = 0; 98  private long basicTxCost = 0; 99  private List<LogInfo> logs = null; 100  private final Set<DataWord> deletedAccounts; 101  private SignatureCache signatureCache; 102  103  private boolean localCall = false; 104  105  public TransactionExecutor( 106  Constants constants, ActivationConfig activationConfig, Transaction tx, int txindex, RskAddress coinbase, 107  Repository track, BlockStore blockStore, ReceiptStore receiptStore, BlockFactory blockFactory, 108  ProgramInvokeFactory programInvokeFactory, Block executionBlock, long gasUsedInTheBlock, VmConfig vmConfig, 109  boolean remascEnabled, PrecompiledContracts precompiledContracts, Set<DataWord> deletedAccounts, 110  SignatureCache signatureCache) { 111  this.constants = constants; 112  this.signatureCache = signatureCache; 113  this.activations = activationConfig.forBlock(executionBlock.getNumber()); 114  this.tx = tx; 115  this.txindex = txindex; 116  this.coinbase = coinbase; 117  this.track = track; 118  this.cacheTrack = track.startTracking(); 119  this.blockStore = blockStore; 120  this.receiptStore = receiptStore; 121  this.blockFactory = blockFactory; 122  this.programInvokeFactory = programInvokeFactory; 123  this.executionBlock = executionBlock; 124  this.gasUsedInTheBlock = gasUsedInTheBlock; 125  this.vmConfig = vmConfig; 126  this.precompiledContracts = precompiledContracts; 127  this.enableRemasc = remascEnabled; 128  this.deletedAccounts = new HashSet<>(deletedAccounts); 129  } 130  131  /** 132  * Validates and executes the transaction 133  * 134  * @return true if the transaction is valid and executed, false if the transaction is invalid 135  */ 136  public boolean executeTransaction() { 137  if (!this.init()) { 138  return false; 139  } 140  141  this.execute(); 142  this.go(); 143  this.finalization(); 144  145  return true; 146  } 147  148  /** 149  * Do all the basic validation, if the executor 150  * will be ready to run the transaction at the end 151  * set readyToExecute = true 152  */ 153  private boolean init() { 154  basicTxCost = tx.transactionCost(constants, activations); 155  156  if (localCall) { 157  return true; 158  } 159  160  long txGasLimit = GasCost.toGas(tx.getGasLimit()); 161  long curBlockGasLimit = GasCost.toGas(executionBlock.getGasLimit()); 162  163  if (!gasIsValid(txGasLimit, curBlockGasLimit)) { 164  return false; 165  } 166  167  if (!nonceIsValid()) { 168  return false; 169  } 170  171  172  Coin totalCost = tx.getValue(); 173  174  if (basicTxCost > 0 ) { 175  // add gas cost only for priced transactions 176  Coin txGasCost = tx.getGasPrice().multiply(BigInteger.valueOf(txGasLimit)); 177  totalCost = totalCost.add(txGasCost); 178  } 179  180  Coin senderBalance = track.getBalance(tx.getSender()); 181  182  if (!isCovers(senderBalance, totalCost)) { 183  184  logger.warn("Not enough cash: Require: {}, Sender cash: {}, tx {}", totalCost, senderBalance, tx.getHash()); 185  logger.warn("Transaction Data: {}", tx); 186  logger.warn("Tx Included in the following block: {}", this.executionBlock); 187  188  execError(String.format("Not enough cash: Require: %s, Sender cash: %s", totalCost, senderBalance)); 189  190  return false; 191  } 192  193  if (!transactionAddressesAreValid()) { 194  return false; 195  } 196  197  return true; 198  } 199  200  private boolean transactionAddressesAreValid() { 201  // Prevent transactions with excessive address size 202  byte[] receiveAddress = tx.getReceiveAddress().getBytes(); 203  if (receiveAddress != null && !Arrays.equals(receiveAddress, EMPTY_BYTE_ARRAY) && receiveAddress.length > Constants.getMaxAddressByteLength()) { 204  logger.warn("Receiver address to long: size: {}, tx {}", receiveAddress.length, tx.getHash()); 205  logger.warn("Transaction Data: {}", tx); 206  logger.warn("Tx Included in the following block: {}", this.executionBlock); 207  208  return false; 209  } 210  211  if (!tx.acceptTransactionSignature(constants.getChainId())) { 212  logger.warn("Transaction {} signature not accepted: {}", tx.getHash(), tx.getSignature()); 213  logger.warn("Transaction Data: {}", tx); 214  logger.warn("Tx Included in the following block: {}", this.executionBlock); 215  216  panicProcessor.panic("invalidsignature", 217  String.format("Transaction %s signature not accepted: %s", 218  tx.getHash(), tx.getSignature())); 219  execError(String.format("Transaction signature not accepted: %s", tx.getSignature())); 220  221  return false; 222  } 223  224  return true; 225  } 226  227  private boolean nonceIsValid() { 228  BigInteger reqNonce = track.getNonce(tx.getSender(signatureCache)); 229  BigInteger txNonce = toBI(tx.getNonce()); 230  231  if (isNotEqual(reqNonce, txNonce)) { 232  if (logger.isWarnEnabled()) { 233  logger.warn("Invalid nonce: sender {}, required: {} , tx.nonce: {}, tx {}", tx.getSender(), reqNonce, txNonce, tx.getHash()); 234  logger.warn("Transaction Data: {}", tx); 235  logger.warn("Tx Included in the following block: {}", this.executionBlock.getShortDescr()); 236  } 237  238  execError(String.format("Invalid nonce: required: %s , tx.nonce: %s", reqNonce, txNonce)); 239  return false; 240  } 241  242  return true; 243  } 244  245  private boolean gasIsValid(long txGasLimit, long curBlockGasLimit) { 246  // if we've passed the curBlockGas limit we must stop exec 247  // cumulativeGas being equal to GasCost.MAX_GAS is a border condition 248  // which is used on some stress tests, but its far from being practical 249  // as the current gas limit on blocks is 6.8M... several orders of magnitude 250  // less than the theoretical max gas on blocks. 251  long cumulativeGas = GasCost.add(txGasLimit, gasUsedInTheBlock); 252  253  boolean cumulativeGasReached = cumulativeGas > curBlockGasLimit || cumulativeGas == GasCost.MAX_GAS; 254  if (cumulativeGasReached) { 255  execError(String.format("Too much gas used in this block: available in block: %s tx sent: %s", 256  curBlockGasLimit - txGasLimit, 257  txGasLimit)); 258  return false; 259  } 260  261  if (txGasLimit < basicTxCost) { 262  execError(String.format("Not enough gas for transaction execution: tx needs: %s tx sent: %s", basicTxCost, txGasLimit)); 263  return false; 264  } 265  266  return true; 267  } 268  269  private void execute() { 270  logger.trace("Execute transaction {} {}", toBI(tx.getNonce()), tx.getHash()); 271  272  if (!localCall) { 273  274  track.increaseNonce(tx.getSender()); 275  276  long txGasLimit = GasCost.toGas(tx.getGasLimit()); 277  Coin txGasCost = tx.getGasPrice().multiply(BigInteger.valueOf(txGasLimit)); 278  track.addBalance(tx.getSender(), txGasCost.negate()); 279  280  logger.trace("Paying: txGasCost: [{}], gasPrice: [{}], gasLimit: [{}]", txGasCost, tx.getGasPrice(), txGasLimit); 281  } 282  283  if (tx.isContractCreation()) { 284  create(); 285  } else { 286  call(); 287  } 288  } 289  290  private boolean enoughGas(long txGasLimit, long requiredGas, long gasUsed) { 291  if (!activations.isActive(ConsensusRule.RSKIP136)) { 292  return txGasLimit >= requiredGas; 293  } 294  return txGasLimit >= gasUsed; 295  } 296  297  private void call() { 298  logger.trace("Call transaction {} {}", toBI(tx.getNonce()), tx.getHash()); 299  300  RskAddress targetAddress = tx.getReceiveAddress(); 301  302  // DataWord(targetAddress)) can fail with exception: 303  // java.lang.RuntimeException: Data word can't exceed 32 bytes: 304  // if targetAddress size is greater than 32 bytes. 305  // But init() will detect this earlier 306  precompiledContract = precompiledContracts.getContractForAddress(activations, DataWord.valueOf(targetAddress.getBytes())); 307  308  this.subtraces = new ArrayList<>(); 309  310  if (precompiledContract != null) { 311  Metric metric = profiler.start(Profiler.PROFILING_TYPE.PRECOMPILED_CONTRACT_INIT); 312  precompiledContract.init(tx, executionBlock, track, blockStore, receiptStore, result.getLogInfoList()); 313  profiler.stop(metric); 314  metric = profiler.start(Profiler.PROFILING_TYPE.PRECOMPILED_CONTRACT_EXECUTE); 315  316  long requiredGas = precompiledContract.getGasForData(tx.getData()); 317  long txGasLimit = GasCost.toGas(tx.getGasLimit()); 318  long gasUsed = GasCost.add(requiredGas, basicTxCost); 319  if (!localCall && !enoughGas(txGasLimit, requiredGas, gasUsed)) { 320  // no refund no endowment 321  execError(String.format( "Out of Gas calling precompiled contract at block %d " + 322  "for address 0x%s. required: %s, used: %s, left: %s ", 323  executionBlock.getNumber(), targetAddress.toString(), requiredGas, gasUsed, mEndGas)); 324  mEndGas = 0; 325  profiler.stop(metric); 326  return; 327  } 328  329  mEndGas = activations.isActive(ConsensusRule.RSKIP136) ? 330  GasCost.subtract(txGasLimit, gasUsed) : 331  txGasLimit - gasUsed; 332  333  // FIXME: save return for vm trace 334  try { 335  byte[] out = precompiledContract.execute(tx.getData()); 336  this.subtraces = precompiledContract.getSubtraces(); 337  result.setHReturn(out); 338  if (!track.isExist(targetAddress)) { 339  track.createAccount(targetAddress); 340  track.setupContract(targetAddress); 341  } else if (!track.isContract(targetAddress)) { 342  track.setupContract(targetAddress); 343  } 344  } catch (VMException | RuntimeException e) { 345  result.setException(e); 346  } 347  result.spendGas(gasUsed); 348  profiler.stop(metric); 349  } else { 350  byte[] code = track.getCode(targetAddress); 351  // Code can be null 352  if (isEmpty(code)) { 353  mEndGas = GasCost.subtract(GasCost.toGas(tx.getGasLimit()), basicTxCost); 354  result.spendGas(basicTxCost); 355  } else { 356  ProgramInvoke programInvoke = 357  programInvokeFactory.createProgramInvoke(tx, txindex, executionBlock, cacheTrack, blockStore); 358  359  this.vm = new VM(vmConfig, precompiledContracts); 360  this.program = new Program(vmConfig, precompiledContracts, blockFactory, activations, code, programInvoke, tx, deletedAccounts); 361  } 362  } 363  364  if (result.getException() == null) { 365  Coin endowment = tx.getValue(); 366  cacheTrack.transfer(tx.getSender(), targetAddress, endowment); 367  } 368  } 369  370  private void create() { 371  RskAddress newContractAddress = tx.getContractAddress(); 372  cacheTrack.createAccount(newContractAddress, activations.isActive(RSKIP174) && cacheTrack.isExist(newContractAddress)); 373  374  if (isEmpty(tx.getData())) { 375  mEndGas = GasCost.subtract(GasCost.toGas(tx.getGasLimit()), basicTxCost); 376  // If there is no data, then the account is created, but without code nor 377  // storage. It doesn't even call setupContract() to setup a storage root 378  } else { 379  cacheTrack.setupContract(newContractAddress); 380  ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke(tx, txindex, executionBlock, cacheTrack, blockStore); 381  382  this.vm = new VM(vmConfig, precompiledContracts); 383  this.program = new Program(vmConfig, precompiledContracts, blockFactory, activations, tx.getData(), programInvoke, tx, deletedAccounts); 384  385  // reset storage if the contract with the same address already exists 386  // TCK test case only - normally this is near-impossible situation in the real network 387  /* Storage keys not available anymore in a fast way 388  ContractDetails contractDetails = program.getStorage().getContractDetails(newContractAddress); 389  for (DataWord key : contractDetails.getStorageKeys()) { 390  program.storageSave(key, DataWord.ZERO); 391  } 392  */ 393  } 394  395  Coin endowment = tx.getValue(); 396  cacheTrack.transfer(tx.getSender(), newContractAddress, endowment); 397  } 398  399  private void execError(Throwable err) { 400  logger.error("execError: ", err); 401  executionError = err.getMessage(); 402  } 403  404  private void execError(String err) { 405  logger.trace(err); 406  executionError = err; 407  } 408  409  private void go() { 410  // TODO: transaction call for pre-compiled contracts 411  if (vm == null) { 412  cacheTrack.commit(); 413  return; 414  } 415  416  logger.trace("Go transaction {} {}", toBI(tx.getNonce()), tx.getHash()); 417  418  //Set the deleted accounts in the block in the remote case there is a CREATE2 creating a deleted account 419  420  Metric metric = profiler.start(Profiler.PROFILING_TYPE.VM_EXECUTE); 421  try { 422  423  // Charge basic cost of the transaction 424  program.spendGas(tx.transactionCost(constants, activations), "TRANSACTION COST"); 425  426  vm.play(program); 427  428  result = program.getResult(); 429  mEndGas = GasCost.subtract(GasCost.toGas(tx.getGasLimit()), program.getResult().getGasUsed()); 430  431  if (tx.isContractCreation() && !result.isRevert()) { 432  createContract(); 433  } 434  435  if (result.getException() != null || result.isRevert()) { 436  result.clearFieldsOnException(); 437  cacheTrack.rollback(); 438  439  if (result.getException() != null) { 440  throw result.getException(); 441  } else { 442  execError("REVERT opcode executed"); 443  } 444  } 445  } catch (Exception e) { 446  cacheTrack.rollback(); 447  mEndGas = 0; 448  execError(e); 449  profiler.stop(metric); 450  return; 451  } 452  cacheTrack.commit(); 453  profiler.stop(metric); 454  } 455  456  private void createContract() { 457  int createdContractSize = getLength(program.getResult().getHReturn()); 458  long returnDataGasValue = GasCost.multiply(GasCost.CREATE_DATA, createdContractSize); 459  if (mEndGas < returnDataGasValue) { 460  program.setRuntimeFailure( 461  Program.ExceptionHelper.notEnoughSpendingGas( 462  program, 463  "No gas to return just created contract", 464  returnDataGasValue)); 465  result = program.getResult(); 466  result.setHReturn(EMPTY_BYTE_ARRAY); 467  } else if (createdContractSize > Constants.getMaxContractSize()) { 468  program.setRuntimeFailure( 469  Program.ExceptionHelper.tooLargeContractSize( 470  program, 471  Constants.getMaxContractSize(), 472  createdContractSize)); 473  result = program.getResult(); 474  result.setHReturn(EMPTY_BYTE_ARRAY); 475  } else { 476  mEndGas = GasCost.subtract(mEndGas, returnDataGasValue); 477  program.spendGas(returnDataGasValue, "CONTRACT DATA COST"); 478  cacheTrack.saveCode(tx.getContractAddress(), result.getHReturn()); 479  } 480  } 481  482  public TransactionReceipt getReceipt() { 483  if (receipt == null) { 484  receipt = new TransactionReceipt(); 485  long totalGasUsed = GasCost.add(gasUsedInTheBlock, getGasUsed()); 486  receipt.setCumulativeGas(totalGasUsed); 487  receipt.setTransaction(tx); 488  receipt.setLogInfoList(getVMLogs()); 489  receipt.setGasUsed(getGasUsed()); 490  receipt.setStatus(executionError.isEmpty()?TransactionReceipt.SUCCESS_STATUS:TransactionReceipt.FAILED_STATUS); 491  } 492  return receipt; 493  } 494  495  496  private void finalization() { 497  // RSK if local call gas balances must not be changed 498  if (localCall) { 499  return; 500  } 501  502  logger.trace("Finalize transaction {} {}", toBI(tx.getNonce()), tx.getHash()); 503  504  cacheTrack.commit(); 505  506  //Transaction sender is stored in cache 507  signatureCache.storeSender(tx); 508  509  // Should include only LogInfo's that was added during not rejected transactions 510  List<LogInfo> notRejectedLogInfos = result.getLogInfoList().stream() 511  .filter(logInfo -> !logInfo.isRejected()) 512  .collect(Collectors.toList()); 513  514  TransactionExecutionSummary.Builder summaryBuilder = TransactionExecutionSummary.builderFor(tx) 515  .gasLeftover(BigInteger.valueOf(mEndGas)) 516  .logs(notRejectedLogInfos) 517  .result(result.getHReturn()); 518  519  // Accumulate refunds for suicides 520  result.addFutureRefund(GasCost.multiply(result.getDeleteAccounts().size(), GasCost.SUICIDE_REFUND)); 521  long gasRefund = Math.min(result.getFutureRefund(), result.getGasUsed() / 2); 522  mEndGas = activations.isActive(ConsensusRule.RSKIP136) ? 523  GasCost.add(mEndGas, gasRefund) : 524  mEndGas + gasRefund; 525  526  summaryBuilder 527  .gasUsed(toBI(result.getGasUsed())) 528  .gasRefund(toBI(gasRefund)) 529  .deletedAccounts(result.getDeleteAccounts()) 530  .internalTransactions(result.getInternalTransactions()); 531  532  if (result.getException() != null) { 533  summaryBuilder.markAsFailed(); 534  } 535  536  logger.trace("Building transaction execution summary"); 537  538  TransactionExecutionSummary summary = summaryBuilder.build(); 539  540  // Refund for gas leftover 541  track.addBalance(tx.getSender(), summary.getLeftover().add(summary.getRefund())); 542  logger.trace("Pay total refund to sender: [{}], refund val: [{}]", tx.getSender(), summary.getRefund()); 543  544  // Transfer fees to miner 545  Coin summaryFee = summary.getFee(); 546  547  //TODO: REMOVE THIS WHEN THE LocalBLockTests starts working with REMASC 548  if(enableRemasc) { 549  logger.trace("Adding fee to remasc contract account"); 550  track.addBalance(PrecompiledContracts.REMASC_ADDR, summaryFee); 551  } else { 552  track.addBalance(coinbase, summaryFee); 553  } 554  555  this.paidFees = summaryFee; 556  557  logger.trace("Processing result"); 558  logs = notRejectedLogInfos; 559  560  result.getCodeChanges().forEach((key, value) -> track.saveCode(new RskAddress(key), value)); 561  // Traverse list of suicides 562  result.getDeleteAccounts().forEach(address -> track.delete(new RskAddress(address))); 563  564  logger.trace("tx listener done"); 565  566  logger.trace("tx finalization done"); 567  } 568  569  /** 570  * This extracts the trace to an object in memory. 571  * Refer to {@link org.ethereum.vm.VMUtils#saveProgramTraceFile} for a way to saving the trace to a file. 572  */ 573  public void extractTrace(ProgramTraceProcessor programTraceProcessor) { 574  if (program != null) { 575  // TODO improve this settings; the trace should already have the values 576  ProgramTrace trace = program.getTrace().result(result.getHReturn()).error(result.getException()).revert(result.isRevert()); 577  programTraceProcessor.processProgramTrace(trace, tx.getHash()); 578  } 579  else { 580  TransferInvoke invoke = new TransferInvoke(DataWord.valueOf(tx.getSender().getBytes()), DataWord.valueOf(tx.getReceiveAddress().getBytes()), 0L, DataWord.valueOf(tx.getValue().getBytes())); 581  582  SummarizedProgramTrace trace = new SummarizedProgramTrace(invoke); 583  584  if (this.subtraces != null) { 585  for (ProgramSubtrace subtrace : this.subtraces) { 586  trace.addSubTrace(subtrace); 587  } 588  } 589  590  programTraceProcessor.processProgramTrace(trace, tx.getHash()); 591  } 592  } 593  594  public TransactionExecutor setLocalCall(boolean localCall) { 595  this.localCall = localCall; 596  this.tx.setLocalCallTransaction(localCall); 597  return this; 598  } 599  600  public List<LogInfo> getVMLogs() { 601  return logs; 602  } 603  604  public ProgramResult getResult() { 605  return result; 606  } 607  608  public long getGasUsed() { 609  if (activations.isActive(ConsensusRule.RSKIP136)) { 610  return GasCost.subtract(GasCost.toGas(tx.getGasLimit()), mEndGas); 611  } 612  return toBI(tx.getGasLimit()).subtract(toBI(mEndGas)).longValue(); 613  } 614  615  public Coin getPaidFees() { return paidFees; } 616 }