Merge pull request #87 from nicksavers/vmfix
Pre-calculation of gas cost before execution of operation
This commit is contained in:
commit
a60e79b1e4
|
@ -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;
|
||||
|
||||
|
@ -66,9 +65,6 @@ public class Blockchain {
|
|||
// 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();
|
||||
|
||||
public Blockchain(Repository repository) {
|
||||
|
@ -116,6 +112,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
|
||||
if (blockCache.isEmpty()
|
||||
|
@ -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) {
|
||||
|
@ -243,23 +245,18 @@ public class Blockchain {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// 3. FIND OUT THE TRANSACTION TYPE
|
||||
// UPDATE THE NONCE
|
||||
repository.increaseNonce(senderAddress);
|
||||
|
||||
// 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());
|
||||
|
||||
// 2.2 PERFORM THE GAS VALUE TX
|
||||
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
@ -307,36 +317,27 @@ public class Blockchain {
|
|||
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 {
|
||||
if (isContractCreation || code != null) {
|
||||
|
||||
// 5. CREATE OR EXECUTE PROGRAM
|
||||
// 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));
|
||||
}
|
||||
|
||||
if (isContractCreation || code != null) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -79,6 +80,10 @@ public class Program {
|
|||
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();
|
||||
}
|
||||
|
@ -141,6 +145,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;
|
||||
if (memory != null) memSize = memory.limit();
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
@ -61,75 +64,105 @@ 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();
|
||||
|
||||
// Log debugging line for VM
|
||||
if(program.getNumber().intValue() == CONFIG.dumpBlock())
|
||||
this.dumpLine(op.val(), program);
|
||||
|
||||
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:
|
||||
// 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())
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
||||
|
|
Loading…
Reference in New Issue