Merge pull request #87 from nicksavers/vmfix

Pre-calculation of gas cost before execution of operation
This commit is contained in:
romanman 2014-08-24 10:11:10 +03:00
commit a60e79b1e4
6 changed files with 292 additions and 215 deletions

View File

@ -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, <block_number, block_hash>
private Map<Long, byte[]> blockCache = new HashMap<>();
private Map<String, Transaction> pendingTransactions = Collections
.synchronizedMap(new HashMap<String, Transaction>());
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;
}

View File

@ -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<DataWord> 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;
}

View File

@ -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];

View File

@ -27,7 +27,7 @@ public class ProgramResult {
*/
private List<CallCreate> callCreateList;
public void spendGas(int gas) {
public void spendGas(long gas) {
gasUsed += gas;
}
public void refundGas(long gas) {

View File

@ -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<DataWord> 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<DataWord> storageKeys = new ArrayList<>(details.getStorage().keySet());
Collections.sort((List<DataWord>) 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<DataWord> storageKeys = new ArrayList<>(details.getStorage().keySet());
Collections.sort((List<DataWord>) 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);
}
}

View File

@ -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() {