From aad53ba2c5aa5fd9686c0091cf301a78b2f5e660 Mon Sep 17 00:00:00 2001 From: romanman Date: Sat, 20 Dec 2014 23:09:48 +0200 Subject: [PATCH] Introducing StateTest json suite support: + passing: stInitCodeTest + passing: stLogTests + passing: stRecursiveCreate + passing: stRefundTest --- .../java/org/ethereum/core/AccountState.java | 8 +- .../org/ethereum/core/BlockchainImpl.java | 276 ++-------------- .../java/org/ethereum/core/Transaction.java | 18 +- .../ethereum/core/TransactionExecutor.java | 292 +++++++++++++++++ .../java/org/ethereum/db/ContractDetails.java | 26 ++ .../java/org/ethereum/db/RepositoryDummy.java | 308 ++++++++++++++++++ .../java/org/ethereum/db/RepositoryImpl.java | 37 ++- .../java/org/ethereum/db/RepositoryTrack.java | 27 +- .../java/org/ethereum/facade/Repository.java | 1 + .../ethereum/jsontestsuite/AccountState.java | 105 ++++-- .../ethereum/jsontestsuite/CallCreate.java | 2 +- .../java/org/ethereum/jsontestsuite/Logs.java | 64 ++++ .../ethereum/jsontestsuite/StateTestCase.java | 124 +++++++ .../jsontestsuite/StateTestSuite.java | 49 +++ .../TestProgramInvokeFactory.java | 93 ++++++ .../ethereum/jsontestsuite/TestRunner.java | 259 ++++++++++----- .../org/ethereum/jsontestsuite/TestSuite.java | 2 - .../ethereum/jsontestsuite/Transaction.java | 101 ++++++ .../org/ethereum/jsontestsuite/Utils.java | 27 ++ .../listener/EthereumListenerWrapper.java | 3 +- .../org/ethereum/manager/WorldManager.java | 4 +- .../main/java/org/ethereum/util/ByteUtil.java | 15 + .../main/java/org/ethereum/vm/GasCost.java | 2 + .../main/java/org/ethereum/vm/Program.java | 21 +- .../org/ethereum/vm/ProgramInvokeFactory.java | 184 +---------- .../ethereum/vm/ProgramInvokeFactoryImpl.java | 199 +++++++++++ .../java/org/ethereum/vm/ProgramResult.java | 9 + .../src/main/java/org/ethereum/vm/VM.java | 32 +- .../jsontestsuite/GitHubJSONTestSuite.java | 65 +++- .../jsontestsuite/GitHubStateTest.java | 88 +++++ 30 files changed, 1868 insertions(+), 573 deletions(-) create mode 100644 ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/db/RepositoryDummy.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/jsontestsuite/StateTestCase.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/jsontestsuite/StateTestSuite.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestProgramInvokeFactory.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Transaction.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Utils.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeFactoryImpl.java create mode 100644 ethereumj-core/src/test/java/test/ethereum/jsontestsuite/GitHubStateTest.java diff --git a/ethereumj-core/src/main/java/org/ethereum/core/AccountState.java b/ethereumj-core/src/main/java/org/ethereum/core/AccountState.java index ecf27b8a..2b67230f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/AccountState.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/AccountState.java @@ -156,10 +156,10 @@ public class AccountState { } public String toString() { - String ret = "Nonce: " + this.getNonce().toString() + "\n" + - "Balance: " + Denomination.toFriendlyString(getBalance()) + "\n" + - "State Root: " + Hex.toHexString(this.getStateRoot()) + "\n" + - "Code Hash: " + Hex.toHexString(this.getCodeHash()); + String ret = " Nonce: " + this.getNonce().toString() + "\n" + + " Balance: " + getBalance() + "\n" + + " State Root: " + Hex.toHexString(this.getStateRoot()) + "\n" + + " Code Hash: " + Hex.toHexString(this.getCodeHash()); return ret; } } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java b/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java index 93b68e9a..38621903 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/BlockchainImpl.java @@ -6,11 +6,10 @@ import org.ethereum.facade.Blockchain; import org.ethereum.facade.Repository; import org.ethereum.listener.EthereumListener; import org.ethereum.manager.AdminInfo; -import org.ethereum.manager.WorldManager; import org.ethereum.net.BlockQueue; import org.ethereum.net.server.ChannelManager; import org.ethereum.util.AdvancedDeviceUtils; -import org.ethereum.vm.*; +import org.ethereum.vm.ProgramInvokeFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; @@ -24,12 +23,10 @@ import java.io.IOException; import java.math.BigInteger; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; import java.util.List; import static org.ethereum.config.SystemProperties.CONFIG; import static org.ethereum.core.Denomination.SZABO; -import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; /** * The Ethereum blockchain is in many ways similar to the Bitcoin blockchain, @@ -84,7 +81,10 @@ public class BlockchainImpl implements Blockchain { private BigInteger totalDifficulty = BigInteger.ZERO; @Autowired - private WorldManager worldManager; + Wallet wallet; + + @Autowired + private EthereumListener listener; @Autowired private BlockQueue blockQueue; @@ -208,13 +208,11 @@ public class BlockchainImpl implements Blockchain { // Remove all wallet transactions as they already approved by the net - worldManager.getWallet().removeTransactions(block.getTransactionsList()); + wallet.removeTransactions(block.getTransactionsList()); + - EthereumListener listener = worldManager.getListener(); listener.trace(String.format("Block chain size: [ %d ]", this.getSize())); - - EthereumListener ethereumListener = worldManager.getListener(); - ethereumListener.onBlock(block); + listener.onBlock(block); if (blockQueue.size() == 0 && !syncDoneCalled && @@ -222,7 +220,7 @@ public class BlockchainImpl implements Blockchain { logger.info("Sync done"); syncDoneCalled = true; - ethereumListener.onSyncDone(); + listener.onSyncDone(); } } @@ -299,7 +297,6 @@ public class BlockchainImpl implements Blockchain { if(isValid(block)) { if (!block.isGenesis()) { if (!CONFIG.blockChainOnly()) { - Wallet wallet = worldManager.getWallet(); wallet.addTransactions(block.getTransactionsList()); receipts = this.applyBlock(block); wallet.processBlock(block); @@ -320,7 +317,11 @@ public class BlockchainImpl implements Blockchain { for (Transaction tx : block.getTransactionsList()) { stateLogger.info("apply block: [{}] tx: [{}] ", block.getNumber(), i); - TransactionReceipt receipt = applyTransaction(block, tx); + TransactionExecutor executor = new TransactionExecutor(tx, block.getCoinbase(), track, + programInvokeFactory, bestBlock); + executor.execute(); + + TransactionReceipt receipt = executor.getReceipt(); totalGasUsed += receipt.getCumulativeGasLong(); track.commit(); @@ -403,235 +404,6 @@ public class BlockchainImpl implements Blockchain { logger.info("*** Last block added [ #{} ]", block.getNumber()); } - /** - * Apply the transaction to the world state. - * - * During this method changes to the repository are either permanent or possibly reverted by a VM exception. - * - * @param block - the block which contains the transactions - * @param tx - the transaction to be applied - * @return gasUsed - the total amount of gas used for this transaction. - */ - public TransactionReceipt applyTransaction(Block block, Transaction tx) { - - logger.info("applyTransaction: [{}]", Hex.toHexString(tx.getHash())); - - TransactionReceipt receipt = new TransactionReceipt(); - - byte[] coinbase = block.getCoinbase(); - - // VALIDATE THE SENDER - byte[] senderAddress = tx.getSender(); -// AccountState senderAccount = repository.getAccountState(senderAddress); - logger.info("tx.sender: [{}]", Hex.toHexString(tx.getSender())); - - // VALIDATE THE NONCE - BigInteger nonce = track.getNonce(senderAddress); - BigInteger txNonce = new BigInteger(1, tx.getNonce()); - if (nonce.compareTo(txNonce) != 0) { - if (stateLogger.isWarnEnabled()) - stateLogger.warn("Invalid nonce account.nonce={} tx.nonce={}", - nonce, txNonce); - - receipt.setCumulativeGas(0); - return receipt; - } - - // UPDATE THE NONCE - track.increaseNonce(senderAddress); - logger.info("increased tx.nonce to: [{}]", track.getNonce(senderAddress)); - - // FIND OUT THE TRANSACTION TYPE - byte[] receiverAddress, code = null; - boolean isContractCreation = tx.isContractCreation(); - if (isContractCreation) { - receiverAddress = tx.getContractAddress(); - code = tx.getData(); // init code - } else { - receiverAddress = tx.getReceiveAddress(); - code = track.getCode(receiverAddress); - - // on invocation the contract is created event if doesn't exist. - track.addBalance(receiverAddress, BigInteger.ZERO); - if (code != EMPTY_BYTE_ARRAY) { - if (stateLogger.isDebugEnabled()) - stateLogger.debug("calling for existing contract: address={}", - Hex.toHexString(receiverAddress)); - } - } - - // THE SIMPLE VALUE/BALANCE CHANGE - boolean isValueTx = tx.getValue() != null; - BigInteger txValue = new BigInteger(1, tx.getValue()); - if (isValueTx && !isContractCreation) { - if (track.getBalance(senderAddress).compareTo(txValue) >= 0) { - - track.addBalance(receiverAddress, txValue); // balance will be read again below - track.addBalance(senderAddress, txValue.negate()); - -// if(!isContractCreation) // adding to new contract could be reverted -// track.addBalance(receiverAddress, txValue); todo: find out what is that ? - - if (stateLogger.isDebugEnabled()) - stateLogger.debug("Update value balance \n " - + "sender={}, receiver={}, value={}", - Hex.toHexString(senderAddress), - Hex.toHexString(receiverAddress), - new BigInteger(tx.getValue())); - } - } - - // GET TOTAL ETHER VALUE AVAILABLE FOR TX FEE - BigInteger gasPrice = new BigInteger(1, tx.getGasPrice()); - BigInteger gasDebit = new BigInteger(1, tx.getGasLimit()).multiply(gasPrice); - logger.info("Gas price limited to [{} wei]", gasDebit.toString()); - - // Debit the actual total gas value from the sender - // the purchased gas will be available for - // the contract in the execution state, - // it can be retrieved using GAS op - if (gasDebit.signum() == 1) { - if (track.getBalance(senderAddress).compareTo(gasDebit) == -1) { - logger.debug("No gas to start the execution: sender={}", - Hex.toHexString(senderAddress)); - - receipt.setCumulativeGas(0); - return receipt; - } - track.addBalance(senderAddress, gasDebit.negate()); - - // The coinbase get the gas cost - if (coinbase != null) - track.addBalance(coinbase, gasDebit); - - if (stateLogger.isDebugEnabled()) - stateLogger.debug( - "Before contract execution debit the sender address with gas total cost, " - + "\n sender={} \n gas_debit= {}", - Hex.toHexString(senderAddress), gasDebit); - } - - // CREATE AND/OR EXECUTE CONTRACT - long gasUsed = 0; - if (isContractCreation || code != EMPTY_BYTE_ARRAY) { - - // START TRACKING FOR REVERT CHANGES OPTION - Repository trackTx = track.startTracking(); - trackTx.addBalance(receiverAddress, BigInteger.ZERO); // the contract created for anycase but SUICIDE call - - trackTx.addBalance(receiverAddress, txValue); - track.addBalance(senderAddress, txValue.negate()); // will not be reverted - - logger.info("Start tracking VM run"); - try { - - // CREATE NEW CONTRACT ADDRESS AND ADD TX VALUE - if(isContractCreation) { - if(stateLogger.isDebugEnabled()) - stateLogger.debug("new contract created address={}", - Hex.toHexString(receiverAddress)); - } - - Block currBlock = (block == null) ? this.getBestBlock() : block; - - ProgramInvoke programInvoke = - programInvokeFactory.createProgramInvoke(tx, currBlock, trackTx); - - VM vm = new VM(); - Program program = new Program(code, programInvoke); - - if (CONFIG.playVM()) - vm.play(program); - - program.saveProgramTraceToFile(Hex.toHexString(tx.getHash())); - ProgramResult result = program.getResult(); - applyProgramResult(result, gasDebit, gasPrice, trackTx, - senderAddress, receiverAddress, coinbase, isContractCreation); - gasUsed = result.getGasUsed(); - - List logs = result.getLogInfoList(); - receipt.setLogInfoList(logs); - - } catch (RuntimeException e) { - trackTx.rollback(); - receipt.setCumulativeGas(tx.getGasLimit()); - return receipt; - } - trackTx.commit(); - } else { - // REFUND GASDEBIT EXCEPT FOR FEE (500 + 5*TX_NO_ZERO_DATA) - long dataCost = tx.getData() == null ? 0: tx.getData().length * GasCost.TX_NO_ZERO_DATA; - gasUsed = GasCost.TRANSACTION + dataCost; - - BigInteger refund = gasDebit.subtract(BigInteger.valueOf(gasUsed).multiply(gasPrice)); - if (refund.signum() > 0) { - track.addBalance(senderAddress, refund); - track.addBalance(coinbase, refund.negate()); - } - } - - receipt.setCumulativeGas(gasUsed); - return receipt; - } - - /** - * After any contract code finish the run the certain result should take - * place, according the given circumstances - * - * @param result - * @param gasDebit - * @param senderAddress - * @param contractAddress - */ - private void applyProgramResult(ProgramResult result, BigInteger gasDebit, - BigInteger gasPrice, Repository repository, byte[] senderAddress, - byte[] contractAddress, byte[] coinbase, boolean initResults) { - - if (result.getException() != null) { - stateLogger.debug("contract run halted by Exception: contract: [{}], exception: [{}]", - Hex.toHexString(contractAddress), - result.getException()); - throw result.getException(); - } - - BigInteger refund = gasDebit.subtract(BigInteger.valueOf( - result.getGasUsed()).multiply(gasPrice)); - - if (refund.signum() > 0) { - if (stateLogger.isDebugEnabled()) - stateLogger - .debug("After contract execution the sender address refunded with gas leftover, " - + "\n sender={} \n contract={} \n gas_refund= {}", - Hex.toHexString(senderAddress), - Hex.toHexString(contractAddress), refund); - // gas refund - repository.addBalance(senderAddress, refund); - repository.addBalance(coinbase, refund.negate()); - } - - if (initResults) { - // Save the code created by init - byte[] bodyCode = null; - if (result.getHReturn() != null && result.getHReturn().array().length > 0) { - bodyCode = result.getHReturn().array(); - } - - if (bodyCode != null) { - if (stateLogger.isDebugEnabled()) - stateLogger - .debug("saving code of the contract to the db:\n contract={} code={}", - Hex.toHexString(contractAddress), - Hex.toHexString(bodyCode)); - repository.saveCode(contractAddress, bodyCode); - } - } - - // delete the marked to die accounts - if (result.getDeleteAccounts() == null) return; - for (DataWord address : result.getDeleteAccounts()){ - repository.delete(address.getNoLeadZeroesData()); - } - } public boolean hasParentOnTheChain(Block block){ return getParent(block.getHeader()) != null; @@ -731,11 +503,23 @@ public class BlockchainImpl implements Blockchain { } } - - - - } + public void setRepository(Repository repository) { + this.repository = repository; + } + + public void setProgramInvokeFactory(ProgramInvokeFactory factory){ + this.programInvokeFactory = factory; + } + + public void startTracking(){ + track = repository.startTracking(); + } + + public void commitTracking(){ + track.commit(); + } + } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java index f96d95f3..de79e789 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java @@ -92,6 +92,8 @@ public class Transaction { if(receiveAddress == null) { this.receiveAddress = ByteUtil.EMPTY_BYTE_ARRAY; } + + getEncoded(); parsed = true; } @@ -143,6 +145,11 @@ public class Transaction { return nonce == null ? ZERO_BYTE_ARRAY : nonce ; } + public boolean isValueTx() { + if (!parsed) rlpParse(); + return value != null ; + } + public byte[] getValue() { if (!parsed) rlpParse(); return value == null ? ZERO_BYTE_ARRAY : value; @@ -155,7 +162,7 @@ public class Transaction { public byte[] getGasPrice() { if (!parsed) rlpParse(); - return gasPrice; + return gasPrice== null ? ZERO_BYTE_ARRAY : gasPrice ; } public byte[] getGasLimit() { @@ -222,9 +229,9 @@ public class Transaction { ", receiveAddress=" + ByteUtil.toHexString(receiveAddress) + ", value=" + ByteUtil.toHexString(value) + ", data=" + ByteUtil.toHexString(data) + - ", signatureV=" + signature.v + - ", signatureR=" + ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.r)) + - ", signatureS=" + ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.s)) + + ", signatureV=" + (signature == null ? "" : signature.v) + + ", signatureR=" + (signature == null ? "" : ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.r))) + + ", signatureS=" + (signature == null ? "" : ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.s))) + "]"; } @@ -286,6 +293,9 @@ public class Transaction { this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit, receiveAddress, value, data, v, r, s); + + this.hash = this.getHash(); + return rlpEncoded; } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java new file mode 100644 index 00000000..2276a447 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java @@ -0,0 +1,292 @@ +package org.ethereum.core; + +import org.ethereum.facade.Repository; +import org.ethereum.vm.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.List; + +import static org.ethereum.config.SystemProperties.CONFIG; +import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; + +/** + * www.ethergit.com + * + * @author: Roman Mandeleil + * Created on: 19/12/2014 12:56 + */ + +public class TransactionExecutor { + + private static final Logger logger = LoggerFactory.getLogger("execute"); + private static final Logger stateLogger = LoggerFactory.getLogger("state"); + + private Transaction tx; + private Repository track; + private ProgramInvokeFactory programInvokeFactory; + private byte[] coinbase; + + private TransactionReceipt receipt; + private ProgramResult result; + private Block bestBlock; + + + public TransactionExecutor(Transaction tx, byte[] coinbase, Repository track, + ProgramInvokeFactory programInvokeFactory, Block bestBlock) { + + this.tx = tx; + this.coinbase = coinbase; + this.track = track; + this.programInvokeFactory = programInvokeFactory; + this.bestBlock = bestBlock; + } + + public void execute(){ + + logger.info("applyTransaction: [{}]", Hex.toHexString(tx.getHash())); + + TransactionReceipt receipt = new TransactionReceipt(); + + // VALIDATE THE SENDER + byte[] senderAddress = tx.getSender(); +// AccountState senderAccount = repository.getAccountState(senderAddress); + logger.info("tx.sender: [{}]", Hex.toHexString(tx.getSender())); + + // VALIDATE THE NONCE + BigInteger nonce = track.getNonce(senderAddress); + BigInteger txNonce = new BigInteger(1, tx.getNonce()); + if (nonce.compareTo(txNonce) != 0) { + if (stateLogger.isWarnEnabled()) + stateLogger.warn("Invalid nonce account.nonce={} tx.nonce={}", + nonce, txNonce); + + receipt.setCumulativeGas(0); + this.receipt = receipt; + return; + } + + + // FIND OUT THE TRANSACTION TYPE + byte[] receiverAddress, code = null; + boolean isContractCreation = tx.isContractCreation(); + if (isContractCreation) { + receiverAddress = tx.getContractAddress(); + code = tx.getData(); // init code + } else { + receiverAddress = tx.getReceiveAddress(); + code = track.getCode(receiverAddress); + + // on invocation the contract is created event if doesn't exist. + track.addBalance(receiverAddress, BigInteger.ZERO); + if (code != EMPTY_BYTE_ARRAY) { + if (stateLogger.isDebugEnabled()) + stateLogger.debug("calling for existing contract: address={}", + Hex.toHexString(receiverAddress)); + } + } + + + // GET TOTAL ETHER VALUE AVAILABLE FOR TX FEE + BigInteger gasPrice = new BigInteger(1, tx.getGasPrice()); + BigInteger gasDebit = new BigInteger(1, tx.getGasLimit()).multiply(gasPrice); + logger.info("Gas price limited to [{} wei]", gasDebit.toString()); + + // Debit the actual total gas value from the sender + // the purchased gas will be available for + // the contract in the execution state, + // it can be retrieved using GAS op + if (track.getBalance(senderAddress).compareTo(gasDebit) == -1) { + logger.debug("No gas to start the execution: sender={}", + Hex.toHexString(senderAddress)); + + receipt.setCumulativeGas(0); + this.receipt = receipt; + return; + } + + + // THE SIMPLE VALUE/BALANCE CHANGE + BigInteger txValue = new BigInteger(1, tx.getValue()); + if (tx.isValueTx()) { + if (track.getBalance(senderAddress).compareTo(txValue) >= 0) { + + track.addBalance(receiverAddress, txValue); // balance will be read again below + track.addBalance(senderAddress, txValue.negate()); + + if (stateLogger.isDebugEnabled()) + stateLogger.debug("Update value balance \n " + + "sender={}, receiver={}, value={}", + Hex.toHexString(senderAddress), + Hex.toHexString(receiverAddress), + new BigInteger(tx.getValue())); + } + } + + + // UPDATE THE NONCE + track.increaseNonce(senderAddress); + logger.info("increased tx.nonce to: [{}]", track.getNonce(senderAddress)); + + // CHARGE FOR GAS + track.addBalance(senderAddress, gasDebit.negate()); + + // The coinbase get the gas cost + if (coinbase != null) + track.addBalance(coinbase, gasDebit); + + if (stateLogger.isDebugEnabled()) + stateLogger.debug( + "Before contract execution debit the sender address with gas total cost, " + + "\n sender={} \n gas_debit= {}", + Hex.toHexString(senderAddress), gasDebit); + + // CREATE AND/OR EXECUTE CONTRACT + long gasUsed = 0; + if (isContractCreation || code != EMPTY_BYTE_ARRAY) { + + // START TRACKING FOR REVERT CHANGES OPTION + Repository trackTx = track.startTracking(); + trackTx.addBalance(receiverAddress, BigInteger.ZERO); // the contract created for anycase but SUICIDE call + + logger.info("Start tracking VM run"); + try { + + // CREATE NEW CONTRACT ADDRESS AND ADD TX VALUE + if(isContractCreation) { + if(stateLogger.isDebugEnabled()) + stateLogger.debug("new contract created address={}", + Hex.toHexString(receiverAddress)); + } + + ProgramInvoke programInvoke = + programInvokeFactory.createProgramInvoke(tx, bestBlock, trackTx); + + VM vm = new VM(); + Program program = new Program(code, programInvoke); + + if (CONFIG.playVM()) + vm.play(program); + + program.saveProgramTraceToFile(Hex.toHexString(tx.getHash())); + result = program.getResult(); + applyProgramResult(result, gasDebit, gasPrice, trackTx, + senderAddress, receiverAddress, coinbase, isContractCreation); + gasUsed = result.getGasUsed(); + + List logs = result.getLogInfoList(); + receipt.setLogInfoList(logs); + + } catch (RuntimeException e) { + trackTx.rollback(); + receipt.setCumulativeGas(tx.getGasLimit()); + this.receipt = receipt; + return; + } + trackTx.commit(); + } else { + // REFUND GASDEBIT EXCEPT FOR FEE (500 + 5*TX_NO_ZERO_DATA) + long dataCost = tx.getData() == null ? 0: tx.getData().length * GasCost.TX_NO_ZERO_DATA; + gasUsed = GasCost.TRANSACTION + dataCost; + + BigInteger refund = gasDebit.subtract(BigInteger.valueOf(gasUsed).multiply(gasPrice)); + if (refund.signum() > 0) { + track.addBalance(senderAddress, refund); + track.addBalance(coinbase, refund.negate()); + } + } + + receipt.setCumulativeGas(gasUsed); + this.receipt = receipt; + return; + + } + + /** + * After any contract code finish the run the certain result should take + * place, according the given circumstances + * + * @param result + * @param gasDebit + * @param senderAddress + * @param contractAddress + */ + private void applyProgramResult(ProgramResult result, BigInteger gasDebit, + BigInteger gasPrice, Repository repository, byte[] senderAddress, + byte[] contractAddress, byte[] coinbase, boolean initResults) { + + if (result.getException() != null) { + stateLogger.debug("contract run halted by Exception: contract: [{}], exception: [{}]", + Hex.toHexString(contractAddress), + result.getException()); + throw result.getException(); + } + + + BigInteger refund = gasDebit.subtract(BigInteger.valueOf( + result.getGasUsed()).multiply(gasPrice)); + + if (refund.signum() > 0) { + if (stateLogger.isDebugEnabled()) + stateLogger + .debug("After contract execution the sender address refunded with gas leftover, " + + "\n sender={} \n contract={} \n gas_refund= {}", + Hex.toHexString(senderAddress), + Hex.toHexString(contractAddress), refund); + // gas refund + repository.addBalance(senderAddress, refund); + repository.addBalance(coinbase, refund.negate()); + } + + if (result.getFutureRefund() > 0){ + + long futureRefund = Math.min(result.getFutureRefund(), result.getGasUsed() / 2); + BigInteger futureRefundBI = BigInteger.valueOf(futureRefund); + BigInteger futureRefundVal = futureRefundBI.multiply(gasPrice); + + if (stateLogger.isDebugEnabled()) + stateLogger + .debug("After contract execution the sender address refunded with storage save refunds, " + + "\n sender={} \n contract={} \n gas_refund= {}", + Hex.toHexString(senderAddress), + Hex.toHexString(contractAddress), futureRefundVal); + repository.addBalance(senderAddress, futureRefundVal); + repository.addBalance(coinbase, futureRefundVal.negate()); + } + + + if (initResults) { + // Save the code created by init + byte[] bodyCode = null; + if (result.getHReturn() != null && result.getHReturn().array().length > 0) { + bodyCode = result.getHReturn().array(); + } + + if (bodyCode != null) { + if (stateLogger.isDebugEnabled()) + stateLogger + .debug("saving code of the contract to the db:\n contract={} code={}", + Hex.toHexString(contractAddress), + Hex.toHexString(bodyCode)); + repository.saveCode(contractAddress, bodyCode); + } + } + + // delete the marked to die accounts + if (result.getDeleteAccounts() == null) return; + for (DataWord address : result.getDeleteAccounts()){ + repository.delete(address.getNoLeadZeroesData()); + } + } + + + public TransactionReceipt getReceipt() { + return receipt; + } + + public ProgramResult getResult() { + return result; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/db/ContractDetails.java b/ethereumj-core/src/main/java/org/ethereum/db/ContractDetails.java index 75476a1f..d35f482c 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/ContractDetails.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/ContractDetails.java @@ -10,6 +10,7 @@ import org.ethereum.trie.Trie; import org.ethereum.trie.TrieImpl; import org.ethereum.util.*; import org.ethereum.vm.DataWord; +import org.spongycastle.util.encoders.Hex; /** * @author: Roman Mandeleil @@ -188,6 +189,23 @@ public class ContractDetails { this.storageValues = storageValues; } + public void setStorage(Map storage) { + + List keys = new ArrayList<>(); + keys.addAll(storageKeys); + + List values = new ArrayList<>(); + for (DataWord key : keys){ + + DataWord value = storage.get(key); + values.add(value); + } + + this.storageKeys = keys; + this.storageValues = values; + } + + public ContractDetails clone(){ @@ -199,5 +217,13 @@ public class ContractDetails { return contractDetails; } + public String toString(){ + + String ret = " Code: " + Hex.toHexString(code) + "\n"; + ret += " Storage: " + getStorage().toString(); + + return ret; + } + } diff --git a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryDummy.java b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryDummy.java new file mode 100644 index 00000000..526c9121 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryDummy.java @@ -0,0 +1,308 @@ +package org.ethereum.db; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.codehaus.plexus.util.FileUtils; +import org.ethereum.core.AccountState; +import org.ethereum.core.Block; +import org.ethereum.facade.Repository; +import org.ethereum.json.EtherObjectMapper; +import org.ethereum.json.JSONHelper; +import org.ethereum.trie.Trie; +import org.ethereum.trie.TrieImpl; +import org.ethereum.vm.DataWord; +import org.iq80.leveldb.DBIterator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; +import org.springframework.stereotype.Component; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.math.BigInteger; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static org.ethereum.config.SystemProperties.CONFIG; +import static org.ethereum.crypto.SHA3Helper.sha3; +import static org.ethereum.util.ByteUtil.wrap; + +/** + * www.etherj.com + * + * @author: Roman Mandeleil + * Created on: 17/11/2014 21:15 + */ +public class RepositoryDummy implements Repository { + + private static final Logger logger = LoggerFactory.getLogger("repository"); + private Map worldState = new HashMap<>(); + private Map detailsDB = new HashMap<>() ; + + + @Override + public void reset() { + + worldState.clear(); + detailsDB.clear(); + } + + @Override + public void close() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isClosed() { + throw new UnsupportedOperationException(); + } + + + @Override + public void updateBatch(HashMap stateCache, HashMap detailsCache) { + + for (ByteArrayWrapper hash : stateCache.keySet()) { + + AccountState accountState = stateCache.get(hash); + ContractDetails contractDetails = detailsCache.get(hash); + + if (accountState.isDeleted()){ + worldState.remove( hash ) ; + detailsDB.remove( hash ); + + logger.debug("delete: [{}]", + Hex.toHexString(hash.getData())); + + } else{ + + if (accountState.isDirty() || contractDetails.isDirty()){ + detailsDB.put( hash, contractDetails); + accountState.setStateRoot(contractDetails.getStorageHash()); + accountState.setCodeHash(sha3(contractDetails.getCode())); + worldState.put( hash, accountState); + if (logger.isDebugEnabled()){ + logger.debug("update: [{}],nonce: [{}] balance: [{}] \n [{}]", + Hex.toHexString(hash.getData()), + accountState.getNonce(), + accountState.getBalance(), + contractDetails.getStorage()); + } + + } + + } + } + + stateCache.clear(); + detailsCache.clear(); + + } + + + @Override + public void flush() { + throw new UnsupportedOperationException(); + } + + @Override + public void rollback() { + throw new UnsupportedOperationException(); + } + + @Override + public void commit() { + throw new UnsupportedOperationException(); + } + + + @Override + public void syncToRoot(byte[] root) { + throw new UnsupportedOperationException(); + } + + @Override + public Repository startTracking() { + return new RepositoryTrack(this); + } + + @Override + public void dumpState(Block block, long gasUsed, int txNumber, byte[] txHash) { + + } + + @Override + public DBIterator getAccountsIterator() { + return null; + } + + public Set getFullAddressSet(){ + return worldState.keySet(); + } + + + @Override + public BigInteger addBalance(byte[] addr, BigInteger value) { + AccountState account = getAccountState(addr); + + if (account == null) + account = createAccount(addr); + + BigInteger result = account.addToBalance(value); + worldState.put(wrap(addr), account); + + return result; + } + + @Override + public BigInteger getBalance(byte[] addr) { + AccountState account = getAccountState(addr); + + if (account == null) + return BigInteger.ZERO; + + return account.getBalance(); + } + + @Override + public DataWord getStorageValue(byte[] addr, DataWord key) { + ContractDetails details = getContractDetails(addr); + + if (details == null) + return null; + + return details.get(key); + } + + @Override + public void addStorageRow(byte[] addr, DataWord key, DataWord value) { + ContractDetails details = getContractDetails(addr); + + if (details == null){ + createAccount(addr); + details = getContractDetails(addr); + } + + details.put(key, value); + detailsDB.put(wrap(addr), details); + } + + @Override + public byte[] getCode(byte[] addr) { + ContractDetails details = getContractDetails(addr); + + if (details == null) + return null; + + return details.getCode(); + } + + + @Override + public void saveCode(byte[] addr, byte[] code) { + ContractDetails details = getContractDetails(addr); + + if (details == null){ + createAccount(addr); + details = getContractDetails(addr); + } + + details.setCode(code); + detailsDB.put(wrap( addr ), details); + } + + @Override + public BigInteger getNonce(byte[] addr) { + AccountState account = getAccountState(addr); + + if (account == null) + account = createAccount(addr); + + return account.getNonce(); + } + + @Override + public BigInteger increaseNonce(byte[] addr) { + AccountState account = getAccountState(addr); + + if (account == null) + account = createAccount(addr); + + account.incrementNonce(); + worldState.put(wrap(addr), account); + + return account.getNonce(); + } + + public BigInteger setNonce(byte[] addr, BigInteger nonce) { + + AccountState account = getAccountState(addr); + + if (account == null) + account = createAccount(addr); + + account.setNonce(nonce); + worldState.put(wrap(addr), account); + + return account.getNonce(); + } + + + @Override + public void delete(byte[] addr) { + worldState.remove(wrap(addr)); + detailsDB.remove(wrap(addr)); + } + + @Override + public ContractDetails getContractDetails(byte[] addr) { + + return detailsDB.get(wrap(addr)); + } + + + @Override + public AccountState getAccountState(byte[] addr) { + return worldState.get(wrap(addr)); + } + + @Override + public AccountState createAccount(byte[] addr) { + AccountState accountState = new AccountState(); + worldState.put(wrap(addr), accountState); + + ContractDetails contractDetails = new ContractDetails(); + detailsDB.put(wrap(addr), contractDetails); + + return accountState; + } + + @Override + public byte[] getRoot() { + throw new UnsupportedOperationException(); + } + + @Override + public void loadAccount(byte[] addr, HashMap cacheAccounts, HashMap cacheDetails) { + + AccountState account = getAccountState(addr); + ContractDetails details = getContractDetails(addr); + + if (account == null) + account = new AccountState(); + else + account = account.clone(); + + if (details == null) + details = new ContractDetails(); + else + details = details.clone(); + + cacheAccounts.put(wrap(addr), account); + cacheDetails.put(wrap(addr), details); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryImpl.java b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryImpl.java index 083d4df5..cfb6ba50 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryImpl.java @@ -12,7 +12,6 @@ import org.ethereum.trie.Trie; import org.ethereum.trie.TrieImpl; import org.ethereum.vm.DataWord; import org.iq80.leveldb.DBIterator; -import org.iq80.leveldb.WriteBatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; @@ -27,7 +26,7 @@ import java.util.HashMap; import java.util.List; import static org.ethereum.config.SystemProperties.CONFIG; -import static org.ethereum.crypto.SHA3Helper.*; +import static org.ethereum.crypto.SHA3Helper.sha3; import static org.ethereum.util.ByteUtil.wrap; /** @@ -103,7 +102,7 @@ public class RepositoryImpl implements Repository { } else{ - if (contractDetails.isDirty()){ + if (accountState.isDirty() || contractDetails.isDirty()){ detailsDB.put(hash.getData(), contractDetails.getEncoded()); accountState.setStateRoot(contractDetails.getStorageHash()); accountState.setCodeHash(sha3(contractDetails.getCode())); @@ -118,22 +117,12 @@ public class RepositoryImpl implements Repository { } - if (!contractDetails.isDirty() && accountState.isDirty()){ - worldState.update(hash.getData(), accountState.getEncoded()); - - if (logger.isDebugEnabled()){ - logger.debug("update: [{}],nonce: [{}] balance: [{}]", - Hex.toHexString(hash.getData()), - accountState.getNonce(), - accountState.getBalance()); - } - - } } - detailsCache.remove(hash.getData()); - stateCache.remove(hash.getData()); } + + stateCache.clear(); + detailsCache.clear(); } @Override @@ -362,6 +351,22 @@ public class RepositoryImpl implements Repository { return account.getNonce(); } + public BigInteger setNonce(byte[] addr, BigInteger nonce) { + + AccountState account = getAccountState(addr); + + if (account == null) + account = createAccount(addr); + + account.setNonce(nonce); + worldState.update(addr, account.getEncoded()); + + return account.getNonce(); + } + + + + @Override public void delete(byte[] addr) { worldState.delete(addr); diff --git a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryTrack.java b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryTrack.java index a392b1e0..88420053 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/RepositoryTrack.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/RepositoryTrack.java @@ -11,6 +11,7 @@ import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; import java.util.HashMap; +import java.util.Set; import static org.ethereum.crypto.SHA3Helper.sha3; import static org.ethereum.util.ByteUtil.wrap; @@ -26,12 +27,15 @@ public class RepositoryTrack implements Repository { private static final Logger logger = LoggerFactory.getLogger("repository"); - HashMap cacheAccounts = new HashMap<>(); HashMap cacheDetails = new HashMap<>(); Repository repository; + public RepositoryTrack(){ + this.repository = new RepositoryDummy(); + } + public RepositoryTrack(Repository repository) { this.repository = repository; } @@ -116,6 +120,23 @@ public class RepositoryTrack implements Repository { return accountState.getNonce(); } + public BigInteger setNonce(byte[] addr, BigInteger bigInteger) { + AccountState accountState = getAccountState(addr); + + if (accountState == null) + accountState = createAccount(addr); + + BigInteger saveNonce = accountState.getNonce(); + accountState.setNonce(bigInteger); + + logger.trace("increase nonce addr: [{}], from: [{}], to: [{}]", Hex.toHexString(addr), + saveNonce, accountState.getNonce()); + + return accountState.getNonce(); + + } + + @Override public BigInteger getNonce(byte[] addr) { AccountState accountState = getAccountState(addr); @@ -177,6 +198,10 @@ public class RepositoryTrack implements Repository { throw new UnsupportedOperationException(); } + public Set getFullAddressSet(){ + return cacheAccounts.keySet(); + } + @Override public void dumpState(Block block, long gasUsed, int txNumber, byte[] txHash) { diff --git a/ethereumj-core/src/main/java/org/ethereum/facade/Repository.java b/ethereumj-core/src/main/java/org/ethereum/facade/Repository.java index 00c17805..2fceec6c 100644 --- a/ethereumj-core/src/main/java/org/ethereum/facade/Repository.java +++ b/ethereumj-core/src/main/java/org/ethereum/facade/Repository.java @@ -192,4 +192,5 @@ public interface Repository { void loadAccount(byte[] addr, HashMap cacheAccounts, HashMap cacheDetails); + } diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/AccountState.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/AccountState.java index d43f8bb1..e3bd5b87 100644 --- a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/AccountState.java +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/AccountState.java @@ -1,13 +1,14 @@ package org.ethereum.jsontestsuite; import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.db.ContractDetails; import org.ethereum.util.ByteUtil; +import org.ethereum.vm.DataWord; import org.json.simple.JSONObject; import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import java.util.regex.Pattern; /** @@ -24,7 +25,7 @@ public class AccountState { byte[] code; byte[] nonce; - Map storage = new HashMap<>(); + Map storage = new HashMap<>(); public AccountState(byte[] address, JSONObject accountState) { @@ -52,28 +53,9 @@ public class AccountState { String keyS = keys[i].toString(); String valS = store.get(keys[i]).toString(); - ByteArrayWrapper key; - boolean hexVal = Pattern.matches("0[xX][0-9a-fA-F]+", keyS); - if (hexVal) { - key = new ByteArrayWrapper(Hex.decode(keyS.substring(2))); - } else { - byte[] data; - if (keyS != null && keyS.length() > 2) - data = Hex.decode(keyS); - else - data = ByteUtil.EMPTY_BYTE_ARRAY; - key = new ByteArrayWrapper(data); - } - - ByteArrayWrapper value; - hexVal = Pattern.matches("0[xX][0-9a-fA-F]+", valS); - if (hexVal) { - value = new ByteArrayWrapper(Hex.decode(valS.substring(2))); - } else { - byte[] data = ByteUtil.bigIntegerToBytes(new BigInteger(valS)); - value = new ByteArrayWrapper(data); - } - storage.put(key, value); + byte[] key = Utils.parseData(keyS); + byte[] value = Utils.parseData(valS); + storage.put(new DataWord(key), new DataWord(value)); } } @@ -103,10 +85,81 @@ public class AccountState { } - public Map getStorage() { + public Map getStorage() { return storage; } + public List compareToReal(org.ethereum.core.AccountState state, ContractDetails details){ + + List results = new ArrayList<>(); + + BigInteger expectedBalance = new BigInteger(1, this.getBalance()); + if (!state.getBalance().equals(expectedBalance)){ + String formatedString = String.format("Account: %s: has unexpected balance, expected balance: %s found balance: %s", + Hex.toHexString(this.address), expectedBalance.toString(), state.getBalance().toString()); + results.add(formatedString); + } + + BigInteger expectedNonce = new BigInteger(1, this.getNonce()); + if (!state.getNonce().equals(expectedNonce)) { + state.getNonce(); + this.getNonce(); + String formatedString = String.format("Account: %s: has unexpected nonce, expected nonce: %s found nonce: %s", + Hex.toHexString(this.address), expectedNonce.toString(), state.getNonce().toString() ); + results.add(formatedString); + } + + if (!Arrays.equals(details.getCode(),this.getCode())) { + String formatedString = String.format("Account: %s: has unexpected nonce, expected nonce: %s found nonce: %s", + Hex.toHexString(this.address), Hex.toHexString( this.getCode() ), Hex.toHexString(details.getCode())); + results.add(formatedString); + } + + + // compare storage + Set keys = details.getStorage().keySet(); + Set expectedKeys = this.getStorage().keySet(); + Set checked = new HashSet<>(); + + for (DataWord key : keys){ + + DataWord value = details.getStorage().get(key); + DataWord expectedValue = this.getStorage().get(key); + if (expectedValue == null) { + + String formatedString = String.format("Account: %s: has unexpected storage data: %s = %s", + Hex.toHexString(this.address), + key.toString(), + value.toString()); + + results.add(formatedString); + + continue; + } + + if (!expectedValue.equals(value)){ + + String formatedString = String.format("Account: %s: has unexpected value, for key: %s , expectedValue: %s real value: %s", + Hex.toHexString(this.address), key.toString(), + expectedValue.toString(), value.toString()); + results.add(formatedString); + continue; + } + + checked.add(key); + } + + for (DataWord key : expectedKeys){ + if (!checked.contains(key)){ + String formatedString = String.format("Account: %s: doesn't exist expected storage key: %s", + Hex.toHexString(this.address), key.toString()); + results.add(formatedString); + } + } + + return results; + } + @Override public String toString() { return "AccountState{" + diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/CallCreate.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/CallCreate.java index f8cc3d79..8c0c8c10 100644 --- a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/CallCreate.java +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/CallCreate.java @@ -7,7 +7,7 @@ import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; /** - * www.ethereumJ.com + * www.etherj.com * * @author: Roman Mandeleil * Created on: 28/06/2014 10:25 diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Logs.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Logs.java index 436d6318..02acc8db 100644 --- a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Logs.java +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Logs.java @@ -41,4 +41,68 @@ public class Logs { public Iterator getIterator(){ return logs.iterator(); } + + + public List compareToReal(List logs){ + + List results = new ArrayList<>(); + + int i = 0; + for (LogInfo postLog : this.logs){ + + LogInfo realLog = logs.get(i); + + String postAddress = Hex.toHexString(postLog.getAddress()); + String realAddress = Hex.toHexString(realLog.getAddress()); + + if (!postAddress.equals(realAddress)){ + + String formatedString = String.format("Log: %s: has unexpected address, expected address: %s found address: %s", + i, postAddress, realAddress); + results.add(formatedString); + } + + String postData = Hex.toHexString(postLog.getData()); + String realData = Hex.toHexString(realLog.getData()); + + if (!postData.equals(realData)){ + + String formatedString = String.format("Log: %s: has unexpected data, expected data: %s found data: %s", + i, postData, realData); + results.add(formatedString); + } + + String postBloom = Hex.toHexString(postLog.getBloom().getData()); + String realBloom = Hex.toHexString(realLog.getBloom().getData()); + + if (!postData.equals(realData)){ + + String formatedString = String.format("Log: %s: has unexpected bloom, expected bloom: %s found bloom: %s", + i, postBloom, realBloom); + results.add(formatedString); + } + + List postTopics = postLog.getTopics(); + List realTopics = realLog.getTopics(); + + int j = 0; + for (DataWord postTopic : postTopics){ + + DataWord realTopic = realTopics.get(j); + + if (!postTopic.equals(realTopic)){ + + String formatedString = String.format("Log: %s: has unexpected topic: %s, expected topic: %s found topic: %s", + i, j, postTopic, realTopic); + results.add(formatedString); + } + ++j; + } + + ++i; + } + + return results; + } + } diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/StateTestCase.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/StateTestCase.java new file mode 100644 index 00000000..1be713c0 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/StateTestCase.java @@ -0,0 +1,124 @@ +package org.ethereum.jsontestsuite; + +import org.ethereum.db.ByteArrayWrapper; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.ParseException; +import org.spongycastle.util.encoders.Hex; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +/** + * www.etherj.com + * + * @author: Roman Mandeleil + * Created on: 15/12/2014 12:24 + */ + +public class StateTestCase { + + private String name = ""; + + private Env env; + private Logs logs; + private byte[] out; + + // "pre": { ... }, + private Map pre = new HashMap<>(); + + // "post": { ... }, + private Map post = new HashMap<>(); + + private Transaction transaction; + + public StateTestCase(String name, JSONObject testCaseJSONObj) throws ParseException { + + this(testCaseJSONObj); + this.name = name; + } + + public StateTestCase(JSONObject testCaseJSONObj) throws ParseException{ + + try { + + JSONObject envJSON = (JSONObject)testCaseJSONObj.get("env"); + JSONArray logsJSON = (JSONArray)testCaseJSONObj.get("logs"); + String outStr = testCaseJSONObj.get("out").toString(); + JSONObject txJSON = (JSONObject)testCaseJSONObj.get("transaction"); + + JSONObject preJSON = (JSONObject)testCaseJSONObj.get("pre"); + JSONObject postJSON = (JSONObject)testCaseJSONObj.get("post"); + + this.env = new Env(envJSON); + this.logs = new Logs(logsJSON); + this.out = Utils.parseData(outStr); + this.transaction = new Transaction(txJSON); + + for (Object key : preJSON.keySet()){ + + byte[] keyBytes = Hex.decode(key.toString()); + AccountState accountState = + new AccountState(keyBytes, (JSONObject) preJSON.get(key)); + + pre.put(new ByteArrayWrapper(keyBytes), accountState); + } + + for (Object key : postJSON.keySet()){ + + byte[] keyBytes = Hex.decode(key.toString()); + AccountState accountState = + new AccountState(keyBytes, (JSONObject) postJSON.get(key)); + + post.put(new ByteArrayWrapper(keyBytes), accountState); + } + + + } catch (Throwable e) { + throw new ParseException(0, e); + } + } + + + public String getName() { + return name; + } + + public Env getEnv() { + return env; + } + + public Logs getLogs() { + return logs; + } + + public byte[] getOut() { + return out; + } + + public Map getPre() { + return pre; + } + + public Map getPost() { + return post; + } + + public Transaction getTransaction() { + return transaction; + } + + @Override + public String toString() { + return "StateTestCase{" + + "name='" + name + '\'' + + ", env=" + env + + ", logs=" + logs + + ", out=" + Arrays.toString(out) + + ", pre=" + pre + + ", post=" + post + + ", transaction=" + transaction + + '}'; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/StateTestSuite.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/StateTestSuite.java new file mode 100644 index 00000000..186e6ec9 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/StateTestSuite.java @@ -0,0 +1,49 @@ +package org.ethereum.jsontestsuite; + +import org.json.simple.JSONObject; +import org.json.simple.parser.ParseException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 10/07/2014 09:46 + */ + +public class StateTestSuite { + + private Logger logger = LoggerFactory.getLogger("TCK-Test"); + Map testCases = new HashMap<>(); + + public StateTestSuite(JSONObject testCaseJSONObj) throws ParseException { + + for (Object key: testCaseJSONObj.keySet()){ + + Object testCaseJSON = testCaseJSONObj.get(key); + + StateTestCase testCase = new StateTestCase(key.toString(), (JSONObject) testCaseJSON); + + testCases.put(key.toString(), testCase); + } + } + + public StateTestCase getTestCase(String name){ + + StateTestCase testCase = testCases.get(name); + if (testCase == null) throw new NullPointerException("Test cases doesn't exist: " + name); + + return testCase; + } + + public Collection getAllTests(){ + return testCases.values(); + } + + + + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestProgramInvokeFactory.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestProgramInvokeFactory.java new file mode 100644 index 00000000..90b2e396 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestProgramInvokeFactory.java @@ -0,0 +1,93 @@ +package org.ethereum.jsontestsuite; + +import org.ethereum.core.*; +import org.ethereum.core.Transaction; +import org.ethereum.facade.Repository; +import org.ethereum.util.ByteUtil; +import org.ethereum.vm.*; + +import java.math.BigInteger; + +/** + * www.ethergit.com + * + * @author: Roman Mandeleil + * Created on: 19/12/2014 12:22 + */ + +public class TestProgramInvokeFactory implements ProgramInvokeFactory{ + + Env env; + + TestProgramInvokeFactory(Env env){ + this.env = env; + } + + + @Override + public ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository repository) { + return generalInvoke(tx, repository); + } + + @Override + public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, DataWord inValue, DataWord inGas, + BigInteger balanceInt, byte[] dataIn, Repository repository) { + return null; + } + + + private ProgramInvoke generalInvoke(Transaction tx, Repository repository){ + + /*** ADDRESS op ***/ + // YP: Get address of currently executing account. + byte[] address = tx.isContractCreation() ? tx.getContractAddress(): tx.getReceiveAddress(); + + /*** ORIGIN op ***/ + // YP: This is the sender of original transaction; it is never a contract. + byte[] origin = tx.getSender(); + + /*** CALLER op ***/ + // YP: This is the address of the account that is directly responsible for this execution. + byte[] caller = tx.getSender(); + + /*** BALANCE op ***/ + byte[] balance = repository.getBalance(address).toByteArray(); + + /*** GASPRICE op ***/ + byte[] gasPrice = tx.getGasPrice(); + + /*** GAS op ***/ + byte[] gas = tx.getGasLimit(); + + /*** CALLVALUE op ***/ + byte[] callValue = tx.getValue() == null ? new byte[]{0} : tx.getValue(); + + /*** CALLDATALOAD op ***/ + /*** CALLDATACOPY op ***/ + /*** CALLDATASIZE op ***/ + byte[] data = tx.getData() == null ? ByteUtil.EMPTY_BYTE_ARRAY : tx.getData(); + + /*** PREVHASH op ***/ + byte[] lastHash = env.getPreviousHash(); + + /*** COINBASE op ***/ + byte[] coinbase = env.getCurrentCoinbase(); + + /*** TIMESTAMP op ***/ + long timestamp = ByteUtil.byteArrayToLong(env.getCurrentTimestamp()); + + /*** NUMBER op ***/ + long number = ByteUtil.byteArrayToLong(env.getCurrentNumber()); + + /*** DIFFICULTY op ***/ + byte[] difficulty = env.getCurrentDifficlty(); + + /*** GASLIMIT op ***/ + long gaslimit = ByteUtil.byteArrayToLong( env.getCurrentGasLimit() ); + + return new ProgramInvokeImpl(address, origin, caller, balance, + gasPrice, gas, callValue, data, lastHash, coinbase, + timestamp, number, difficulty, gaslimit, repository); + } + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestRunner.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestRunner.java index c2e92127..ecd65a5c 100644 --- a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestRunner.java +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestRunner.java @@ -1,8 +1,11 @@ package org.ethereum.jsontestsuite; +import org.ethereum.core.BlockchainImpl; +import org.ethereum.core.TransactionExecutor; import org.ethereum.db.ByteArrayWrapper; import org.ethereum.db.ContractDetails; -import org.ethereum.db.RepositoryImpl; +import org.ethereum.db.RepositoryDummy; +import org.ethereum.db.RepositoryTrack; import org.ethereum.facade.Repository; import org.ethereum.util.ByteUtil; import org.ethereum.vm.*; @@ -14,6 +17,9 @@ import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; import java.util.*; +import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; +import static org.ethereum.util.ByteUtil.wrap; + /** * www.ethereumJ.com * @@ -23,7 +29,7 @@ import java.util.*; public class TestRunner { - private Logger logger = LoggerFactory.getLogger("JSONTest"); + private Logger logger = LoggerFactory.getLogger("TCK-Test"); private ProgramTrace trace = null; public List runTestSuite(TestSuite testSuite) { @@ -43,36 +49,102 @@ public class TestRunner { return resultCollector; } + public List runTestCase(StateTestCase testCase) { + + List results = null; + logger.info("\n***"); + logger.info(" Running test case: [" + testCase.getName() + "]") ; + logger.info("***\n"); + results = new ArrayList<>(); + + logger.info("--------- PRE ---------"); + RepositoryDummy repository = loadRepository(testCase.getPre()); + + logger.info("loaded repository"); + + org.ethereum.core.Transaction tx = createTransaction(testCase.getTransaction()); + logger.info("transaction: {}", tx.toString()); + + byte[] secretKey = testCase.getTransaction().secretKey; + logger.info("sign tx with: {}", Hex.toHexString(secretKey)); + tx.sign(secretKey); + + BlockchainImpl blockchain = new BlockchainImpl(); + blockchain.setRepository(repository); + + byte[] coinbase = testCase.getEnv().getCurrentCoinbase(); + ProgramInvokeFactory invokeFactory = new TestProgramInvokeFactory(testCase.getEnv()); + + blockchain.setProgramInvokeFactory(invokeFactory); + blockchain.startTracking(); + + Repository track = repository.startTracking(); + TransactionExecutor executor = new TransactionExecutor(tx, coinbase, track, + invokeFactory, null); + executor.execute(); + track.commit(); + + logger.info("compare results"); + + // todo: perform logs comparision + List logs = executor.getResult().getLogInfoList(); + List logResults = testCase.getLogs().compareToReal(logs); + results.addAll(logResults); + + Set fullAddressSet = repository.getFullAddressSet(); + int repoSize = 0; + for (ByteArrayWrapper addrWrapped : fullAddressSet){ + + byte[] addr = addrWrapped.getData(); + + org.ethereum.core.AccountState accountState = repository.getAccountState(addr); + ContractDetails contractDetails = repository.getContractDetails(addr); + + logger.info("{} \n{} \n{}", Hex.toHexString(addr), + accountState.toString(), contractDetails.toString() ); + logger.info(""); + + AccountState expectedAccountState = testCase.getPost().get(wrap(addr)); + if (expectedAccountState == null){ + String formatedString = String.format("Unexpected account state: address: %s", Hex.toHexString(addr)); + results.add(formatedString); + continue; + } + + List result = expectedAccountState.compareToReal(accountState, contractDetails); + results.addAll(result); + + ++repoSize; + } + + int postRepoSize = testCase.getPost().size(); + + if (postRepoSize > repoSize){ + results.add("ERROR: Post repository contains more accounts than executed repository "); + } + + return results; + } public List runTestCase(TestCase testCase) { - Repository repository = new RepositoryImpl(); - + logger.info("\n***"); + logger.info(" Running test case: [" + testCase.getName() + "]") ; + logger.info("***\n"); + List results = new ArrayList<>(); + + + logger.info("--------- PRE ---------"); + RepositoryDummy repository = loadRepository(testCase.getPre()); + try { - System.out.println("\n***"); - System.out.println(" Running test case: [" + testCase.getName() + "]") ; - System.out.println("***\n"); - List results = new ArrayList<>(); - - System.out.println("--------- PRE ---------"); - /* 1. Store pre-exist accounts - Pre */ - for (ByteArrayWrapper key : testCase.getPre().keySet()) { - - AccountState accountState = testCase.getPre().get(key); - - repository.createAccount(key.getData()); - repository.saveCode(key.getData(), accountState.getCode()); - repository.addBalance(key.getData(), new BigInteger(accountState.getBalance())); - - for (long i = 0; i < accountState.getNonceLong(); ++i) - repository.increaseNonce(key.getData()); - } - + + /* 2. Create ProgramInvoke - Env/Exec */ Env env = testCase.getEnv(); Exec exec = testCase.getExec(); Logs logs = testCase.getLogs(); - + byte[] address = exec.getAddress(); byte[] origin = exec.getOrigin(); byte[] caller = exec.getCaller(); @@ -87,17 +159,17 @@ public class TestRunner { long number = new BigInteger(env.getCurrentNumber()).longValue(); byte[] difficulty = env.getCurrentDifficlty(); long gaslimit = new BigInteger(env.getCurrentGasLimit()).longValue(); - + // Origin and caller need to exist in order to be able to execute if(repository.getAccountState(origin) == null) repository.createAccount(origin); if(repository.getAccountState(caller) == null) repository.createAccount(caller); - + ProgramInvoke programInvoke = new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue, msgData, lastHash, coinbase, timestamp, number, difficulty, gaslimit, repository, true); - + /* 3. Create Program - exec.code */ /* 4. run VM */ VM vm = new VM(); @@ -113,7 +185,7 @@ public class TestRunner { e = ex; } program.saveProgramTraceToFile(testCase.getName()); - + if(testCase.getPost().size() == 0) { if(vmDidThrowAnEception != true) { String output = @@ -132,22 +204,22 @@ public class TestRunner { results.add(output); return results; } - + this.trace = program.getProgramTrace(); - + System.out.println("--------- POST --------"); /* 5. Assert Post values */ for (ByteArrayWrapper key : testCase.getPost().keySet()) { - + AccountState accountState = testCase.getPost().get(key); - + long expectedNonce = accountState.getNonceLong(); BigInteger expectedBalance = accountState.getBigIntegerBalance(); byte[] expectedCode = accountState.getCode(); - + boolean accountExist = (null != repository.getAccountState(key.getData())); if (!accountExist) { - + String output = String.format("The expected account does not exist. key: [ %s ]", Hex.toHexString(key.getData())); @@ -155,32 +227,32 @@ public class TestRunner { results.add(output); continue; } - + long actualNonce = repository.getNonce(key.getData()).longValue(); BigInteger actualBalance = repository.getBalance(key.getData()); byte[] actualCode = repository.getCode(key.getData()); if (actualCode == null) actualCode = "".getBytes(); - + if (expectedNonce != actualNonce) { - + String output = String.format("The nonce result is different. key: [ %s ], expectedNonce: [ %d ] is actualNonce: [ %d ] ", Hex.toHexString(key.getData()), expectedNonce, actualNonce); logger.info(output); results.add(output); } - + if (!expectedBalance.equals(actualBalance)) { - + String output = String.format("The balance result is different. key: [ %s ], expectedBalance: [ %s ] is actualBalance: [ %s ] ", Hex.toHexString(key.getData()), expectedBalance.toString(), actualBalance.toString()); logger.info(output); results.add(output); } - + if (!Arrays.equals(expectedCode, actualCode)) { - + String output = String.format("The code result is different. account: [ %s ], expectedCode: [ %s ] is actualCode: [ %s ] ", Hex.toHexString(key.getData()), @@ -189,18 +261,18 @@ public class TestRunner { logger.info(output); results.add(output); } - + // assert storage - Map storage = accountState.getStorage(); - for (ByteArrayWrapper storageKey : storage.keySet()) { - + Map storage = accountState.getStorage(); + for (DataWord storageKey : storage.keySet()) { + byte[] expectedStValue = storage.get(storageKey).getData(); - + ContractDetails contractDetails = program.getResult().getRepository().getContractDetails(accountState.getAddress()); - + if (contractDetails == null) { - + String output = String.format("Storage raw doesn't exist: key [ %s ], expectedValue: [ %s ]", Hex.toHexString(storageKey.getData()), @@ -210,13 +282,13 @@ public class TestRunner { results.add(output); continue; } - + Map testStorage = contractDetails.getStorage(); DataWord actualValue = testStorage.get(new DataWord(storageKey.getData())); if (actualValue == null || !Arrays.equals(expectedStValue, actualValue.getNoLeadZeroesData())) { - + String output = String.format("Storage value different: key [ %s ], expectedValue: [ %s ], actualValue: [ %s ]", Hex.toHexString(storageKey.getData()), @@ -226,7 +298,7 @@ public class TestRunner { results.add(output); } } - + /* asset logs */ List logResult = program.getResult().getLogInfoList(); @@ -296,26 +368,26 @@ public class TestRunner { ++i; } } - + // TODO: assert that you have no extra accounts in the repository // TODO: -> basically the deleted by suicide should be deleted // TODO: -> and no unexpected created - + List resultCallCreates = program.getResult().getCallCreateList(); - + // assert call creates for (int i = 0; i < testCase.getCallCreateList().size(); ++i) { - + org.ethereum.vm.CallCreate resultCallCreate = null; if (resultCallCreates != null && resultCallCreates.size() > i) { resultCallCreate = resultCallCreates.get(i); } - + CallCreate expectedCallCreate = testCase.getCallCreateList().get(i); - + if (resultCallCreate == null && expectedCallCreate != null) { - + String output = String.format("Missing call/create invoke: to: [ %s ], data: [ %s ], gas: [ %s ], value: [ %s ]", Hex.toHexString(expectedCallCreate.getDestination()), @@ -324,15 +396,15 @@ public class TestRunner { Hex.toHexString(expectedCallCreate.getValue())); logger.info(output); results.add(output); - + continue; } - + boolean assertDestination = Arrays.equals( expectedCallCreate.getDestination(), resultCallCreate.getDestination()); if (!assertDestination) { - + String output = String.format("Call/Create destination is different. Expected: [ %s ], result: [ %s ]", Hex.toHexString(expectedCallCreate.getDestination()), @@ -340,12 +412,12 @@ public class TestRunner { logger.info(output); results.add(output); } - + boolean assertData = Arrays.equals( expectedCallCreate.getData(), resultCallCreate.getData()); if (!assertData) { - + String output = String.format("Call/Create data is different. Expected: [ %s ], result: [ %s ]", Hex.toHexString(expectedCallCreate.getData()), @@ -353,7 +425,7 @@ public class TestRunner { logger.info(output); results.add(output); } - + boolean assertGasLimit = Arrays.equals( expectedCallCreate.getGasLimit(), resultCallCreate.getGasLimit()); @@ -365,7 +437,7 @@ public class TestRunner { logger.info(output); results.add(output); } - + boolean assertValue = Arrays.equals( expectedCallCreate.getValue(), resultCallCreate.getValue()); @@ -378,16 +450,16 @@ public class TestRunner { results.add(output); } } - + // assert out byte[] expectedHReturn = testCase.getOut(); - byte[] actualHReturn = ByteUtil.EMPTY_BYTE_ARRAY; + byte[] actualHReturn = EMPTY_BYTE_ARRAY; if (program.getResult().getHReturn() != null) { actualHReturn = program.getResult().getHReturn().array(); } - + if (!Arrays.equals(expectedHReturn, actualHReturn)) { - + String output = String.format("HReturn is different. Expected hReturn: [ %s ], actual hReturn: [ %s ]", Hex.toHexString(expectedHReturn), @@ -395,13 +467,13 @@ public class TestRunner { logger.info(output); results.add(output); } - + // assert gas BigInteger expectedGas = new BigInteger(testCase.getGas()); BigInteger actualGas = new BigInteger(gas).subtract(BigInteger.valueOf(program.getResult().getGasUsed())); - + if (!expectedGas.equals(actualGas)) { - + String output = String.format("Gas remaining is different. Expected gas remaining: [ %s ], actual gas remaining: [ %s ]", expectedGas.toString() , @@ -413,13 +485,54 @@ public class TestRunner { * end of if(testCase.getPost().size() == 0) */ } - + return results; } finally { repository.close(); } } + public org.ethereum.core.Transaction createTransaction(Transaction tx){ + + byte[] nonceBytes = ByteUtil.longToBytes(tx.nonce); + byte[] gasPriceBytes = ByteUtil.longToBytes(tx.gasPrice); + byte[] gasBytes = ByteUtil.longToBytes(tx.gasLimit); + byte[] valueBytes = ByteUtil.longToBytes(tx.value); + byte[] toAddr = tx.getTo(); + byte[] data = tx.getData(); + + org.ethereum.core.Transaction transaction = new org.ethereum.core.Transaction( + nonceBytes, gasPriceBytes, gasBytes, + toAddr, valueBytes, data); + + return transaction; + } + + public RepositoryDummy loadRepository(Map pre){ + + + RepositoryDummy track = new RepositoryDummy(); + + /* 1. Store pre-exist accounts - Pre */ + for (ByteArrayWrapper key : pre.keySet()) { + + AccountState accountState = pre.get(key); + byte[] addr = key.getData(); + + track.addBalance(addr, new BigInteger(1, accountState.getBalance())); + track.setNonce(key.getData(), new BigInteger(1, accountState.getNonce())); + + track.saveCode(addr, accountState.getCode()); + + for (DataWord storageKey : accountState.getStorage().keySet()){ + track.addStorageRow(addr, storageKey, accountState.getStorage().get(storageKey)); + } + } + + return track; + } + + public ProgramTrace getTrace() { return trace; } diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestSuite.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestSuite.java index accdb36b..023f8117 100644 --- a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestSuite.java +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/TestSuite.java @@ -21,9 +21,7 @@ public class TestSuite { for (Object key: testCaseJSONObj.keySet()){ Object testCaseJSON = testCaseJSONObj.get(key); - TestCase testCase = new TestCase(key.toString(), (JSONObject) testCaseJSON); - testList.add(testCase); } } diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Transaction.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Transaction.java new file mode 100644 index 00000000..d2a4c80e --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Transaction.java @@ -0,0 +1,101 @@ +package org.ethereum.jsontestsuite; + +import org.ethereum.util.ByteUtil; +import org.json.simple.JSONObject; +import org.spongycastle.util.encoders.Hex; + +import java.math.BigInteger; +import java.util.Arrays; + +import static org.ethereum.util.ByteUtil.toHexString; + +/** + * www.etherj.com + * + * @author: Roman Mandeleil + * Created on: 28/06/2014 10:25 + */ + +public class Transaction { + + byte[] data; + long gasLimit; + long gasPrice; + long nonce; + byte[] secretKey; + byte[] to; + long value; + +/* e.g. + "transaction" : { + "data" : "", + "gasLimit" : "10000", + "gasPrice" : "1", + "nonce" : "0", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87", + "value" : "100000" +} +*/ + + public Transaction(JSONObject callCreateJSON) { + + String dataStr = callCreateJSON.get("data").toString(); + String gasLimitStr = callCreateJSON.get("gasLimit").toString(); + String gasPriceStr = callCreateJSON.get("gasPrice").toString(); + String nonceStr = callCreateJSON.get("nonce").toString(); + String secretKeyStr = callCreateJSON.get("secretKey").toString(); + String toStr = callCreateJSON.get("to").toString(); + String valueStr = callCreateJSON.get("value").toString(); + + + this.data = Utils.parseData(dataStr); + this.gasLimit = Utils.parseLong(gasLimitStr); + this.gasPrice = Utils.parseLong(gasPriceStr); + this.nonce = Utils.parseLong(nonceStr); + this.secretKey = Utils.parseData(secretKeyStr); + this.to = Utils.parseData(toStr); + this.value = Utils.parseLong(valueStr); + } + + public byte[] getData() { + return data; + } + + public long getGasLimit() { + return gasLimit; + } + + public long getGasPrice() { + return gasPrice; + } + + public long getNonce() { + return nonce; + } + + public byte[] getSecretKey() { + return secretKey; + } + + public byte[] getTo() { + return to; + } + + public long getValue() { + return value; + } + + @Override + public String toString() { + return "Transaction{" + + "data=" + toHexString(data) + + ", gasLimit=" + gasLimit + + ", gasPrice=" + gasPrice + + ", nonce=" + nonce + + ", secretKey=" + toHexString(secretKey) + + ", to=" + toHexString(to) + + ", value=" + value + + '}'; + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Utils.java b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Utils.java new file mode 100644 index 00000000..c264a73f --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/jsontestsuite/Utils.java @@ -0,0 +1,27 @@ +package org.ethereum.jsontestsuite; + +import org.ethereum.util.ByteUtil; +import org.spongycastle.pqc.math.linearalgebra.ByteUtils; +import org.spongycastle.util.encoders.Hex; + +/** + * www.ethergit.com + * + * @author: Roman Mandeleil + * Created on: 15/12/2014 12:41 + */ + +public class Utils { + + + public static byte[] parseData(String data){ + if (data == null) return ByteUtil.EMPTY_BYTE_ARRAY; + if (data.startsWith("0x")) data = data.substring(2); + return Hex.decode(data); + } + + public static long parseLong(String data){ + return data.equals("") ? 0 : Long.parseLong(data); + } + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/listener/EthereumListenerWrapper.java b/ethereumj-core/src/main/java/org/ethereum/listener/EthereumListenerWrapper.java index 09d9c8a0..75b85912 100644 --- a/ethereumj-core/src/main/java/org/ethereum/listener/EthereumListenerWrapper.java +++ b/ethereumj-core/src/main/java/org/ethereum/listener/EthereumListenerWrapper.java @@ -3,6 +3,7 @@ package org.ethereum.listener; import org.ethereum.core.Block; import org.ethereum.core.Transaction; import org.ethereum.net.message.Message; +import org.springframework.stereotype.Component; import java.util.Set; @@ -12,7 +13,7 @@ import java.util.Set; * @author: Roman Mandeleil * Created on: 12/11/2014 08:34 */ - +@Component(value="EthereumListener") public class EthereumListenerWrapper implements EthereumListener{ EthereumListener listener; diff --git a/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java b/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java index 2e1bcefc..be349769 100644 --- a/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java @@ -60,8 +60,8 @@ public class WorldManager { private final Set pendingTransactions = Collections.synchronizedSet(new HashSet()); - - private EthereumListener listener = new EthereumListenerWrapper(); + @Autowired + private EthereumListener listener; @PostConstruct public void init() { diff --git a/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java b/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java index d23ca5cf..8f5d58c5 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java @@ -138,6 +138,21 @@ public class ByteUtil { return 0; return new BigInteger(1, b).intValue(); } + + /** + * Cast hex encoded value from byte[] to int + * + * Limited to Integer.MAX_VALUE: 2^32-1 (4 bytes) + * + * @param b array contains the values + * @return unsigned positive long value. + */ + public static long byteArrayToLong(byte[] b) { + if (b == null || b.length == 0) + return 0; + return new BigInteger(1, b).longValue(); + } + /** * Turn nibbles to a pretty looking output string diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/GasCost.java b/ethereumj-core/src/main/java/org/ethereum/vm/GasCost.java index 44b12073..f7df0274 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/GasCost.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/GasCost.java @@ -26,6 +26,8 @@ public class GasCost { public static int SSTORE = 300; /** Cost 100 gas */ public static int RESET_SSTORE = 100; + /** Cost 100 gas */ + public static int REFUND_SSTORE = 100; /** Cost 100 gas */ public static int CREATE = 100; /** Cost 20 gas */ diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/Program.java b/ethereumj-core/src/main/java/org/ethereum/vm/Program.java index 2bfe5f2d..cdeaea2f 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/Program.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/Program.java @@ -13,6 +13,7 @@ import org.ethereum.vmtrace.ProgramTrace; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; +import org.springframework.beans.factory.annotation.Autowired; import java.io.BufferedWriter; import java.io.File; @@ -33,7 +34,9 @@ public class Program { private static final Logger logger = LoggerFactory.getLogger("VM"); private static final Logger gasLogger = LoggerFactory.getLogger("gas"); - + + ProgramInvokeFactory programInvokeFactory = new ProgramInvokeFactoryImpl(); + private int invokeHash; private ProgramListener listener; @@ -181,7 +184,7 @@ public class Program { public void stackRequire(int stackSize) { if (stack.size() < stackSize) { throw new StackTooSmallException("Expected: " + stackSize - + ", found" + stack.size()); + + ", found: " + stack.size()); } } @@ -318,14 +321,14 @@ public class Program { result.getRepository().addBalance(senderAddress, endowment.negate()); BigInteger newBalance = result.getRepository().addBalance(newAddress, endowment); - Repository track = result.getRepository().startTracking(); - // [3] UPDATE THE NONCE // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) - track.increaseNonce(senderAddress); + result.getRepository().increaseNonce(senderAddress); + + Repository track = result.getRepository().startTracking(); // [5] COOK THE INVOKE AND EXECUTE - ProgramInvoke programInvoke = ProgramInvokeFactory.createProgramInvoke( + ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke( this, new DataWord(newAddress), DataWord.ZERO, gasLimit, newBalance, null, track); @@ -427,7 +430,7 @@ public class Program { this.spendGas(msg.getGas().longValue(), "internal call"); Repository trackRepository = result.getRepository().startTracking(); - ProgramInvoke programInvoke = ProgramInvokeFactory.createProgramInvoke( + ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke( this, new DataWord(contextAddress), msg.getEndowment(), msg.getGas(), contextBalance, data, trackRepository); @@ -507,6 +510,10 @@ public class Program { result.refundGas(gasValue); } + public void futureRefundGas(long gasValue) { + result.futureRefundGas(gasValue); + } + public void storageSave(DataWord word1, DataWord word2) { storageSave(word1.getData(), word2.getData()); } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeFactory.java b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeFactory.java index 71860ebe..65972700 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeFactory.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeFactory.java @@ -2,196 +2,24 @@ package org.ethereum.vm; import org.ethereum.core.Block; import org.ethereum.core.Transaction; -import org.ethereum.facade.Blockchain; import org.ethereum.facade.Repository; -import org.ethereum.manager.WorldManager; -import org.ethereum.util.ByteUtil; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.spongycastle.util.encoders.Hex; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import java.math.BigInteger; /** - * www.ethereumJ.com + * www.etherj.com * * @author: Roman Mandeleil - * Created on: 08/06/2014 09:59 + * Created on: 19/12/2014 12:14 */ -@Component -public class ProgramInvokeFactory { - private static final Logger logger = LoggerFactory.getLogger("VM"); +public interface ProgramInvokeFactory { - @Autowired - private Blockchain blockchain; - - /** - * This attribute defines the number of recursive calls allowed in the EVM - * Note: For the JVM to reach this level without a StackOverflow exception, - * ethereumj may need to be started with a JVM argument to increase - * the stack size. For example: -Xss10m - */ - private static final int MAX_DEPTH = 1024; - - // Invocation by the wire tx - public ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository repository) { - - // https://ethereum.etherpad.mozilla.org/26 - Block lastBlock = blockchain.getBestBlock(); - - /*** ADDRESS op ***/ - // YP: Get address of currently executing account. - byte[] address = tx.isContractCreation() ? tx.getContractAddress(): tx.getReceiveAddress(); - - /*** ORIGIN op ***/ - // YP: This is the sender of original transaction; it is never a contract. - byte[] origin = tx.getSender(); - - /*** CALLER op ***/ - // YP: This is the address of the account that is directly responsible for this execution. - byte[] caller = tx.getSender(); - - /*** BALANCE op ***/ - byte[] balance = repository.getBalance(address).toByteArray(); - - /*** GASPRICE op ***/ - byte[] gasPrice = tx.getGasPrice(); - - /*** GAS op ***/ - byte[] gas = tx.getGasLimit(); - - /*** CALLVALUE op ***/ - byte[] callValue = tx.getValue() == null ? new byte[]{0} : tx.getValue(); - - /*** CALLDATALOAD op ***/ - /*** CALLDATACOPY op ***/ - /*** CALLDATASIZE op ***/ - byte[] data = tx.getData() == null ? ByteUtil.EMPTY_BYTE_ARRAY : tx.getData(); - - /*** PREVHASH op ***/ - byte[] lastHash = lastBlock.getHash(); - - /*** COINBASE op ***/ - byte[] coinbase = block.getCoinbase(); - - /*** TIMESTAMP op ***/ - long timestamp = block.getTimestamp(); - - /*** NUMBER op ***/ - long number = block.getNumber(); - - /*** DIFFICULTY op ***/ - byte[] difficulty = block.getDifficulty(); - - /*** GASLIMIT op ***/ - long gaslimit = block.getGasLimit(); - - if (logger.isInfoEnabled()) { - logger.info("Top level call: \n" + - "address={}\n" + - "origin={}\n" + - "caller={}\n" + - "balance={}\n" + - "gasPrice={}\n" + - "gas={}\n" + - "callValue={}\n" + - "data={}\n" + - "lastHash={}\n" + - "coinbase={}\n" + - "timestamp={}\n" + - "blockNumber={}\n" + - "difficulty={}\n" + - "gaslimit={}\n", - - Hex.toHexString(address), - Hex.toHexString(origin), - Hex.toHexString(caller), - new BigInteger(balance).longValue(), - new BigInteger(gasPrice).longValue(), - new BigInteger(gas).longValue(), - new BigInteger(callValue).longValue(), - Hex.toHexString(data), - Hex.toHexString(lastHash), - Hex.toHexString(coinbase), - timestamp, - number, - Hex.toHexString(difficulty), - gaslimit); - } - - ProgramInvoke programInvoke = - new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue, data, - lastHash, coinbase, timestamp, number, difficulty, gaslimit, - repository); - - return programInvoke; - } - - /** - * This invocation created for contract call contract - */ - public static ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, + public ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository repository); + public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, DataWord inValue, DataWord inGas, BigInteger balanceInt, byte[] dataIn, - Repository repository) { + Repository repository); - DataWord address = toAddress; - DataWord origin = program.getOriginAddress(); - DataWord caller = program.getOwnerAddress(); - DataWord balance = new DataWord(balanceInt.toByteArray()); - DataWord gasPrice = program.getGasPrice(); - DataWord gas = inGas; - DataWord callValue = inValue; - - byte[] data = dataIn; - DataWord lastHash = program.getPrevHash(); - DataWord coinbase = program.getCoinbase(); - DataWord timestamp = program.getTimestamp(); - DataWord number = program.getNumber(); - DataWord difficulty = program.getDifficulty(); - DataWord gasLimit = program.getGaslimit(); - - if (logger.isInfoEnabled()) { - logger.info("Internal call: \n" + - "address={}\n" + - "origin={}\n" + - "caller={}\n" + - "balance={}\n" + - "gasPrice={}\n" + - "gas={}\n" + - "callValue={}\n" + - "data={}\n" + - "lastHash={}\n" + - "coinbase={}\n" + - "timestamp={}\n" + - "blockNumber={}\n" + - "difficulty={}\n" + - "gaslimit={}\n", - Hex.toHexString(address.getLast20Bytes()), - Hex.toHexString(origin.getLast20Bytes()), - Hex.toHexString(caller.getLast20Bytes()), - balance.longValue(), - gasPrice.longValue(), - gas.longValue(), - callValue.longValue(), - data == null ? "": Hex.toHexString(data), - Hex.toHexString(lastHash.getData()), - Hex.toHexString(coinbase.getLast20Bytes()), - timestamp.longValue(), - number.longValue(), - Hex.toHexString(difficulty.getNoLeadZeroesData()), - gasLimit.longValue()); - } - - if (program.invokeData.getCallDeep() >= MAX_DEPTH) - throw program.new OutOfGasException(); - - return new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue, - data, lastHash, coinbase, timestamp, number, difficulty, gasLimit, - repository, program.invokeData.getCallDeep()+1); - } } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeFactoryImpl.java b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeFactoryImpl.java new file mode 100644 index 00000000..f64ab13b --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeFactoryImpl.java @@ -0,0 +1,199 @@ +package org.ethereum.vm; + +import org.ethereum.core.Block; +import org.ethereum.core.Transaction; +import org.ethereum.facade.Blockchain; +import org.ethereum.facade.Repository; +import org.ethereum.manager.WorldManager; +import org.ethereum.util.ByteUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.math.BigInteger; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 08/06/2014 09:59 + */ +@Component("ProgramInvokeFactory") +public class ProgramInvokeFactoryImpl implements ProgramInvokeFactory { + + private static final Logger logger = LoggerFactory.getLogger("VM"); + + @Autowired + private Blockchain blockchain; + + /** + * This attribute defines the number of recursive calls allowed in the EVM + * Note: For the JVM to reach this level without a StackOverflow exception, + * ethereumj may need to be started with a JVM argument to increase + * the stack size. For example: -Xss10m + */ + private static final int MAX_DEPTH = 1024; + + // Invocation by the wire tx + @Override + public ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository repository) { + + // https://ethereum.etherpad.mozilla.org/26 + Block lastBlock = blockchain.getBestBlock(); + + /*** ADDRESS op ***/ + // YP: Get address of currently executing account. + byte[] address = tx.isContractCreation() ? tx.getContractAddress(): tx.getReceiveAddress(); + + /*** ORIGIN op ***/ + // YP: This is the sender of original transaction; it is never a contract. + byte[] origin = tx.getSender(); + + /*** CALLER op ***/ + // YP: This is the address of the account that is directly responsible for this execution. + byte[] caller = tx.getSender(); + + /*** BALANCE op ***/ + byte[] balance = repository.getBalance(address).toByteArray(); + + /*** GASPRICE op ***/ + byte[] gasPrice = tx.getGasPrice(); + + /*** GAS op ***/ + byte[] gas = tx.getGasLimit(); + + /*** CALLVALUE op ***/ + byte[] callValue = tx.getValue() == null ? new byte[]{0} : tx.getValue(); + + /*** CALLDATALOAD op ***/ + /*** CALLDATACOPY op ***/ + /*** CALLDATASIZE op ***/ + byte[] data = tx.getData() == null ? ByteUtil.EMPTY_BYTE_ARRAY : tx.getData(); + + /*** PREVHASH op ***/ + byte[] lastHash = lastBlock.getHash(); + + /*** COINBASE op ***/ + byte[] coinbase = block.getCoinbase(); + + /*** TIMESTAMP op ***/ + long timestamp = block.getTimestamp(); + + /*** NUMBER op ***/ + long number = block.getNumber(); + + /*** DIFFICULTY op ***/ + byte[] difficulty = block.getDifficulty(); + + /*** GASLIMIT op ***/ + long gaslimit = block.getGasLimit(); + + if (logger.isInfoEnabled()) { + logger.info("Top level call: \n" + + "address={}\n" + + "origin={}\n" + + "caller={}\n" + + "balance={}\n" + + "gasPrice={}\n" + + "gas={}\n" + + "callValue={}\n" + + "data={}\n" + + "lastHash={}\n" + + "coinbase={}\n" + + "timestamp={}\n" + + "blockNumber={}\n" + + "difficulty={}\n" + + "gaslimit={}\n", + + Hex.toHexString(address), + Hex.toHexString(origin), + Hex.toHexString(caller), + new BigInteger(balance).longValue(), + new BigInteger(gasPrice).longValue(), + new BigInteger(gas).longValue(), + new BigInteger(callValue).longValue(), + Hex.toHexString(data), + Hex.toHexString(lastHash), + Hex.toHexString(coinbase), + timestamp, + number, + Hex.toHexString(difficulty), + gaslimit); + } + + ProgramInvoke programInvoke = + new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue, data, + lastHash, coinbase, timestamp, number, difficulty, gaslimit, + repository); + + return programInvoke; + } + + /** + * This invocation created for contract call contract + */ + @Override + public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, + DataWord inValue, DataWord inGas, + BigInteger balanceInt, byte[] dataIn, + Repository repository) { + + DataWord address = toAddress; + DataWord origin = program.getOriginAddress(); + DataWord caller = program.getOwnerAddress(); + + DataWord balance = new DataWord(balanceInt.toByteArray()); + DataWord gasPrice = program.getGasPrice(); + DataWord gas = inGas; + DataWord callValue = inValue; + + byte[] data = dataIn; + DataWord lastHash = program.getPrevHash(); + DataWord coinbase = program.getCoinbase(); + DataWord timestamp = program.getTimestamp(); + DataWord number = program.getNumber(); + DataWord difficulty = program.getDifficulty(); + DataWord gasLimit = program.getGaslimit(); + + if (logger.isInfoEnabled()) { + logger.info("Internal call: \n" + + "address={}\n" + + "origin={}\n" + + "caller={}\n" + + "balance={}\n" + + "gasPrice={}\n" + + "gas={}\n" + + "callValue={}\n" + + "data={}\n" + + "lastHash={}\n" + + "coinbase={}\n" + + "timestamp={}\n" + + "blockNumber={}\n" + + "difficulty={}\n" + + "gaslimit={}\n", + Hex.toHexString(address.getLast20Bytes()), + Hex.toHexString(origin.getLast20Bytes()), + Hex.toHexString(caller.getLast20Bytes()), + balance.longValue(), + gasPrice.longValue(), + gas.longValue(), + callValue.longValue(), + data == null ? "": Hex.toHexString(data), + Hex.toHexString(lastHash.getData()), + Hex.toHexString(coinbase.getLast20Bytes()), + timestamp.longValue(), + number.longValue(), + Hex.toHexString(difficulty.getNoLeadZeroesData()), + gasLimit.longValue()); + } + + if (program.invokeData.getCallDeep() >= MAX_DEPTH) + throw program.new OutOfGasException(); + + return new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue, + data, lastHash, coinbase, timestamp, number, difficulty, gasLimit, + repository, program.invokeData.getCallDeep()+1); + } +} diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java index ac950d51..c4554261 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java @@ -18,6 +18,7 @@ public class ProgramResult { private RuntimeException exception; private List deleteAccounts; private List logInfoList; + private long futureRefund = 0; private Repository repository = null; @@ -105,4 +106,12 @@ public class ProgramResult { callCreateList = new ArrayList<>(); callCreateList.add(new CallCreate(data, destination, gasLimit, value)); } + + public void futureRefundGas(long gasValue) { + futureRefund += gasValue; + } + + public long getFutureRefund(){ + return futureRefund; + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java index b2be1c38..37c60266 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java @@ -7,6 +7,7 @@ import static org.ethereum.config.SystemProperties.CONFIG; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.spongycastle.util.BigIntegers; import org.spongycastle.util.encoders.Hex; import org.ethereum.vm.MessageCall.MsgType; @@ -106,10 +107,13 @@ public class VM { DataWord oldValue = program.storageLoad(stack.peek()); if (oldValue == null && !newValue.isZero()) gasCost = GasCost.SSTORE; - else if (oldValue != null && newValue.isZero()) + else if (oldValue != null && newValue.isZero()) { // todo: GASREFUND counter policy - System.currentTimeMillis(); - else + + // refund step cost policy. + program.futureRefundGas( GasCost.REFUND_SSTORE ); + gasCost = 0; + } else gasCost = GasCost.RESET_SSTORE; break; case SLOAD: @@ -170,10 +174,16 @@ public class VM { int nTopics = op.val() - OpCode.LOG0.val(); newMemSize = memNeeded(stack.peek(), stack.get(stack.size()-2)); + + BigInteger dataSize = stack.get(stack.size() - 2).value(); + BigInteger dataCost = dataSize.multiply(BigInteger.valueOf(GasCost.LOG_DATA_GAS)); + if (program.getGas().value().compareTo(dataCost) < 0){ + throw program.new OutOfGasException(); + } + gasCost = GasCost.LOG_GAS + GasCost.LOG_TOPIC_GAS * nTopics + GasCost.LOG_DATA_GAS * stack.get(stack.size()-2).longValue(); - break; case EXP: @@ -632,18 +642,22 @@ public class VM { BigInteger codeOffsetData = program.stackPop().value(); BigInteger lengthData = program.stackPop().value(); - if (fullCode == null - || BigInteger.valueOf(fullCode.length).compareTo( - codeOffsetData.add(lengthData)) < 0) { +/* + todo: find out what to do where params are exits the actual code + if (fullCode == null || + BigInteger.valueOf(fullCode.length). + compareTo( + codeOffsetData.add(lengthData)) > 0) { program.stop(); break; } +*/ int length = lengthData.intValue(); int codeOffset = codeOffsetData.intValue(); - byte[] codeCopy = new byte[length]; - System.arraycopy(fullCode, codeOffset, codeCopy, 0, length); + byte[] codeCopy = new byte[fullCode.length - codeOffset]; + System.arraycopy(fullCode, codeOffset, codeCopy, 0, fullCode.length - codeOffset); if (logger.isInfoEnabled()) hint = "code: " + Hex.toHexString(codeCopy); diff --git a/ethereumj-core/src/test/java/test/ethereum/jsontestsuite/GitHubJSONTestSuite.java b/ethereumj-core/src/test/java/test/ethereum/jsontestsuite/GitHubJSONTestSuite.java index 22f33d59..0e1d26cb 100644 --- a/ethereumj-core/src/test/java/test/ethereum/jsontestsuite/GitHubJSONTestSuite.java +++ b/ethereumj-core/src/test/java/test/ethereum/jsontestsuite/GitHubJSONTestSuite.java @@ -1,11 +1,10 @@ package test.ethereum.jsontestsuite; +import java.util.Collection; import java.util.Iterator; import java.util.List; -import org.ethereum.jsontestsuite.TestCase; -import org.ethereum.jsontestsuite.TestRunner; -import org.ethereum.jsontestsuite.TestSuite; +import org.ethereum.jsontestsuite.*; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; @@ -14,6 +13,8 @@ import org.junit.Assume; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Test file specific for tests maintained in the GitHub repository @@ -26,6 +27,9 @@ import org.junit.runners.Suite.SuiteClasses; GitHubVMTest.class, }) public class GitHubJSONTestSuite { + + private static Logger logger = LoggerFactory.getLogger("TCK-Test"); + protected static void runGitHubJsonTest(String json) throws ParseException { Assume.assumeFalse("Online test is not available", json.equals("")); @@ -45,4 +49,59 @@ public class GitHubJSONTestSuite { Assert.assertTrue(result.isEmpty()); } } + + protected static void runGitHubJsonStateTest(String json, String testName) throws ParseException { + Assume.assumeFalse("Online test is not available", json.equals("")); + + JSONParser parser = new JSONParser(); + JSONObject testSuiteObj = (JSONObject)parser.parse(json); + + StateTestSuite testSuite = new StateTestSuite(testSuiteObj); + + for(StateTestCase testCase : testSuite.getAllTests()){ + if (testCase.getName().equals(testName)) + logger.info(" => " + testCase.getName()); + else + logger.info(" " + testCase.getName()); + } + + StateTestCase testCase = testSuite.getTestCase(testName); + TestRunner runner = new TestRunner(); + List result = runner.runTestCase(testCase); + + if (!result.isEmpty()){ + for (String single : result){ + logger.info(single); + } + } + + Assert.assertTrue(result.isEmpty()); + } + + protected static void runGitHubJsonStateTest(String json) throws ParseException { + Assume.assumeFalse("Online test is not available", json.equals("")); + + JSONParser parser = new JSONParser(); + JSONObject testSuiteObj = (JSONObject)parser.parse(json); + + StateTestSuite testSuite = new StateTestSuite(testSuiteObj); + Collection testCollection = testSuite.getAllTests(); + + + for (StateTestCase testCase : testCollection){ + + TestRunner runner = new TestRunner(); + List result = runner.runTestCase(testCase); + + if (!result.isEmpty()){ + for (String single : result){ + logger.info(single); + } + } + + Assert.assertTrue(result.isEmpty()); + } + } + + } diff --git a/ethereumj-core/src/test/java/test/ethereum/jsontestsuite/GitHubStateTest.java b/ethereumj-core/src/test/java/test/ethereum/jsontestsuite/GitHubStateTest.java new file mode 100644 index 00000000..47814253 --- /dev/null +++ b/ethereumj-core/src/test/java/test/ethereum/jsontestsuite/GitHubStateTest.java @@ -0,0 +1,88 @@ +package test.ethereum.jsontestsuite; + +import org.ethereum.jsontestsuite.JSONReader; +import org.json.simple.parser.ParseException; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class GitHubStateTest { + + @Test + public void stSingleTest() throws ParseException { + + String json = JSONReader.loadJSON("StateTests/stSystemOperationsTest.json"); + GitHubJSONTestSuite.runGitHubJsonStateTest(json, "CallRecursiveBombLog"); + } + + @Test + public void stExample() throws ParseException { + + String json = JSONReader.loadJSON("StateTests/stExample.json"); + GitHubJSONTestSuite.runGitHubJsonStateTest(json); + } + + @Test + public void stInitCodeTest() throws ParseException { // [V] + + String json = JSONReader.loadJSON("StateTests/stInitCodeTest.json"); + GitHubJSONTestSuite.runGitHubJsonStateTest(json); + } + + @Test + public void stLogTests() throws ParseException { // [V] + + String json = JSONReader.loadJSON("StateTests/stLogTests.json"); + GitHubJSONTestSuite.runGitHubJsonStateTest(json); + } + + @Test + public void stPreCompiledContracts() throws ParseException { + + String json = JSONReader.loadJSON("StateTests/stPreCompiledContracts.json"); + GitHubJSONTestSuite.runGitHubJsonStateTest(json); + } + + @Test + public void stRecursiveCreate() throws ParseException { // [V] + + String json = JSONReader.loadJSON("StateTests/stRecursiveCreate.json"); + GitHubJSONTestSuite.runGitHubJsonStateTest(json); + } + + @Test + public void stRefundTest() throws ParseException { // [V] + + String json = JSONReader.loadJSON("StateTests/stRefundTest.json"); + GitHubJSONTestSuite.runGitHubJsonStateTest(json); + } + + + @Test + public void stSpecialTest() throws ParseException { + + String json = JSONReader.loadJSON("StateTests/stSpecialTest.json"); + GitHubJSONTestSuite.runGitHubJsonStateTest(json); + } + + @Test + public void stSystemOperationsTest() throws ParseException { + + String json = JSONReader.loadJSON("StateTests/stSystemOperationsTest.json"); + GitHubJSONTestSuite.runGitHubJsonStateTest(json); + } + + + @Test + public void stTransactionTest() throws ParseException { + + String json = JSONReader.loadJSON("StateTests/stTransactionTest.json"); + GitHubJSONTestSuite.runGitHubJsonStateTest(json); + } + + + + +} +