diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java b/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java index ae75ac3a..52e72137 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java @@ -12,7 +12,6 @@ import org.spongycastle.util.encoders.Hex; import java.math.BigInteger; import java.util.Arrays; -import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -65,9 +64,6 @@ public class Blockchain { // keep the index of the chain for // convenient usage, private Map blockCache = new HashMap<>(); - - private Map pendingTransactions = Collections - .synchronizedMap(new HashMap()); private BlockQueue blockQueue = new BlockQueue(); @@ -115,6 +111,9 @@ public class Blockchain { if (block == null) return; + + if (block.getNumber() == 3211) + logger.debug("Block #3211"); // if it is the first block to add // make sure the parent is genesis @@ -171,7 +170,7 @@ public class Blockchain { stateLogger.debug("apply block: [ {} ] tx: [ {} ] ", block.getNumber(), i); totalGasUsed += applyTransaction(block, txr.getTransaction()); if(!Arrays.equals(this.repository.getWorldState().getRootHash(), txr.getPostTxState())) - logger.warn("TX: STATE CONFLICT {}..: {}", Hex.toHexString(txr.getTransaction().getHash()).substring(0, 8), + stateLogger.warn("TX: STATE CONFLICT {}..: {}", Hex.toHexString(txr.getTransaction().getHash()).substring(0, 8), Hex.toHexString(this.repository.getWorldState().getRootHash())); if(block.getNumber() >= CONFIG.traceStartBlock()) repository.dumpState(block, totalGasUsed, i++, txr.getTransaction().getHash()); @@ -197,7 +196,7 @@ public class Blockchain { String blockStateRootHash = Hex.toHexString(block.getStateRoot()); String worldStateRootHash = Hex.toHexString(WorldManager.getInstance().getRepository().getWorldState().getRootHash()); if(!blockStateRootHash.equals(worldStateRootHash)){ - logger.warn("BLOCK: STATE CONFLICT! block: {} worldstate {} mismatch", block.getNumber(), worldStateRootHash); + stateLogger.warn("BLOCK: STATE CONFLICT! block: {} worldstate {} mismatch", block.getNumber(), worldStateRootHash); // repository.close(); // System.exit(-1); // Don't add block } @@ -214,18 +213,21 @@ public class Blockchain { /** - * Apply the transaction to the world state + * 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 + * @return gasUsed - the total amount of gas used for this transaction. */ public long applyTransaction(Block block, Transaction tx) { byte[] coinbase = block.getCoinbase(); + + // VALIDATE THE SENDER byte[] senderAddress = tx.getSender(); AccountState senderAccount = repository.getAccountState(senderAddress); - if (senderAccount == null) { if (stateLogger.isWarnEnabled()) stateLogger.warn("No such address: {}", @@ -233,7 +235,7 @@ public class Blockchain { return 0; } - // 1. VALIDATE THE NONCE + // VALIDATE THE NONCE BigInteger nonce = senderAccount.getNonce(); BigInteger txNonce = new BigInteger(1, tx.getNonce()); if (nonce.compareTo(txNonce) != 0) { @@ -242,24 +244,19 @@ public class Blockchain { nonce, txNonce); return 0; } + + // UPDATE THE NONCE + repository.increaseNonce(senderAddress); - // 3. FIND OUT THE TRANSACTION TYPE + // FIND OUT THE TRANSACTION TYPE byte[] receiverAddress, code = null; boolean isContractCreation = tx.isContractCreation(); if (isContractCreation) { receiverAddress = tx.getContractAddress(); - repository.createAccount(receiverAddress); - if(stateLogger.isDebugEnabled()) - stateLogger.debug("new contract created address={}", - Hex.toHexString(receiverAddress)); code = tx.getData(); // init code - if (stateLogger.isDebugEnabled()) - stateLogger.debug("running the init for contract: address={}", - Hex.toHexString(receiverAddress)); } else { receiverAddress = tx.getReceiveAddress(); - AccountState receiverState = repository.getAccountState(receiverAddress); - if (receiverState == null) { + if (repository.getAccountState(receiverAddress) == null) { repository.createAccount(receiverAddress); if (stateLogger.isDebugEnabled()) stateLogger.debug("new receiver account created address={}", @@ -274,12 +271,27 @@ public class Blockchain { } } - // 2.1 UPDATE THE NONCE - // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) - repository.increaseNonce(senderAddress); + // THE SIMPLE VALUE/BALANCE CHANGE + boolean isValueTx = tx.getValue() != null; + if (isValueTx) { + BigInteger txValue = new BigInteger(1, tx.getValue()); + if (senderAccount.getBalance().compareTo(txValue) >= 0) { + senderAccount.subFromBalance(txValue); // balance will be read again below + repository.addBalance(senderAddress, txValue.negate()); + + if(!isContractCreation) // adding to new contract could be reverted + repository.addBalance(receiverAddress, txValue); + + if (stateLogger.isDebugEnabled()) + stateLogger.debug("Update value balance \n " + + "sender={}, receiver={}, value={}", + Hex.toHexString(senderAddress), + Hex.toHexString(receiverAddress), + new BigInteger(tx.getValue())); + } + } - // 2.2 PERFORM THE GAS VALUE TX - // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) + // GET TOTAL ETHER VALUE AVAILABLE FOR TX FEE BigInteger gasDebit = tx.getTotalGasValueDebit(); // Debit the actual total gas value from the sender @@ -287,14 +299,12 @@ public class Blockchain { // the contract in the execution state, // it can be retrieved using GAS op if (gasDebit.signum() == 1) { - BigInteger balance = senderAccount.getBalance(); - if (balance.compareTo(gasDebit) == -1) { + if (senderAccount.getBalance().compareTo(gasDebit) == -1) { logger.debug("No gas to start the execution: sender={}", Hex.toHexString(senderAddress)); return 0; } repository.addBalance(senderAddress, gasDebit.negate()); - senderAccount.subFromBalance(gasDebit); // balance will be read again below // The coinbase get the gas cost if (coinbase != null) @@ -306,37 +316,28 @@ public class Blockchain { + "\n sender={} \n gas_debit= {}", Hex.toHexString(senderAddress), gasDebit); } - - - // 3. THE SIMPLE VALUE/BALANCE CHANGE - if (tx.getValue() != null) { - - BigInteger senderBalance = senderAccount.getBalance(); - - if (senderBalance.compareTo(new BigInteger(1, tx.getValue())) >= 0) { - repository.addBalance(receiverAddress, - new BigInteger(1, tx.getValue())); - repository.addBalance(senderAddress, - new BigInteger(1, tx.getValue()).negate()); - - if (stateLogger.isDebugEnabled()) - stateLogger.debug("Update value balance \n " - + "sender={}, receiver={}, value={}", - Hex.toHexString(senderAddress), - Hex.toHexString(receiverAddress), - new BigInteger(tx.getValue())); - } - } - - // 4. START TRACKING FOR REVERT CHANGES OPTION !!! - Repository trackRepository = repository.getTrack(); - trackRepository.startTracking(); + + // CREATE AND/OR EXECUTE CONTRACT long gasUsed = 0; - try { - - // 5. CREATE OR EXECUTE PROGRAM - - if (isContractCreation || code != null) { + if (isContractCreation || code != null) { + + // START TRACKING FOR REVERT CHANGES OPTION + Repository trackRepository = repository.getTrack(); + trackRepository.startTracking(); + try { + + // CREATE NEW CONTRACT ADDRESS AND ADD TX VALUE + if(isContractCreation) { + if (isValueTx) // adding to balance also creates the account + trackRepository.addBalance(receiverAddress, new BigInteger(1, tx.getValue())); + else + trackRepository.createAccount(receiverAddress); + + if(stateLogger.isDebugEnabled()) + stateLogger.debug("new contract created address={}", + Hex.toHexString(receiverAddress)); + } + Block currBlock = (block == null) ? this.getLastBlock() : block; ProgramInvoke programInvoke = ProgramInvokeFactory @@ -351,24 +352,24 @@ public class Blockchain { applyProgramResult(result, gasDebit, trackRepository, senderAddress, receiverAddress, coinbase, isContractCreation); gasUsed = result.getGasUsed(); - } else { - // refund everything except fee (500 + 5*txdata) - BigInteger gasPrice = new BigInteger(1, tx.getGasPrice()); - long dataFee = tx.getData() == null ? 0: tx.getData().length * GasCost.TXDATA; - long minTxFee = GasCost.TRANSACTION + dataFee; - BigInteger refund = gasDebit.subtract(BigInteger.valueOf(minTxFee).multiply(gasPrice)); - if (refund.signum() > 0) { - // gas refund - repository.addBalance(senderAddress, refund); - repository.addBalance(coinbase, refund.negate()); - } + + } catch (RuntimeException e) { + trackRepository.rollback(); + return new BigInteger(1, tx.getGasLimit()).longValue(); + } + trackRepository.commit(); + } else { + // REFUND GASDEBIT EXCEPT FOR FEE (500 + 5*TXDATA) + long dataCost = tx.getData() == null ? 0: tx.getData().length * GasCost.TXDATA; + gasUsed = GasCost.TRANSACTION + dataCost; + + BigInteger gasPrice = new BigInteger(1, tx.getGasPrice()); + BigInteger refund = gasDebit.subtract(BigInteger.valueOf(gasUsed).multiply(gasPrice)); + if (refund.signum() > 0) { + repository.addBalance(senderAddress, refund); + repository.addBalance(coinbase, refund.negate()); } - } catch (RuntimeException e) { - trackRepository.rollback(); - return 0; } - trackRepository.commit(); - pendingTransactions.put(Hex.toHexString(tx.getHash()), tx); return gasUsed; } 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 f70e64ac..beded606 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/Program.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/Program.java @@ -44,15 +44,16 @@ public class Program { this.invokeHash = invokeData.hashCode(); result.setRepository(invokeData.getRepository()); - if (ops == null) throw new RuntimeException("program can not run with ops: null"); + if (ops == null) ops = new byte[0]; //throw new RuntimeException("program can not run with ops: null"); this.invokeData = invokeData; this.ops = ops; this.programAddress = invokeData.getOwnerAddress(); - } public byte getCurrentOp() { + if(ops.length == 0) + return 0; return ops[pc]; } @@ -78,6 +79,10 @@ public class Program { public void stackPush(DataWord stackWord) { stack.push(stackWord); } + + public Stack getStack() { + return this.stack; + } public int getPC() { return pc; @@ -114,7 +119,6 @@ public class Program { } public void step() { - ++pc; if (pc >= ops.length) stop(); } @@ -140,6 +144,13 @@ public class Program { } return stack.pop(); } + + public void require(int stackSize) { + if(stack.size() != stackSize) { + stop(); + throw new RuntimeException("stack too small"); + } + } public int getMemSize() { int memSize = 0; @@ -171,11 +182,10 @@ public class Program { public ByteBuffer memoryChunk(DataWord offsetData, DataWord sizeData) { - int offset = offsetData.value().intValue(); - int size = sizeData.value().intValue(); - allocateMemory(offset, new byte[sizeData.intValue()]); - + int offset = offsetData.intValue(); + int size = sizeData.intValue(); byte[] chunk = new byte[size]; + allocateMemory(offset, chunk); if (memory != null) { if (memory.limit() < offset + size) size = memory.limit() - offset; @@ -269,7 +279,6 @@ public class Program { result.getRepository().addBalance(senderAddress, endowment.negate()); result.getRepository().addBalance(newAddress, endowment); - Repository trackRepository = result.getRepository().getTrack(); trackRepository.startTracking(); @@ -435,12 +444,11 @@ public class Program { Hex.toHexString(senderAddress), refundGas.toString()); } } else { - this.refundGas(gas.intValue(), "remaining gas from the internal call"); } } - public void spendGas(int gasValue, String cause) { + public void spendGas(long gasValue, String cause) { gasLogger.info("[{}] Spent for cause: [ {} ], gas: [ {} ]", invokeHash, cause, gasValue); long afterSpend = invokeData.getGas().longValue() - gasValue - result.getGasUsed(); @@ -547,7 +555,6 @@ public class Program { return invokeData.getGaslimit().clone(); } - public ProgramResult getResult() { return result; } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java index 3dde641d..6a05c650 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java @@ -2,6 +2,7 @@ package org.ethereum.vm; import org.ethereum.db.Repository; +import java.math.BigInteger; import java.util.Arrays; import java.util.Map; @@ -134,28 +135,32 @@ public class ProgramInvokeImpl implements ProgramInvoke { /*****************/ /*** msg data ***/ /*****************/ - + /* NOTE: In the protocol there is no restriction on the maximum message data, + * However msgData here is a byte[] and this can't hold more than 2^32-1 + */ + private static BigInteger MAX_MSG_DATA = BigInteger.valueOf(Integer.MAX_VALUE); /* CALLDATALOAD op */ public DataWord getDataValue(DataWord indexData) { + BigInteger tempIndex = indexData.value(); + int index = tempIndex.intValue(); // possible overflow is caught below + int size = 32; // maximum datavalue size + + if (msgData == null || index >= msgData.length + || tempIndex.compareTo(MAX_MSG_DATA) == 1) + return new DataWord(); + if (index + size > msgData.length) + size = msgData.length - index; + byte[] data = new byte[32]; - - int index = indexData.value().intValue(); - int size = 32; - - if (msgData == null) return new DataWord(data); - if (index > msgData.length) return new DataWord(data); - if (index + 32 > msgData.length) size = msgData.length - index ; - System.arraycopy(msgData, index, data, 0, size); - return new DataWord(data); } /* CALLDATASIZE */ public DataWord getDataSize() { - if (msgData == null || msgData.length == 0) return new DataWord(new byte[32]); + if (msgData == null || msgData.length == 0) return DataWord.ZERO; int size = msgData.length; return new DataWord(size); } @@ -163,8 +168,8 @@ public class ProgramInvokeImpl implements ProgramInvoke { /* CALLDATACOPY */ public byte[] getDataCopy(DataWord offsetData, DataWord lengthData) { - int offset = offsetData.value().intValue(); - int length = lengthData.value().intValue(); + int offset = offsetData.intValue(); + int length = lengthData.intValue(); byte[] data = new byte[length]; 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 8849130c..efca8d4d 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java @@ -27,7 +27,7 @@ public class ProgramResult { */ private List callCreateList; - public void spendGas(int gas) { + public void spendGas(long gas) { gasUsed += gas; } public void refundGas(long gas) { 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 0f3445b8..52a2d901 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java @@ -3,7 +3,9 @@ package org.ethereum.vm; import org.ethereum.crypto.HashUtil; import org.ethereum.db.ContractDetails; import org.ethereum.vm.Program.OutOfGasException; + import static org.ethereum.config.SystemProperties.CONFIG; + import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; @@ -13,6 +15,7 @@ import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Stack; import static org.ethereum.vm.OpCode.CALL; import static org.ethereum.vm.OpCode.CREATE; @@ -57,79 +60,109 @@ import static org.ethereum.vm.OpCode.PUSH1; * Created on: 01/06/2014 10:44 */ public class VM { - + private Logger logger = LoggerFactory.getLogger("VM"); private Logger dumpLogger = LoggerFactory.getLogger("dump"); private static BigInteger _32_ = BigInteger.valueOf(32); - private static String logString = "[ {} ]\t Op: [ {} ]\t Gas: [ {} ]\t Deep: [ {} ] Hint: [ {} ]"; + private static String logString = "[{}]\t Op: [{}] Gas: [{}]\t Deep: [{}] Hint: [{}]"; + + private static BigInteger MAX_GAS = BigInteger.valueOf(Long.MAX_VALUE); public void step(Program program) { try { + OpCode op = OpCode.code(program.getCurrentOp()); + program.setLastOp(op.val()); - byte op = program.getCurrentOp(); - program.setLastOp(op); - - int oldMemSize = program.getMemSize(); + long oldMemSize = program.getMemSize(); + BigInteger newMemSize = BigInteger.ZERO; + Stack stack = program.getStack(); String hint = ""; long gasBefore = program.getGas().longValue(); int stepBefore = program.getPC(); - - if(program.getNumber().intValue() == CONFIG.dumpBlock()) { - - switch (OpCode.code(op)) { - case STOP: case RETURN: case SUICIDE: - - ContractDetails details = program.getResult().getRepository() - .getContractDetails(program.getOwnerAddress().getLast20Bytes()); - List storageKeys = new ArrayList<>(details.getStorage().keySet()); - Collections.sort((List) storageKeys); - - for (DataWord key : storageKeys) { - dumpLogger.info("{} {}", - Hex.toHexString(key.getNoLeadZeroesData()), - Hex.toHexString(details.getStorage().get(key).getNoLeadZeroesData())); - } - default: - break; - } - String addressString = Hex.toHexString(program.getOwnerAddress().getLast20Bytes()); - String pcString = Hex.toHexString(new DataWord(program.getPC()).getNoLeadZeroesData()); - String opString = Hex.toHexString(new byte[]{op}); - String gasString = Hex.toHexString(program.getGas().getNoLeadZeroesData()); - - dumpLogger.info("{} {} {} {}", addressString, pcString, opString, gasString); - } - - - switch (OpCode.code(op)) { - case SHA3: - program.spendGas(GasCost.SHA3, OpCode.code(op).name()); - break; - case SLOAD: - program.spendGas(GasCost.SLOAD, OpCode.code(op).name()); - break; - case BALANCE: - program.spendGas(GasCost.BALANCE, OpCode.code(op).name()); - break; - case CREATE: - program.spendGas(GasCost.CREATE, OpCode.code(op).name()); - break; - case CALL: - program.spendGas(GasCost.CALL, OpCode.code(op).name()); - break; - case SSTORE: case STOP: case SUICIDE: + // Log debugging line for VM + if(program.getNumber().intValue() == CONFIG.dumpBlock()) + this.dumpLine(op.val(), program); + + // Calculate fees and spend gas + switch (op) { + case STOP: case SUICIDE: // The ops that doesn't charged by step, or // charged in the following section break; + case SSTORE: + // for gas calculations [YP 9.2] + DataWord newValue = stack.get(stack.size()-2); + DataWord oldValue = program.storageLoad(stack.peek()); + if (oldValue == null && !newValue.isZero()) { + program.spendGas(GasCost.SSTORE * 2, op.name()); + } else if (oldValue != null && newValue.isZero()) { + program.spendGas(GasCost.SSTORE * 0, op.name()); + } else + program.spendGas(GasCost.SSTORE, op.name()); + break; + case SLOAD: + program.spendGas(GasCost.SLOAD, op.name()); + break; + case BALANCE: + program.spendGas(GasCost.BALANCE, op.name()); + break; + + // These all operate on memory and therefore potentially expand it: + case MSTORE: + newMemSize = stack.peek().value().add(BigInteger.valueOf(32)); + program.spendGas(GasCost.STEP, op.name()); + break; + case MSTORE8: + newMemSize = stack.peek().value().add(BigInteger.ONE); + program.spendGas(GasCost.STEP, op.name()); + break; + case MLOAD: + newMemSize = stack.peek().value().add(BigInteger.valueOf(32)); + program.spendGas(GasCost.STEP, op.name()); + break; + case RETURN: + newMemSize = stack.peek().value().add(stack.get(stack.size()-2).value()); + program.spendGas(GasCost.STEP, op.name()); + break; + case SHA3: + program.spendGas(GasCost.SHA3, op.name()); + newMemSize = stack.peek().value().add(stack.get(stack.size()-2).value()); + break; + case CALLDATACOPY: + newMemSize = stack.peek().value().add(stack.get(stack.size()-3).value()); + program.spendGas(GasCost.STEP, op.name()); + break; + case CODECOPY: + newMemSize = stack.peek().value().add(stack.get(stack.size()-3).value()); + program.spendGas(GasCost.STEP, op.name()); + break; + case CALL: + program.spendGas(GasCost.CALL, op.name()); + BigInteger x = stack.get(stack.size()-6).value().add(stack.get(stack.size()-7).value()); + BigInteger y = stack.get(stack.size()-4).value().add(stack.get(stack.size()-5).value()); + newMemSize = x.max(y); + break; + case CREATE: + program.spendGas(GasCost.CREATE, op.name()); + newMemSize = stack.get(stack.size()-2).value().add(stack.get(stack.size()-3).value()); + break; default: - program.spendGas(GasCost.STEP, OpCode.code(op).name()); + program.spendGas(GasCost.STEP, op.name()); break; } + if(newMemSize.compareTo(MAX_GAS) == 1) { + throw program.new OutOfGasException(); + } + // memory gas calc + long memoryUsage = (newMemSize.longValue() + 31) / 32 * 32; + if (memoryUsage > oldMemSize) + program.spendGas(GasCost.MEMORY * ((memoryUsage - oldMemSize) / 32), op.name() + " (memory usage)"); - switch (OpCode.code(op)) { + // Execute operation + switch (op) { /** * Stop and Arithmetic Operations */ @@ -138,7 +171,7 @@ public class VM { program.stop(); } break; case ADD:{ - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -150,7 +183,7 @@ public class VM { } break; case MUL:{ - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -161,7 +194,7 @@ public class VM { program.step(); } break; case SUB:{ - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -172,7 +205,7 @@ public class VM { program.step(); } break; case DIV:{ - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -183,7 +216,7 @@ public class VM { program.step(); } break; case SDIV:{ - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -194,7 +227,7 @@ public class VM { program.step(); } break; case MOD:{ - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -205,7 +238,7 @@ public class VM { program.step(); } break; case SMOD:{ - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -216,7 +249,7 @@ public class VM { program.step(); } break; case EXP:{ - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -227,7 +260,7 @@ public class VM { program.step(); } break; case NEG:{ - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); word1.negate(); if (logger.isInfoEnabled()) @@ -237,7 +270,7 @@ public class VM { program.step(); } break; case LT:{ - // TODO: can be improved by not using BigInteger + // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -254,7 +287,7 @@ public class VM { program.step(); } break; case SLT:{ - // TODO: can be improved by not using BigInteger + // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -271,7 +304,7 @@ public class VM { program.step(); } break; case SGT:{ - // TODO: can be improved by not using BigInteger + // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -288,7 +321,7 @@ public class VM { program.step(); } break; case GT:{ - // TODO: can be improved by not using BigInteger + // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -305,7 +338,7 @@ public class VM { program.step(); } break; case EQ:{ - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -321,7 +354,7 @@ public class VM { program.step(); } break; case NOT: { - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); if (word1.isZero()) { word1.getData()[31] = 1; } else { @@ -339,7 +372,7 @@ public class VM { * Bitwise Logic Operations */ case AND:{ - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -350,7 +383,7 @@ public class VM { program.step(); } break; case OR: { - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -361,7 +394,7 @@ public class VM { program.step(); } break; case XOR: { - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -372,7 +405,7 @@ public class VM { program.step(); } break; case BYTE:{ - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); DataWord result = null; if (word1.value().compareTo(_32_) == -1) { @@ -395,7 +428,7 @@ public class VM { * SHA3 */ case SHA3:{ - DataWord memOffsetData = program.stackPop(); + DataWord memOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); ByteBuffer buffer = program.memoryChunk(memOffsetData, lengthData); @@ -422,7 +455,7 @@ public class VM { program.step(); } break; case BALANCE:{ - DataWord address = program.stackPop(); + DataWord address = program.stackPop(); DataWord balance = program.getBalance(address); if (logger.isInfoEnabled()) @@ -461,7 +494,7 @@ public class VM { program.step(); } break; case CALLDATALOAD:{ - DataWord dataOffs = program.stackPop(); + DataWord dataOffs = program.stackPop(); DataWord value = program.getDataValue(dataOffs); if (logger.isInfoEnabled()) @@ -483,7 +516,7 @@ public class VM { DataWord memOffsetData = program.stackPop(); DataWord dataOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); - + byte[] msgData = program.getDataCopy(dataOffsetData, lengthData); if (logger.isInfoEnabled()) @@ -591,25 +624,25 @@ public class VM { program.step(); } break; case POP:{ - program.stackPop(); + program.stackPop(); program.step(); } break; case DUP:{ - DataWord word_1 = program.stackPop(); + DataWord word_1 = program.stackPop(); DataWord word_2 = word_1.clone(); program.stackPush(word_1); program.stackPush(word_2); program.step(); } break; case SWAP:{ - DataWord word_1 = program.stackPop(); + DataWord word_1 = program.stackPop(); DataWord word_2 = program.stackPop(); program.stackPush(word_1); program.stackPush(word_2); program.step(); } break; case MLOAD:{ - DataWord addr = program.stackPop(); + DataWord addr = program.stackPop(); DataWord data = program.memoryLoad(addr); if (logger.isInfoEnabled()) @@ -619,7 +652,7 @@ public class VM { program.step(); } break; case MSTORE:{ - DataWord addr = program.stackPop(); + DataWord addr = program.stackPop(); DataWord value = program.stackPop(); if (logger.isInfoEnabled()) @@ -629,14 +662,14 @@ public class VM { program.step(); } break; case MSTORE8:{ - DataWord addr = program.stackPop(); + DataWord addr = program.stackPop(); DataWord value = program.stackPop(); byte[] byteVal = {value.getData()[31]}; program.memorySave(addr.getData(), byteVal); program.step(); } break; case SLOAD:{ - DataWord key = program.stackPop(); + DataWord key = program.stackPop(); DataWord val = program.storageLoad(key); if (logger.isInfoEnabled()) @@ -649,25 +682,17 @@ public class VM { program.step(); } break; case SSTORE:{ - DataWord addr = program.stackPop(); + DataWord addr = program.stackPop(); DataWord value = program.stackPop(); if (logger.isInfoEnabled()) hint = "addr: " + addr + " value: " + value; - // for gas calculations [YP 9.2] - DataWord oldValue = program.storageLoad(addr); program.storageSave(addr, value); - if (oldValue == null && !value.isZero()) { - program.spendGas(GasCost.SSTORE * 2, OpCode.code(op).name()); - } else if (oldValue != null && value.isZero()) { - program.spendGas(GasCost.SSTORE * 0, OpCode.code(op).name()); - } else - program.spendGas(GasCost.SSTORE, OpCode.code(op).name()); program.step(); } break; case JUMP:{ - DataWord pos = program.stackPop(); + DataWord pos = program.stackPop(); if (logger.isInfoEnabled()) hint = "~> " + pos.value(); @@ -675,7 +700,7 @@ public class VM { program.setPC(pos); } break; case JUMPI:{ - DataWord pos = program.stackPop(); + DataWord pos = program.stackPop(); DataWord cond = program.stackPop(); if (!cond.isZero()) { @@ -723,7 +748,7 @@ public class VM { case PUSH17: case PUSH18: case PUSH19: case PUSH20: case PUSH21: case PUSH22: case PUSH23: case PUSH24: case PUSH25: case PUSH26: case PUSH27: case PUSH28: case PUSH29: case PUSH30: case PUSH31: case PUSH32:{ program.step(); - int nPush = op - PUSH1.val() + 1; + int nPush = op.val() - PUSH1.val() + 1; byte[] data = program.sweep(nPush); hint = "" + Hex.toHexString(data); @@ -731,21 +756,21 @@ public class VM { program.stackPush(data); } break; case CREATE:{ - DataWord value = program.stackPop(); + DataWord value = program.stackPop(); DataWord inOffset = program.stackPop(); DataWord inSize = program.stackPop(); if (logger.isInfoEnabled()) - logger.info(logString, program.getPC(), OpCode.code(op) - .name(), program.getGas().value(), - program.invokeData.getCallDeep(), hint); + logger.info(logString, program.getPC(), op.name(), + program.getGas().value(), + program.invokeData.getCallDeep(), hint); program.createContract(value, inOffset, inSize); program.step(); } break; case CALL:{ - DataWord gas = program.stackPop(); + DataWord gas = program.stackPop(); DataWord toAddress = program.stackPop(); DataWord value = program.stackPop(); @@ -756,8 +781,8 @@ public class VM { DataWord outDataSize = program.stackPop(); if (logger.isInfoEnabled()) - logger.info(logString, program.getPC(), OpCode.code(op) - .name(), program.getGas().value(), + logger.info(logString, program.getPC(), op.name(), + program.getGas().value(), program.invokeData.getCallDeep(), hint); program.callToAddress(gas, toAddress, value, inDataOffs, inDataSize, outDataOffs, outDataSize); @@ -765,7 +790,7 @@ public class VM { program.step(); } break; case RETURN:{ - DataWord offset = program.stackPop(); + DataWord offset = program.stackPop(); DataWord size = program.stackPop(); ByteBuffer hReturn = program.memoryChunk(offset, size); @@ -778,7 +803,7 @@ public class VM { program.stop(); } break; case SUICIDE:{ - DataWord address = program.stackPop(); + DataWord address = program.stackPop(); program.suicide(address); if (logger.isInfoEnabled()) @@ -790,25 +815,18 @@ public class VM { } } - if (logger.isInfoEnabled() && !OpCode.code(op).equals(CALL) - && !OpCode.code(op).equals(CREATE)) - logger.info(logString, stepBefore, OpCode.code(op).name(), - gasBefore, program.invokeData.getCallDeep(), hint); - - // memory gas calc - int newMemSize = program.getMemSize(); - int memoryUsage = (newMemSize - oldMemSize) /32; - - if (memoryUsage > 0) - program.spendGas(GasCost.MEMORY * memoryUsage, OpCode.code(op).name() + " (memory usage)"); + if (logger.isInfoEnabled() && !op.equals(CALL) + && !op.equals(CREATE)) + logger.info(logString, stepBefore, String.format("%-12s", op.name()), gasBefore, + program.invokeData.getCallDeep(), hint); // program.fullTrace(); } catch (RuntimeException e) { - program.stop(); if(e instanceof OutOfGasException) logger.warn("OutOfGasException occurred", e); else logger.error("VM halted", e); + program.stop(); throw e; } } @@ -830,4 +848,29 @@ public class VM { program.setRuntimeFailure(e); } } + + private void dumpLine(byte op, Program program) { + switch (OpCode.code(op)) { + case STOP: case RETURN: case SUICIDE: + + ContractDetails details = program.getResult().getRepository() + .getContractDetails(program.getOwnerAddress().getLast20Bytes()); + List storageKeys = new ArrayList<>(details.getStorage().keySet()); + Collections.sort((List) storageKeys); + + for (DataWord key : storageKeys) { + dumpLogger.info("{} {}", + Hex.toHexString(key.getNoLeadZeroesData()), + Hex.toHexString(details.getStorage().get(key).getNoLeadZeroesData())); + } + default: + break; + } + String addressString = Hex.toHexString(program.getOwnerAddress().getLast20Bytes()); + String pcString = Hex.toHexString(new DataWord(program.getPC()).getNoLeadZeroesData()); + String opString = Hex.toHexString(new byte[]{op}); + String gasString = Hex.toHexString(program.getGas().getNoLeadZeroesData()); + + dumpLogger.info("{} {} {} {}", addressString, pcString, opString, gasString); + } } diff --git a/ethereumj-core/src/test/java/org/ethereum/vm/VMTest.java b/ethereumj-core/src/test/java/org/ethereum/vm/VMTest.java index 520d96fc..bb8b8c41 100644 --- a/ethereumj-core/src/test/java/org/ethereum/vm/VMTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/vm/VMTest.java @@ -1,6 +1,7 @@ package org.ethereum.vm; import org.ethereum.db.Repository; +import org.ethereum.vm.Program.OutOfGasException; import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; @@ -2662,6 +2663,26 @@ public class VMTest { } } + @Test(expected=OutOfGasException.class) // CALLDATACOPY OP mal + public void testCALLDATACOPY_7() { + + VM vm = new VM(); + Program program = + new Program(Hex.decode("6020600073CC0929EB16730E7C14FEFC63006AC2D794C5795637"), + createProgramInvoke_1()); + + try { + vm.step(program); + vm.step(program); + vm.step(program); + vm.step(program); + fail(); + } finally { + program.getResult().getRepository().close(); + assertTrue(program.isStopped()); + } + } + @Test // ADDRESS OP public void testADDRESS_1() {