From 6281a752b4c9fde2993b1b80cafd9d2a62517036 Mon Sep 17 00:00:00 2001 From: nicksavers Date: Thu, 21 Aug 2014 07:51:19 +0200 Subject: [PATCH 01/12] Calculate gasfee in advance --- .../java/org/ethereum/core/Blockchain.java | 6 + .../main/java/org/ethereum/vm/Program.java | 18 ++- .../java/org/ethereum/vm/ProgramResult.java | 2 +- .../src/main/java/org/ethereum/vm/VM.java | 145 +++++++++++------- 4 files changed, 108 insertions(+), 63 deletions(-) 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..1e749d32 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java @@ -6,6 +6,7 @@ import org.ethereum.manager.WorldManager; import org.ethereum.net.BlockQueue; import org.ethereum.util.AdvancedDeviceUtils; import org.ethereum.vm.*; +import org.ethereum.vm.Program.OutOfGasException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; @@ -115,6 +116,9 @@ public class Blockchain { if (block == null) return; + + if (block.getNumber() == 1984) + logger.debug("Block #1984"); // if it is the first block to add // make sure the parent is genesis @@ -365,6 +369,8 @@ public class Blockchain { } } catch (RuntimeException e) { trackRepository.rollback(); + if(e instanceof OutOfGasException) + return gasDebit.longValue(); return 0; } trackRepository.commit(); 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..ba58c73d 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/Program.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/Program.java @@ -49,7 +49,6 @@ public class Program { this.invokeData = invokeData; this.ops = ops; this.programAddress = invokeData.getOwnerAddress(); - } public byte getCurrentOp() { @@ -78,6 +77,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 +117,6 @@ public class Program { } public void step() { - ++pc; if (pc >= ops.length) stop(); } @@ -140,6 +142,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; @@ -269,7 +278,6 @@ public class Program { result.getRepository().addBalance(senderAddress, endowment.negate()); result.getRepository().addBalance(newAddress, endowment); - Repository trackRepository = result.getRepository().getTrack(); trackRepository.startTracking(); @@ -435,12 +443,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 +554,6 @@ public class Program { return invokeData.getGaslimit().clone(); } - public ProgramResult getResult() { return result; } 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..f0e59ea6 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; @@ -70,65 +73,85 @@ public class VM { byte op = program.getCurrentOp(); program.setLastOp(op); - int oldMemSize = program.getMemSize(); + long oldMemSize = program.getMemSize(); + long newMemSize = oldMemSize; + 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); - } - + // Log debugging line for VM + if(program.getNumber().intValue() == CONFIG.dumpBlock()) + this.dumpLine(op, program); + + // Calculate fees and memory 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: + 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, OpCode.code(op).name()); + } else if (oldValue != null && newValue.isZero()) { + program.spendGas(GasCost.SSTORE * 0, OpCode.code(op).name()); + } else + program.spendGas(GasCost.SSTORE, OpCode.code(op).name()); + break; + case MSTORE: + newMemSize = stack.peek().longValue() + 32; + break; + case MLOAD: + newMemSize = stack.peek().longValue() + 32; + break; + case MSTORE8: + newMemSize = stack.peek().longValue() + 1; + break; + case RETURN: + newMemSize = stack.peek().longValue() + stack.get(stack.size()-2).longValue(); + break; + case SHA3: + program.spendGas(GasCost.SHA3, OpCode.code(op).name()); + newMemSize = stack.peek().longValue() + stack.get(stack.size()-2).longValue(); + break; + case CALLDATACOPY: + newMemSize = stack.peek().longValue() + stack.get(stack.size()-3).longValue(); + break; + case CODECOPY: + newMemSize = stack.peek().longValue() + stack.get(stack.size()-3).longValue(); + break; + case CALL: + program.spendGas(GasCost.CALL + stack.get(stack.size()-1).longValue(), OpCode.code(op).name()); + long x = stack.get(stack.size()-6).longValue() + stack.get(stack.size()-7).longValue(); + long y = stack.get(stack.size()-4).longValue() + stack.get(stack.size()-5).longValue(); + newMemSize = Math.max(x, y); + break; + case CREATE: + program.spendGas(GasCost.CREATE, OpCode.code(op).name()); + newMemSize = stack.get(stack.size()-2).longValue() + stack.get(stack.size()-3).longValue(); + break; default: - program.spendGas(GasCost.STEP, OpCode.code(op).name()); +// program.spendGas(GasCost.STEP, OpCode.code(op).name()); break; } + + // memory gas calc + long memoryUsage = (newMemSize + 31) / 32 * 32; +// long memoryUsage = (newMemSize - oldMemSize) / 32; + if (memoryUsage > oldMemSize) + program.spendGas(GasCost.MEMORY * ((memoryUsage-oldMemSize)/32), OpCode.code(op).name() + " (memory usage)"); + // Execute operation switch (OpCode.code(op)) { /** * Stop and Arithmetic Operations @@ -655,15 +678,7 @@ public class VM { 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:{ @@ -794,21 +809,14 @@ public class VM { && !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)"); // 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 +838,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); + } } From e592e24eb2715561aceecbc0d9592224508c98da Mon Sep 17 00:00:00 2001 From: nicksavers Date: Thu, 21 Aug 2014 14:27:51 +0200 Subject: [PATCH 02/12] Add basic gas step cost to mem exapnding operations and use enum directly --- .../src/main/java/org/ethereum/vm/VM.java | 132 +++++++++++++----- 1 file changed, 97 insertions(+), 35 deletions(-) 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 f0e59ea6..7a0c0028 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java @@ -69,12 +69,11 @@ public class VM { public void step(Program program) { try { - - byte op = program.getCurrentOp(); - program.setLastOp(op); + OpCode op = OpCode.code(program.getCurrentOp()); + program.setLastOp(op.val()); long oldMemSize = program.getMemSize(); - long newMemSize = oldMemSize; + long newMemSize = 0; Stack stack = program.getStack(); String hint = ""; @@ -83,65 +82,83 @@ public class VM { // Log debugging line for VM if(program.getNumber().intValue() == CONFIG.dumpBlock()) - this.dumpLine(op, program); + this.dumpLine(op.val(), program); - // Calculate fees and memory - switch (OpCode.code(op)) { - case SLOAD: - program.spendGas(GasCost.SLOAD, OpCode.code(op).name()); - break; - case BALANCE: - program.spendGas(GasCost.BALANCE, OpCode.code(op).name()); - break; + // 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: + assert(stack.size() == 2); // 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, OpCode.code(op).name()); + program.spendGas(GasCost.SSTORE * 2, op.name()); } else if (oldValue != null && newValue.isZero()) { - program.spendGas(GasCost.SSTORE * 0, OpCode.code(op).name()); + program.spendGas(GasCost.SSTORE * 0, op.name()); } else - program.spendGas(GasCost.SSTORE, OpCode.code(op).name()); + 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: + assert(stack.size() == 2); newMemSize = stack.peek().longValue() + 32; - break; - case MLOAD: - newMemSize = stack.peek().longValue() + 32; + program.spendGas(GasCost.STEP, op.name()); break; case MSTORE8: + assert(stack.size() == 2); newMemSize = stack.peek().longValue() + 1; + program.spendGas(GasCost.STEP, op.name()); + break; + case MLOAD: + assert(stack.size() == 1); + newMemSize = stack.peek().longValue() + 32; + program.spendGas(GasCost.STEP, op.name()); break; case RETURN: + assert(stack.size() == 2); newMemSize = stack.peek().longValue() + stack.get(stack.size()-2).longValue(); + program.spendGas(GasCost.STEP, op.name()); break; case SHA3: - program.spendGas(GasCost.SHA3, OpCode.code(op).name()); + assert(stack.size() == 2); + program.spendGas(GasCost.SHA3, op.name()); newMemSize = stack.peek().longValue() + stack.get(stack.size()-2).longValue(); break; case CALLDATACOPY: + assert(stack.size() == 3); newMemSize = stack.peek().longValue() + stack.get(stack.size()-3).longValue(); + program.spendGas(GasCost.STEP, op.name()); break; case CODECOPY: + assert(stack.size() == 3); newMemSize = stack.peek().longValue() + stack.get(stack.size()-3).longValue(); + program.spendGas(GasCost.STEP, op.name()); break; case CALL: - program.spendGas(GasCost.CALL + stack.get(stack.size()-1).longValue(), OpCode.code(op).name()); + assert(stack.size() == 7); + program.spendGas(GasCost.CALL + stack.get(stack.size()-1).longValue(), op.name()); long x = stack.get(stack.size()-6).longValue() + stack.get(stack.size()-7).longValue(); long y = stack.get(stack.size()-4).longValue() + stack.get(stack.size()-5).longValue(); newMemSize = Math.max(x, y); break; case CREATE: - program.spendGas(GasCost.CREATE, OpCode.code(op).name()); + assert(stack.size() == 3); + program.spendGas(GasCost.CREATE, op.name()); newMemSize = stack.get(stack.size()-2).longValue() + stack.get(stack.size()-3).longValue(); break; default: -// program.spendGas(GasCost.STEP, OpCode.code(op).name()); + program.spendGas(GasCost.STEP, op.name()); break; } @@ -149,10 +166,10 @@ public class VM { long memoryUsage = (newMemSize + 31) / 32 * 32; // long memoryUsage = (newMemSize - oldMemSize) / 32; if (memoryUsage > oldMemSize) - program.spendGas(GasCost.MEMORY * ((memoryUsage-oldMemSize)/32), OpCode.code(op).name() + " (memory usage)"); + program.spendGas(GasCost.MEMORY * ((memoryUsage-oldMemSize)/32), op.name() + " (memory usage)"); // Execute operation - switch (OpCode.code(op)) { + switch (op) { /** * Stop and Arithmetic Operations */ @@ -161,6 +178,7 @@ public class VM { program.stop(); } break; case ADD:{ + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -173,6 +191,7 @@ public class VM { } break; case MUL:{ + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -184,6 +203,7 @@ public class VM { program.step(); } break; case SUB:{ + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -195,6 +215,7 @@ public class VM { program.step(); } break; case DIV:{ + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -206,6 +227,7 @@ public class VM { program.step(); } break; case SDIV:{ + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -217,6 +239,7 @@ public class VM { program.step(); } break; case MOD:{ + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -228,6 +251,7 @@ public class VM { program.step(); } break; case SMOD:{ + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -239,6 +263,7 @@ public class VM { program.step(); } break; case EXP:{ + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -250,6 +275,7 @@ public class VM { program.step(); } break; case NEG:{ + assert(stack.size() == 1); DataWord word1 = program.stackPop(); word1.negate(); @@ -260,6 +286,7 @@ public class VM { program.step(); } break; case LT:{ + assert(stack.size() == 2); // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -277,6 +304,7 @@ public class VM { program.step(); } break; case SLT:{ + assert(stack.size() == 2); // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -294,6 +322,7 @@ public class VM { program.step(); } break; case SGT:{ + assert(stack.size() == 2); // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -311,6 +340,7 @@ public class VM { program.step(); } break; case GT:{ + assert(stack.size() == 2); // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -328,6 +358,7 @@ public class VM { program.step(); } break; case EQ:{ + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -344,6 +375,7 @@ public class VM { program.step(); } break; case NOT: { + assert(stack.size() == 1); DataWord word1 = program.stackPop(); if (word1.isZero()) { word1.getData()[31] = 1; @@ -362,6 +394,7 @@ public class VM { * Bitwise Logic Operations */ case AND:{ + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -373,6 +406,7 @@ public class VM { program.step(); } break; case OR: { + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -384,6 +418,7 @@ public class VM { program.step(); } break; case XOR: { + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -395,6 +430,7 @@ public class VM { program.step(); } break; case BYTE:{ + assert(stack.size() == 2); DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); DataWord result = null; @@ -418,6 +454,7 @@ public class VM { * SHA3 */ case SHA3:{ + assert(stack.size() == 2); DataWord memOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); ByteBuffer buffer = program.memoryChunk(memOffsetData, lengthData); @@ -445,6 +482,7 @@ public class VM { program.step(); } break; case BALANCE:{ + assert(stack.size() == 1); DataWord address = program.stackPop(); DataWord balance = program.getBalance(address); @@ -484,6 +522,7 @@ public class VM { program.step(); } break; case CALLDATALOAD:{ + assert(stack.size() == 1); DataWord dataOffs = program.stackPop(); DataWord value = program.getDataValue(dataOffs); @@ -503,10 +542,18 @@ public class VM { program.step(); } break; case CALLDATACOPY:{ + assert(stack.size() == 3); DataWord memOffsetData = program.stackPop(); DataWord dataOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); + // gas calculation + long price = GasCost.MEMORY * lengthData.value().intValue(); + System.out.println("Spneding "+price+" gas " ); + if( price < 0 ) // special case because of BigInteger to int conversion + price = Long.MAX_VALUE; + program.spendGas(price, op.name()); + byte[] msgData = program.getDataCopy(dataOffsetData, lengthData); if (logger.isInfoEnabled()) @@ -525,6 +572,7 @@ public class VM { program.step(); } break; case CODECOPY:{ + assert(stack.size() == 3); DataWord memOffsetData = program.stackPop(); DataWord codeOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); @@ -614,10 +662,12 @@ public class VM { program.step(); } break; case POP:{ + assert(stack.size() == 1); program.stackPop(); program.step(); } break; case DUP:{ + assert(stack.size() == 1); DataWord word_1 = program.stackPop(); DataWord word_2 = word_1.clone(); program.stackPush(word_1); @@ -625,6 +675,7 @@ public class VM { program.step(); } break; case SWAP:{ + assert(stack.size() == 2); DataWord word_1 = program.stackPop(); DataWord word_2 = program.stackPop(); program.stackPush(word_1); @@ -632,6 +683,7 @@ public class VM { program.step(); } break; case MLOAD:{ + assert(stack.size() == 1); DataWord addr = program.stackPop(); DataWord data = program.memoryLoad(addr); @@ -642,6 +694,7 @@ public class VM { program.step(); } break; case MSTORE:{ + assert(stack.size() == 2); DataWord addr = program.stackPop(); DataWord value = program.stackPop(); @@ -652,6 +705,7 @@ public class VM { program.step(); } break; case MSTORE8:{ + assert(stack.size() == 2); DataWord addr = program.stackPop(); DataWord value = program.stackPop(); byte[] byteVal = {value.getData()[31]}; @@ -659,6 +713,7 @@ public class VM { program.step(); } break; case SLOAD:{ + assert(stack.size() == 1); DataWord key = program.stackPop(); DataWord val = program.storageLoad(key); @@ -672,6 +727,7 @@ public class VM { program.step(); } break; case SSTORE:{ + assert(stack.size() == 2); DataWord addr = program.stackPop(); DataWord value = program.stackPop(); @@ -682,6 +738,7 @@ public class VM { program.step(); } break; case JUMP:{ + assert(stack.size() == 1); DataWord pos = program.stackPop(); if (logger.isInfoEnabled()) @@ -690,6 +747,7 @@ public class VM { program.setPC(pos); } break; case JUMPI:{ + assert(stack.size() == 2); DataWord pos = program.stackPop(); DataWord cond = program.stackPop(); @@ -738,7 +796,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); @@ -746,20 +804,22 @@ public class VM { program.stackPush(data); } break; case CREATE:{ + assert(stack.size() == 3); 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:{ + assert(stack.size() == 7); DataWord gas = program.stackPop(); DataWord toAddress = program.stackPop(); DataWord value = program.stackPop(); @@ -771,8 +831,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); @@ -780,6 +840,7 @@ public class VM { program.step(); } break; case RETURN:{ + assert(stack.size() == 2); DataWord offset = program.stackPop(); DataWord size = program.stackPop(); @@ -793,6 +854,7 @@ public class VM { program.stop(); } break; case SUICIDE:{ + assert(stack.size() == 1); DataWord address = program.stackPop(); program.suicide(address); @@ -805,10 +867,10 @@ 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); + if (logger.isInfoEnabled() && !op.equals(CALL) + && !op.equals(CREATE)) + logger.info(logString, stepBefore, op.name(), gasBefore, + program.invokeData.getCallDeep(), hint); // program.fullTrace(); } catch (RuntimeException e) { From 334f4ceb20f1c1430607326ea34dbc9dbaaf1d51 Mon Sep 17 00:00:00 2001 From: nicksavers Date: Thu, 21 Aug 2014 16:12:02 +0200 Subject: [PATCH 03/12] Don't spend reserved gas --- ethereumj-core/src/main/java/org/ethereum/vm/VM.java | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) 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 7a0c0028..af65e76b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java @@ -147,7 +147,7 @@ public class VM { break; case CALL: assert(stack.size() == 7); - program.spendGas(GasCost.CALL + stack.get(stack.size()-1).longValue(), op.name()); + program.spendGas(GasCost.CALL, op.name()); long x = stack.get(stack.size()-6).longValue() + stack.get(stack.size()-7).longValue(); long y = stack.get(stack.size()-4).longValue() + stack.get(stack.size()-5).longValue(); newMemSize = Math.max(x, y); @@ -546,13 +546,6 @@ public class VM { DataWord memOffsetData = program.stackPop(); DataWord dataOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); - - // gas calculation - long price = GasCost.MEMORY * lengthData.value().intValue(); - System.out.println("Spneding "+price+" gas " ); - if( price < 0 ) // special case because of BigInteger to int conversion - price = Long.MAX_VALUE; - program.spendGas(price, op.name()); byte[] msgData = program.getDataCopy(dataOffsetData, lengthData); From 88df96b7cc08ac4b5880b518d454296e62f4bf65 Mon Sep 17 00:00:00 2001 From: nicksavers Date: Thu, 21 Aug 2014 16:40:54 +0200 Subject: [PATCH 04/12] Use BigInt for gas calculation instead of long --- .../src/main/java/org/ethereum/vm/VM.java | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) 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 af65e76b..fcfa87f8 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java @@ -66,6 +66,8 @@ public class VM { private static BigInteger _32_ = BigInteger.valueOf(32); private static String logString = "[ {} ]\t Op: [ {} ]\t Gas: [ {} ]\t Deep: [ {} ] Hint: [ {} ]"; + private static BigInteger MAX_GAS = BigInteger.valueOf(Long.MAX_VALUE); + public void step(Program program) { try { @@ -73,7 +75,7 @@ public class VM { program.setLastOp(op.val()); long oldMemSize = program.getMemSize(); - long newMemSize = 0; + BigInteger newMemSize = BigInteger.ZERO; Stack stack = program.getStack(); String hint = ""; @@ -112,58 +114,60 @@ public class VM { // These all operate on memory and therefore potentially expand it: case MSTORE: assert(stack.size() == 2); - newMemSize = stack.peek().longValue() + 32; + newMemSize = stack.peek().value().add(BigInteger.valueOf(32)); program.spendGas(GasCost.STEP, op.name()); break; case MSTORE8: assert(stack.size() == 2); - newMemSize = stack.peek().longValue() + 1; + newMemSize = stack.peek().value().add(BigInteger.ONE); program.spendGas(GasCost.STEP, op.name()); break; case MLOAD: assert(stack.size() == 1); - newMemSize = stack.peek().longValue() + 32; + newMemSize = stack.peek().value().add(BigInteger.valueOf(32)); program.spendGas(GasCost.STEP, op.name()); break; case RETURN: assert(stack.size() == 2); - newMemSize = stack.peek().longValue() + stack.get(stack.size()-2).longValue(); + newMemSize = stack.peek().value().add(stack.get(stack.size()-2).value()); program.spendGas(GasCost.STEP, op.name()); break; case SHA3: assert(stack.size() == 2); program.spendGas(GasCost.SHA3, op.name()); - newMemSize = stack.peek().longValue() + stack.get(stack.size()-2).longValue(); + newMemSize = stack.peek().value().add(stack.get(stack.size()-2).value()); break; case CALLDATACOPY: assert(stack.size() == 3); - newMemSize = stack.peek().longValue() + stack.get(stack.size()-3).longValue(); + newMemSize = stack.peek().value().add(stack.get(stack.size()-3).value()); program.spendGas(GasCost.STEP, op.name()); break; case CODECOPY: assert(stack.size() == 3); - newMemSize = stack.peek().longValue() + stack.get(stack.size()-3).longValue(); + newMemSize = stack.peek().value().add(stack.get(stack.size()-3).value()); program.spendGas(GasCost.STEP, op.name()); break; case CALL: assert(stack.size() == 7); program.spendGas(GasCost.CALL, op.name()); - long x = stack.get(stack.size()-6).longValue() + stack.get(stack.size()-7).longValue(); - long y = stack.get(stack.size()-4).longValue() + stack.get(stack.size()-5).longValue(); - newMemSize = Math.max(x, y); + 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: assert(stack.size() == 3); program.spendGas(GasCost.CREATE, op.name()); - newMemSize = stack.get(stack.size()-2).longValue() + stack.get(stack.size()-3).longValue(); + newMemSize = stack.get(stack.size()-2).value().add(stack.get(stack.size()-3).value()); break; default: program.spendGas(GasCost.STEP, op.name()); break; } - + if(newMemSize.compareTo(MAX_GAS) == 1) { + throw program.new OutOfGasException(); + } // memory gas calc - long memoryUsage = (newMemSize + 31) / 32 * 32; + long memoryUsage = (newMemSize.longValue() + 31) / 32 * 32; // long memoryUsage = (newMemSize - oldMemSize) / 32; if (memoryUsage > oldMemSize) program.spendGas(GasCost.MEMORY * ((memoryUsage-oldMemSize)/32), op.name() + " (memory usage)"); From e7bab2a8ee68660a4c87e1c32617e822f6c72f7b Mon Sep 17 00:00:00 2001 From: nicksavers Date: Thu, 21 Aug 2014 18:20:49 +0200 Subject: [PATCH 05/12] Remove asserts --- .../src/main/java/org/ethereum/vm/VM.java | 127 ++++++------------ 1 file changed, 39 insertions(+), 88 deletions(-) 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 fcfa87f8..ecfcde12 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java @@ -60,7 +60,7 @@ 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); @@ -93,8 +93,7 @@ public class VM { // charged in the following section break; case SSTORE: - assert(stack.size() == 2); - // for gas calculations [YP 9.2] + // for gas calculations [YP 9.2] DataWord newValue = stack.get(stack.size()-2); DataWord oldValue = program.storageLoad(stack.peek()); if (oldValue == null && !newValue.isZero()) { @@ -113,49 +112,40 @@ public class VM { // These all operate on memory and therefore potentially expand it: case MSTORE: - assert(stack.size() == 2); newMemSize = stack.peek().value().add(BigInteger.valueOf(32)); program.spendGas(GasCost.STEP, op.name()); break; case MSTORE8: - assert(stack.size() == 2); newMemSize = stack.peek().value().add(BigInteger.ONE); program.spendGas(GasCost.STEP, op.name()); break; case MLOAD: - assert(stack.size() == 1); newMemSize = stack.peek().value().add(BigInteger.valueOf(32)); program.spendGas(GasCost.STEP, op.name()); break; case RETURN: - assert(stack.size() == 2); newMemSize = stack.peek().value().add(stack.get(stack.size()-2).value()); program.spendGas(GasCost.STEP, op.name()); break; case SHA3: - assert(stack.size() == 2); program.spendGas(GasCost.SHA3, op.name()); newMemSize = stack.peek().value().add(stack.get(stack.size()-2).value()); break; case CALLDATACOPY: - assert(stack.size() == 3); newMemSize = stack.peek().value().add(stack.get(stack.size()-3).value()); program.spendGas(GasCost.STEP, op.name()); break; case CODECOPY: - assert(stack.size() == 3); newMemSize = stack.peek().value().add(stack.get(stack.size()-3).value()); program.spendGas(GasCost.STEP, op.name()); break; case CALL: - assert(stack.size() == 7); 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: - assert(stack.size() == 3); program.spendGas(GasCost.CREATE, op.name()); newMemSize = stack.get(stack.size()-2).value().add(stack.get(stack.size()-3).value()); break; @@ -168,9 +158,8 @@ public class VM { } // memory gas calc long memoryUsage = (newMemSize.longValue() + 31) / 32 * 32; -// long memoryUsage = (newMemSize - oldMemSize) / 32; if (memoryUsage > oldMemSize) - program.spendGas(GasCost.MEMORY * ((memoryUsage-oldMemSize)/32), op.name() + " (memory usage)"); + program.spendGas(GasCost.MEMORY * ((memoryUsage - oldMemSize) / 32), op.name() + " (memory usage)"); // Execute operation switch (op) { @@ -182,8 +171,7 @@ public class VM { program.stop(); } break; case ADD:{ - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -195,8 +183,7 @@ public class VM { } break; case MUL:{ - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -207,8 +194,7 @@ public class VM { program.step(); } break; case SUB:{ - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -219,8 +205,7 @@ public class VM { program.step(); } break; case DIV:{ - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -231,8 +216,7 @@ public class VM { program.step(); } break; case SDIV:{ - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -243,8 +227,7 @@ public class VM { program.step(); } break; case MOD:{ - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -255,8 +238,7 @@ public class VM { program.step(); } break; case SMOD:{ - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -267,8 +249,7 @@ public class VM { program.step(); } break; case EXP:{ - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -279,8 +260,7 @@ public class VM { program.step(); } break; case NEG:{ - assert(stack.size() == 1); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); word1.negate(); if (logger.isInfoEnabled()) @@ -290,8 +270,7 @@ public class VM { program.step(); } break; case LT:{ - assert(stack.size() == 2); - // TODO: can be improved by not using BigInteger + // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -308,8 +287,7 @@ public class VM { program.step(); } break; case SLT:{ - assert(stack.size() == 2); - // TODO: can be improved by not using BigInteger + // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -326,8 +304,7 @@ public class VM { program.step(); } break; case SGT:{ - assert(stack.size() == 2); - // TODO: can be improved by not using BigInteger + // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -344,8 +321,7 @@ public class VM { program.step(); } break; case GT:{ - assert(stack.size() == 2); - // TODO: can be improved by not using BigInteger + // TODO: can be improved by not using BigInteger DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); @@ -362,8 +338,7 @@ public class VM { program.step(); } break; case EQ:{ - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -379,8 +354,7 @@ public class VM { program.step(); } break; case NOT: { - assert(stack.size() == 1); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); if (word1.isZero()) { word1.getData()[31] = 1; } else { @@ -398,8 +372,7 @@ public class VM { * Bitwise Logic Operations */ case AND:{ - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -410,8 +383,7 @@ public class VM { program.step(); } break; case OR: { - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -422,8 +394,7 @@ public class VM { program.step(); } break; case XOR: { - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); if (logger.isInfoEnabled()) @@ -434,8 +405,7 @@ public class VM { program.step(); } break; case BYTE:{ - assert(stack.size() == 2); - DataWord word1 = program.stackPop(); + DataWord word1 = program.stackPop(); DataWord word2 = program.stackPop(); DataWord result = null; if (word1.value().compareTo(_32_) == -1) { @@ -458,8 +428,7 @@ public class VM { * SHA3 */ case SHA3:{ - assert(stack.size() == 2); - DataWord memOffsetData = program.stackPop(); + DataWord memOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); ByteBuffer buffer = program.memoryChunk(memOffsetData, lengthData); @@ -486,8 +455,7 @@ public class VM { program.step(); } break; case BALANCE:{ - assert(stack.size() == 1); - DataWord address = program.stackPop(); + DataWord address = program.stackPop(); DataWord balance = program.getBalance(address); if (logger.isInfoEnabled()) @@ -526,8 +494,7 @@ public class VM { program.step(); } break; case CALLDATALOAD:{ - assert(stack.size() == 1); - DataWord dataOffs = program.stackPop(); + DataWord dataOffs = program.stackPop(); DataWord value = program.getDataValue(dataOffs); if (logger.isInfoEnabled()) @@ -546,7 +513,6 @@ public class VM { program.step(); } break; case CALLDATACOPY:{ - assert(stack.size() == 3); DataWord memOffsetData = program.stackPop(); DataWord dataOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); @@ -569,7 +535,6 @@ public class VM { program.step(); } break; case CODECOPY:{ - assert(stack.size() == 3); DataWord memOffsetData = program.stackPop(); DataWord codeOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); @@ -659,29 +624,25 @@ public class VM { program.step(); } break; case POP:{ - assert(stack.size() == 1); - program.stackPop(); + program.stackPop(); program.step(); } break; case DUP:{ - assert(stack.size() == 1); - 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:{ - assert(stack.size() == 2); - 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:{ - assert(stack.size() == 1); - DataWord addr = program.stackPop(); + DataWord addr = program.stackPop(); DataWord data = program.memoryLoad(addr); if (logger.isInfoEnabled()) @@ -691,8 +652,7 @@ public class VM { program.step(); } break; case MSTORE:{ - assert(stack.size() == 2); - DataWord addr = program.stackPop(); + DataWord addr = program.stackPop(); DataWord value = program.stackPop(); if (logger.isInfoEnabled()) @@ -702,16 +662,14 @@ public class VM { program.step(); } break; case MSTORE8:{ - assert(stack.size() == 2); - 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:{ - assert(stack.size() == 1); - DataWord key = program.stackPop(); + DataWord key = program.stackPop(); DataWord val = program.storageLoad(key); if (logger.isInfoEnabled()) @@ -724,8 +682,7 @@ public class VM { program.step(); } break; case SSTORE:{ - assert(stack.size() == 2); - DataWord addr = program.stackPop(); + DataWord addr = program.stackPop(); DataWord value = program.stackPop(); if (logger.isInfoEnabled()) @@ -735,8 +692,7 @@ public class VM { program.step(); } break; case JUMP:{ - assert(stack.size() == 1); - DataWord pos = program.stackPop(); + DataWord pos = program.stackPop(); if (logger.isInfoEnabled()) hint = "~> " + pos.value(); @@ -744,8 +700,7 @@ public class VM { program.setPC(pos); } break; case JUMPI:{ - assert(stack.size() == 2); - DataWord pos = program.stackPop(); + DataWord pos = program.stackPop(); DataWord cond = program.stackPop(); if (!cond.isZero()) { @@ -801,8 +756,7 @@ public class VM { program.stackPush(data); } break; case CREATE:{ - assert(stack.size() == 3); - DataWord value = program.stackPop(); + DataWord value = program.stackPop(); DataWord inOffset = program.stackPop(); DataWord inSize = program.stackPop(); @@ -816,8 +770,7 @@ public class VM { program.step(); } break; case CALL:{ - assert(stack.size() == 7); - DataWord gas = program.stackPop(); + DataWord gas = program.stackPop(); DataWord toAddress = program.stackPop(); DataWord value = program.stackPop(); @@ -837,8 +790,7 @@ public class VM { program.step(); } break; case RETURN:{ - assert(stack.size() == 2); - DataWord offset = program.stackPop(); + DataWord offset = program.stackPop(); DataWord size = program.stackPop(); ByteBuffer hReturn = program.memoryChunk(offset, size); @@ -851,8 +803,7 @@ public class VM { program.stop(); } break; case SUICIDE:{ - assert(stack.size() == 1); - DataWord address = program.stackPop(); + DataWord address = program.stackPop(); program.suicide(address); if (logger.isInfoEnabled()) From 0a2a0803b983a66c678b63f1ce14b596df6723b8 Mon Sep 17 00:00:00 2001 From: nicksavers Date: Thu, 21 Aug 2014 19:10:28 +0200 Subject: [PATCH 06/12] Return gasUsed instead of gasDebit --- ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 1e749d32..0ee006ef 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java @@ -370,7 +370,7 @@ public class Blockchain { } catch (RuntimeException e) { trackRepository.rollback(); if(e instanceof OutOfGasException) - return gasDebit.longValue(); + return new BigInteger(1, tx.getGasLimit()).longValue(); return 0; } trackRepository.commit(); From dbaf79670d12d814a1b96c599f1ff9247edda46d Mon Sep 17 00:00:00 2001 From: nicksavers Date: Fri, 22 Aug 2014 23:07:37 +0200 Subject: [PATCH 07/12] Proper VM trace outlining --- ethereumj-core/src/main/java/org/ethereum/vm/VM.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ecfcde12..52a2d901 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java @@ -64,7 +64,7 @@ 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); @@ -817,7 +817,7 @@ public class VM { if (logger.isInfoEnabled() && !op.equals(CALL) && !op.equals(CREATE)) - logger.info(logString, stepBefore, op.name(), gasBefore, + logger.info(logString, stepBefore, String.format("%-12s", op.name()), gasBefore, program.invokeData.getCallDeep(), hint); // program.fullTrace(); From b61e40494f468773f8792aa143eecd7fbb78a34f Mon Sep 17 00:00:00 2001 From: nicksavers Date: Sat, 23 Aug 2014 19:39:16 +0200 Subject: [PATCH 08/12] Revert contract creation in event of VM exception and Program with 0 ops is allowed --- .../java/org/ethereum/core/Blockchain.java | 149 +++++++++--------- .../main/java/org/ethereum/vm/Program.java | 11 +- .../org/ethereum/vm/ProgramInvokeImpl.java | 8 +- 3 files changed, 82 insertions(+), 86 deletions(-) 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 0ee006ef..8f45a79a 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java @@ -6,14 +6,12 @@ import org.ethereum.manager.WorldManager; import org.ethereum.net.BlockQueue; import org.ethereum.util.AdvancedDeviceUtils; import org.ethereum.vm.*; -import org.ethereum.vm.Program.OutOfGasException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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; @@ -66,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(); @@ -117,8 +112,8 @@ public class Blockchain { if (block == null) return; - if (block.getNumber() == 1984) - logger.debug("Block #1984"); + if (block.getNumber() == 2111) + logger.debug("Block #2111"); // if it is the first block to add // make sure the parent is genesis @@ -218,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: {}", @@ -237,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) { @@ -246,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={}", @@ -278,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 @@ -291,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) @@ -310,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 @@ -355,26 +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(); - if(e instanceof OutOfGasException) + + } catch (RuntimeException e) { + trackRepository.rollback(); return new BigInteger(1, tx.getGasLimit()).longValue(); - return 0; + } + 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()); + } } - 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 ba58c73d..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,7 +44,7 @@ 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; @@ -52,6 +52,8 @@ public class Program { } public byte getCurrentOp() { + if(ops.length == 0) + return 0; return ops[pc]; } @@ -180,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; 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..35cb3de7 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java @@ -140,7 +140,7 @@ public class ProgramInvokeImpl implements ProgramInvoke { byte[] data = new byte[32]; - int index = indexData.value().intValue(); + int index = indexData.intValue(); int size = 32; if (msgData == null) return new DataWord(data); @@ -155,7 +155,7 @@ public class ProgramInvokeImpl implements ProgramInvoke { /* 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 +163,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]; From 477394ac49c15e15ff5ef900383367fdb742b43c Mon Sep 17 00:00:00 2001 From: nicksavers Date: Sat, 23 Aug 2014 20:47:48 +0200 Subject: [PATCH 09/12] Add CALLDATACOPY OutOfGasException Unit Test --- .../src/test/java/org/ethereum/vm/VMTest.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) 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() { From cdbc5f0a251ac0606c575513a076a3cb9c0bb9ed Mon Sep 17 00:00:00 2001 From: nicksavers Date: Sat, 23 Aug 2014 22:46:55 +0200 Subject: [PATCH 10/12] Add catch overflow for CALLDATALOAD --- .../org/ethereum/vm/ProgramInvokeImpl.java | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) 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 35cb3de7..d262142b 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,21 +135,25 @@ 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) { - byte[] data = new byte[32]; - - int index = indexData.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 ; + 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[size]; System.arraycopy(msgData, index, data, 0, size); - return new DataWord(data); } From 99522657ef42a330f8dead77ae9998e19450e025 Mon Sep 17 00:00:00 2001 From: nicksavers Date: Sat, 23 Aug 2014 22:47:40 +0200 Subject: [PATCH 11/12] Use statelogger for logging state conflict --- .../src/main/java/org/ethereum/core/Blockchain.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 8f45a79a..52e72137 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java @@ -112,8 +112,8 @@ public class Blockchain { if (block == null) return; - if (block.getNumber() == 2111) - logger.debug("Block #2111"); + if (block.getNumber() == 3211) + logger.debug("Block #3211"); // if it is the first block to add // make sure the parent is genesis @@ -170,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()); @@ -196,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 } From 4b722c9aa7fabe32376d72701175c951f435ea1f Mon Sep 17 00:00:00 2001 From: nicksavers Date: Sat, 23 Aug 2014 23:57:36 +0200 Subject: [PATCH 12/12] Revert standard size for CALLDATALOAD to 32 bytes --- .../src/main/java/org/ethereum/vm/ProgramInvokeImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 d262142b..6a05c650 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java @@ -152,7 +152,7 @@ public class ProgramInvokeImpl implements ProgramInvoke { if (index + size > msgData.length) size = msgData.length - index; - byte[] data = new byte[size]; + byte[] data = new byte[32]; System.arraycopy(msgData, index, data, 0, size); return new DataWord(data); }