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 }