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.math.BigInteger;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -66,9 +65,6 @@ public class Blockchain {
|
||||||
// convenient usage, <block_number, block_hash>
|
// convenient usage, <block_number, block_hash>
|
||||||
private Map<Long, byte[]> blockCache = new HashMap<>();
|
private Map<Long, byte[]> blockCache = new HashMap<>();
|
||||||
|
|
||||||
private Map<String, Transaction> pendingTransactions = Collections
|
|
||||||
.synchronizedMap(new HashMap<String, Transaction>());
|
|
||||||
|
|
||||||
private BlockQueue blockQueue = new BlockQueue();
|
private BlockQueue blockQueue = new BlockQueue();
|
||||||
|
|
||||||
public Blockchain(Repository repository) {
|
public Blockchain(Repository repository) {
|
||||||
|
@ -116,6 +112,9 @@ public class Blockchain {
|
||||||
if (block == null)
|
if (block == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (block.getNumber() == 3211)
|
||||||
|
logger.debug("Block #3211");
|
||||||
|
|
||||||
// if it is the first block to add
|
// if it is the first block to add
|
||||||
// make sure the parent is genesis
|
// make sure the parent is genesis
|
||||||
if (blockCache.isEmpty()
|
if (blockCache.isEmpty()
|
||||||
|
@ -171,7 +170,7 @@ public class Blockchain {
|
||||||
stateLogger.debug("apply block: [ {} ] tx: [ {} ] ", block.getNumber(), i);
|
stateLogger.debug("apply block: [ {} ] tx: [ {} ] ", block.getNumber(), i);
|
||||||
totalGasUsed += applyTransaction(block, txr.getTransaction());
|
totalGasUsed += applyTransaction(block, txr.getTransaction());
|
||||||
if(!Arrays.equals(this.repository.getWorldState().getRootHash(), txr.getPostTxState()))
|
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()));
|
Hex.toHexString(this.repository.getWorldState().getRootHash()));
|
||||||
if(block.getNumber() >= CONFIG.traceStartBlock())
|
if(block.getNumber() >= CONFIG.traceStartBlock())
|
||||||
repository.dumpState(block, totalGasUsed, i++, txr.getTransaction().getHash());
|
repository.dumpState(block, totalGasUsed, i++, txr.getTransaction().getHash());
|
||||||
|
@ -197,7 +196,7 @@ public class Blockchain {
|
||||||
String blockStateRootHash = Hex.toHexString(block.getStateRoot());
|
String blockStateRootHash = Hex.toHexString(block.getStateRoot());
|
||||||
String worldStateRootHash = Hex.toHexString(WorldManager.getInstance().getRepository().getWorldState().getRootHash());
|
String worldStateRootHash = Hex.toHexString(WorldManager.getInstance().getRepository().getWorldState().getRootHash());
|
||||||
if(!blockStateRootHash.equals(worldStateRootHash)){
|
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();
|
// repository.close();
|
||||||
// System.exit(-1); // Don't add block
|
// 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 block - the block which contains the transactions
|
||||||
* @param tx - the transaction to be applied
|
* @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) {
|
public long applyTransaction(Block block, Transaction tx) {
|
||||||
|
|
||||||
byte[] coinbase = block.getCoinbase();
|
byte[] coinbase = block.getCoinbase();
|
||||||
|
|
||||||
|
// VALIDATE THE SENDER
|
||||||
byte[] senderAddress = tx.getSender();
|
byte[] senderAddress = tx.getSender();
|
||||||
AccountState senderAccount = repository.getAccountState(senderAddress);
|
AccountState senderAccount = repository.getAccountState(senderAddress);
|
||||||
|
|
||||||
if (senderAccount == null) {
|
if (senderAccount == null) {
|
||||||
if (stateLogger.isWarnEnabled())
|
if (stateLogger.isWarnEnabled())
|
||||||
stateLogger.warn("No such address: {}",
|
stateLogger.warn("No such address: {}",
|
||||||
|
@ -233,7 +235,7 @@ public class Blockchain {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 1. VALIDATE THE NONCE
|
// VALIDATE THE NONCE
|
||||||
BigInteger nonce = senderAccount.getNonce();
|
BigInteger nonce = senderAccount.getNonce();
|
||||||
BigInteger txNonce = new BigInteger(1, tx.getNonce());
|
BigInteger txNonce = new BigInteger(1, tx.getNonce());
|
||||||
if (nonce.compareTo(txNonce) != 0) {
|
if (nonce.compareTo(txNonce) != 0) {
|
||||||
|
@ -243,23 +245,18 @@ public class Blockchain {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. FIND OUT THE TRANSACTION TYPE
|
// UPDATE THE NONCE
|
||||||
|
repository.increaseNonce(senderAddress);
|
||||||
|
|
||||||
|
// FIND OUT THE TRANSACTION TYPE
|
||||||
byte[] receiverAddress, code = null;
|
byte[] receiverAddress, code = null;
|
||||||
boolean isContractCreation = tx.isContractCreation();
|
boolean isContractCreation = tx.isContractCreation();
|
||||||
if (isContractCreation) {
|
if (isContractCreation) {
|
||||||
receiverAddress = tx.getContractAddress();
|
receiverAddress = tx.getContractAddress();
|
||||||
repository.createAccount(receiverAddress);
|
|
||||||
if(stateLogger.isDebugEnabled())
|
|
||||||
stateLogger.debug("new contract created address={}",
|
|
||||||
Hex.toHexString(receiverAddress));
|
|
||||||
code = tx.getData(); // init code
|
code = tx.getData(); // init code
|
||||||
if (stateLogger.isDebugEnabled())
|
|
||||||
stateLogger.debug("running the init for contract: address={}",
|
|
||||||
Hex.toHexString(receiverAddress));
|
|
||||||
} else {
|
} else {
|
||||||
receiverAddress = tx.getReceiveAddress();
|
receiverAddress = tx.getReceiveAddress();
|
||||||
AccountState receiverState = repository.getAccountState(receiverAddress);
|
if (repository.getAccountState(receiverAddress) == null) {
|
||||||
if (receiverState == null) {
|
|
||||||
repository.createAccount(receiverAddress);
|
repository.createAccount(receiverAddress);
|
||||||
if (stateLogger.isDebugEnabled())
|
if (stateLogger.isDebugEnabled())
|
||||||
stateLogger.debug("new receiver account created address={}",
|
stateLogger.debug("new receiver account created address={}",
|
||||||
|
@ -274,12 +271,27 @@ public class Blockchain {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.1 UPDATE THE NONCE
|
// THE SIMPLE VALUE/BALANCE CHANGE
|
||||||
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
|
boolean isValueTx = tx.getValue() != null;
|
||||||
repository.increaseNonce(senderAddress);
|
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
|
if(!isContractCreation) // adding to new contract could be reverted
|
||||||
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
|
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();
|
BigInteger gasDebit = tx.getTotalGasValueDebit();
|
||||||
|
|
||||||
// Debit the actual total gas value from the sender
|
// Debit the actual total gas value from the sender
|
||||||
|
@ -287,14 +299,12 @@ public class Blockchain {
|
||||||
// the contract in the execution state,
|
// the contract in the execution state,
|
||||||
// it can be retrieved using GAS op
|
// it can be retrieved using GAS op
|
||||||
if (gasDebit.signum() == 1) {
|
if (gasDebit.signum() == 1) {
|
||||||
BigInteger balance = senderAccount.getBalance();
|
if (senderAccount.getBalance().compareTo(gasDebit) == -1) {
|
||||||
if (balance.compareTo(gasDebit) == -1) {
|
|
||||||
logger.debug("No gas to start the execution: sender={}",
|
logger.debug("No gas to start the execution: sender={}",
|
||||||
Hex.toHexString(senderAddress));
|
Hex.toHexString(senderAddress));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
repository.addBalance(senderAddress, gasDebit.negate());
|
repository.addBalance(senderAddress, gasDebit.negate());
|
||||||
senderAccount.subFromBalance(gasDebit); // balance will be read again below
|
|
||||||
|
|
||||||
// The coinbase get the gas cost
|
// The coinbase get the gas cost
|
||||||
if (coinbase != null)
|
if (coinbase != null)
|
||||||
|
@ -307,36 +317,27 @@ public class Blockchain {
|
||||||
Hex.toHexString(senderAddress), gasDebit);
|
Hex.toHexString(senderAddress), gasDebit);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CREATE AND/OR EXECUTE CONTRACT
|
||||||
|
long gasUsed = 0;
|
||||||
|
if (isContractCreation || code != null) {
|
||||||
|
|
||||||
// 3. THE SIMPLE VALUE/BALANCE CHANGE
|
// START TRACKING FOR REVERT CHANGES OPTION
|
||||||
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();
|
Repository trackRepository = repository.getTrack();
|
||||||
trackRepository.startTracking();
|
trackRepository.startTracking();
|
||||||
long gasUsed = 0;
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
// 5. CREATE OR EXECUTE PROGRAM
|
// 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;
|
Block currBlock = (block == null) ? this.getLastBlock() : block;
|
||||||
|
|
||||||
ProgramInvoke programInvoke = ProgramInvokeFactory
|
ProgramInvoke programInvoke = ProgramInvokeFactory
|
||||||
|
@ -351,24 +352,24 @@ public class Blockchain {
|
||||||
applyProgramResult(result, gasDebit, trackRepository,
|
applyProgramResult(result, gasDebit, trackRepository,
|
||||||
senderAddress, receiverAddress, coinbase, isContractCreation);
|
senderAddress, receiverAddress, coinbase, isContractCreation);
|
||||||
gasUsed = result.getGasUsed();
|
gasUsed = result.getGasUsed();
|
||||||
|
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
trackRepository.rollback();
|
||||||
|
return new BigInteger(1, tx.getGasLimit()).longValue();
|
||||||
|
}
|
||||||
|
trackRepository.commit();
|
||||||
} else {
|
} else {
|
||||||
// refund everything except fee (500 + 5*txdata)
|
// 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 gasPrice = new BigInteger(1, tx.getGasPrice());
|
||||||
long dataFee = tx.getData() == null ? 0: tx.getData().length * GasCost.TXDATA;
|
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(gasUsed).multiply(gasPrice));
|
||||||
long minTxFee = GasCost.TRANSACTION + dataFee;
|
|
||||||
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(minTxFee).multiply(gasPrice));
|
|
||||||
if (refund.signum() > 0) {
|
if (refund.signum() > 0) {
|
||||||
// gas refund
|
|
||||||
repository.addBalance(senderAddress, refund);
|
repository.addBalance(senderAddress, refund);
|
||||||
repository.addBalance(coinbase, refund.negate());
|
repository.addBalance(coinbase, refund.negate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (RuntimeException e) {
|
|
||||||
trackRepository.rollback();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
trackRepository.commit();
|
|
||||||
pendingTransactions.put(Hex.toHexString(tx.getHash()), tx);
|
|
||||||
return gasUsed;
|
return gasUsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,15 +44,16 @@ public class Program {
|
||||||
this.invokeHash = invokeData.hashCode();
|
this.invokeHash = invokeData.hashCode();
|
||||||
result.setRepository(invokeData.getRepository());
|
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.invokeData = invokeData;
|
||||||
this.ops = ops;
|
this.ops = ops;
|
||||||
this.programAddress = invokeData.getOwnerAddress();
|
this.programAddress = invokeData.getOwnerAddress();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte getCurrentOp() {
|
public byte getCurrentOp() {
|
||||||
|
if(ops.length == 0)
|
||||||
|
return 0;
|
||||||
return ops[pc];
|
return ops[pc];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,6 +80,10 @@ public class Program {
|
||||||
stack.push(stackWord);
|
stack.push(stackWord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Stack<DataWord> getStack() {
|
||||||
|
return this.stack;
|
||||||
|
}
|
||||||
|
|
||||||
public int getPC() {
|
public int getPC() {
|
||||||
return pc;
|
return pc;
|
||||||
}
|
}
|
||||||
|
@ -114,7 +119,6 @@ public class Program {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void step() {
|
public void step() {
|
||||||
|
|
||||||
++pc;
|
++pc;
|
||||||
if (pc >= ops.length) stop();
|
if (pc >= ops.length) stop();
|
||||||
}
|
}
|
||||||
|
@ -141,6 +145,13 @@ public class Program {
|
||||||
return stack.pop();
|
return stack.pop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void require(int stackSize) {
|
||||||
|
if(stack.size() != stackSize) {
|
||||||
|
stop();
|
||||||
|
throw new RuntimeException("stack too small");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public int getMemSize() {
|
public int getMemSize() {
|
||||||
int memSize = 0;
|
int memSize = 0;
|
||||||
if (memory != null) memSize = memory.limit();
|
if (memory != null) memSize = memory.limit();
|
||||||
|
@ -171,11 +182,10 @@ public class Program {
|
||||||
|
|
||||||
public ByteBuffer memoryChunk(DataWord offsetData, DataWord sizeData) {
|
public ByteBuffer memoryChunk(DataWord offsetData, DataWord sizeData) {
|
||||||
|
|
||||||
int offset = offsetData.value().intValue();
|
int offset = offsetData.intValue();
|
||||||
int size = sizeData.value().intValue();
|
int size = sizeData.intValue();
|
||||||
allocateMemory(offset, new byte[sizeData.intValue()]);
|
|
||||||
|
|
||||||
byte[] chunk = new byte[size];
|
byte[] chunk = new byte[size];
|
||||||
|
allocateMemory(offset, chunk);
|
||||||
|
|
||||||
if (memory != null) {
|
if (memory != null) {
|
||||||
if (memory.limit() < offset + size) size = memory.limit() - offset;
|
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(senderAddress, endowment.negate());
|
||||||
result.getRepository().addBalance(newAddress, endowment);
|
result.getRepository().addBalance(newAddress, endowment);
|
||||||
|
|
||||||
|
|
||||||
Repository trackRepository = result.getRepository().getTrack();
|
Repository trackRepository = result.getRepository().getTrack();
|
||||||
trackRepository.startTracking();
|
trackRepository.startTracking();
|
||||||
|
|
||||||
|
@ -435,12 +444,11 @@ public class Program {
|
||||||
Hex.toHexString(senderAddress), refundGas.toString());
|
Hex.toHexString(senderAddress), refundGas.toString());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
this.refundGas(gas.intValue(), "remaining gas from the internal call");
|
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);
|
gasLogger.info("[{}] Spent for cause: [ {} ], gas: [ {} ]", invokeHash, cause, gasValue);
|
||||||
|
|
||||||
long afterSpend = invokeData.getGas().longValue() - gasValue - result.getGasUsed();
|
long afterSpend = invokeData.getGas().longValue() - gasValue - result.getGasUsed();
|
||||||
|
@ -547,7 +555,6 @@ public class Program {
|
||||||
return invokeData.getGaslimit().clone();
|
return invokeData.getGaslimit().clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public ProgramResult getResult() {
|
public ProgramResult getResult() {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.ethereum.vm;
|
||||||
|
|
||||||
import org.ethereum.db.Repository;
|
import org.ethereum.db.Repository;
|
||||||
|
|
||||||
|
import java.math.BigInteger;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -134,28 +135,32 @@ public class ProgramInvokeImpl implements ProgramInvoke {
|
||||||
/*****************/
|
/*****************/
|
||||||
/*** msg data ***/
|
/*** 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 */
|
/* CALLDATALOAD op */
|
||||||
public DataWord getDataValue(DataWord indexData) {
|
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];
|
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);
|
System.arraycopy(msgData, index, data, 0, size);
|
||||||
|
|
||||||
return new DataWord(data);
|
return new DataWord(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* CALLDATASIZE */
|
/* CALLDATASIZE */
|
||||||
public DataWord getDataSize() {
|
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;
|
int size = msgData.length;
|
||||||
return new DataWord(size);
|
return new DataWord(size);
|
||||||
}
|
}
|
||||||
|
@ -163,8 +168,8 @@ public class ProgramInvokeImpl implements ProgramInvoke {
|
||||||
/* CALLDATACOPY */
|
/* CALLDATACOPY */
|
||||||
public byte[] getDataCopy(DataWord offsetData, DataWord lengthData) {
|
public byte[] getDataCopy(DataWord offsetData, DataWord lengthData) {
|
||||||
|
|
||||||
int offset = offsetData.value().intValue();
|
int offset = offsetData.intValue();
|
||||||
int length = lengthData.value().intValue();
|
int length = lengthData.intValue();
|
||||||
|
|
||||||
byte[] data = new byte[length];
|
byte[] data = new byte[length];
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ public class ProgramResult {
|
||||||
*/
|
*/
|
||||||
private List<CallCreate> callCreateList;
|
private List<CallCreate> callCreateList;
|
||||||
|
|
||||||
public void spendGas(int gas) {
|
public void spendGas(long gas) {
|
||||||
gasUsed += gas;
|
gasUsed += gas;
|
||||||
}
|
}
|
||||||
public void refundGas(long gas) {
|
public void refundGas(long gas) {
|
||||||
|
|
|
@ -3,7 +3,9 @@ package org.ethereum.vm;
|
||||||
import org.ethereum.crypto.HashUtil;
|
import org.ethereum.crypto.HashUtil;
|
||||||
import org.ethereum.db.ContractDetails;
|
import org.ethereum.db.ContractDetails;
|
||||||
import org.ethereum.vm.Program.OutOfGasException;
|
import org.ethereum.vm.Program.OutOfGasException;
|
||||||
|
|
||||||
import static org.ethereum.config.SystemProperties.CONFIG;
|
import static org.ethereum.config.SystemProperties.CONFIG;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.spongycastle.util.encoders.Hex;
|
import org.spongycastle.util.encoders.Hex;
|
||||||
|
@ -13,6 +15,7 @@ import java.nio.ByteBuffer;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
import static org.ethereum.vm.OpCode.CALL;
|
import static org.ethereum.vm.OpCode.CALL;
|
||||||
import static org.ethereum.vm.OpCode.CREATE;
|
import static org.ethereum.vm.OpCode.CREATE;
|
||||||
|
@ -61,75 +64,105 @@ public class VM {
|
||||||
private Logger logger = LoggerFactory.getLogger("VM");
|
private Logger logger = LoggerFactory.getLogger("VM");
|
||||||
private Logger dumpLogger = LoggerFactory.getLogger("dump");
|
private Logger dumpLogger = LoggerFactory.getLogger("dump");
|
||||||
private static BigInteger _32_ = BigInteger.valueOf(32);
|
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) {
|
public void step(Program program) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
OpCode op = OpCode.code(program.getCurrentOp());
|
||||||
|
program.setLastOp(op.val());
|
||||||
|
|
||||||
byte op = program.getCurrentOp();
|
long oldMemSize = program.getMemSize();
|
||||||
program.setLastOp(op);
|
BigInteger newMemSize = BigInteger.ZERO;
|
||||||
|
Stack<DataWord> stack = program.getStack();
|
||||||
int oldMemSize = program.getMemSize();
|
|
||||||
|
|
||||||
String hint = "";
|
String hint = "";
|
||||||
long gasBefore = program.getGas().longValue();
|
long gasBefore = program.getGas().longValue();
|
||||||
int stepBefore = program.getPC();
|
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()) {
|
// Calculate fees and spend gas
|
||||||
|
switch (op) {
|
||||||
switch (OpCode.code(op)) {
|
case STOP: case SUICIDE:
|
||||||
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:
|
|
||||||
// The ops that doesn't charged by step, or
|
// The ops that doesn't charged by step, or
|
||||||
// charged in the following section
|
// charged in the following section
|
||||||
break;
|
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:
|
default:
|
||||||
program.spendGas(GasCost.STEP, OpCode.code(op).name());
|
program.spendGas(GasCost.STEP, op.name());
|
||||||
break;
|
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
|
* Stop and Arithmetic Operations
|
||||||
*/
|
*/
|
||||||
|
@ -655,15 +688,7 @@ public class VM {
|
||||||
if (logger.isInfoEnabled())
|
if (logger.isInfoEnabled())
|
||||||
hint = "addr: " + addr + " value: " + value;
|
hint = "addr: " + addr + " value: " + value;
|
||||||
|
|
||||||
// for gas calculations [YP 9.2]
|
|
||||||
DataWord oldValue = program.storageLoad(addr);
|
|
||||||
program.storageSave(addr, value);
|
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();
|
program.step();
|
||||||
} break;
|
} break;
|
||||||
case JUMP:{
|
case JUMP:{
|
||||||
|
@ -723,7 +748,7 @@ public class VM {
|
||||||
case PUSH17: case PUSH18: case PUSH19: case PUSH20: case PUSH21: case PUSH22: case PUSH23: case PUSH24:
|
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:{
|
case PUSH25: case PUSH26: case PUSH27: case PUSH28: case PUSH29: case PUSH30: case PUSH31: case PUSH32:{
|
||||||
program.step();
|
program.step();
|
||||||
int nPush = op - PUSH1.val() + 1;
|
int nPush = op.val() - PUSH1.val() + 1;
|
||||||
|
|
||||||
byte[] data = program.sweep(nPush);
|
byte[] data = program.sweep(nPush);
|
||||||
hint = "" + Hex.toHexString(data);
|
hint = "" + Hex.toHexString(data);
|
||||||
|
@ -736,8 +761,8 @@ public class VM {
|
||||||
DataWord inSize = program.stackPop();
|
DataWord inSize = program.stackPop();
|
||||||
|
|
||||||
if (logger.isInfoEnabled())
|
if (logger.isInfoEnabled())
|
||||||
logger.info(logString, program.getPC(), OpCode.code(op)
|
logger.info(logString, program.getPC(), op.name(),
|
||||||
.name(), program.getGas().value(),
|
program.getGas().value(),
|
||||||
program.invokeData.getCallDeep(), hint);
|
program.invokeData.getCallDeep(), hint);
|
||||||
|
|
||||||
program.createContract(value, inOffset, inSize);
|
program.createContract(value, inOffset, inSize);
|
||||||
|
@ -756,8 +781,8 @@ public class VM {
|
||||||
DataWord outDataSize = program.stackPop();
|
DataWord outDataSize = program.stackPop();
|
||||||
|
|
||||||
if (logger.isInfoEnabled())
|
if (logger.isInfoEnabled())
|
||||||
logger.info(logString, program.getPC(), OpCode.code(op)
|
logger.info(logString, program.getPC(), op.name(),
|
||||||
.name(), program.getGas().value(),
|
program.getGas().value(),
|
||||||
program.invokeData.getCallDeep(), hint);
|
program.invokeData.getCallDeep(), hint);
|
||||||
|
|
||||||
program.callToAddress(gas, toAddress, value, inDataOffs, inDataSize, outDataOffs, outDataSize);
|
program.callToAddress(gas, toAddress, value, inDataOffs, inDataSize, outDataOffs, outDataSize);
|
||||||
|
@ -790,25 +815,18 @@ public class VM {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logger.isInfoEnabled() && !OpCode.code(op).equals(CALL)
|
if (logger.isInfoEnabled() && !op.equals(CALL)
|
||||||
&& !OpCode.code(op).equals(CREATE))
|
&& !op.equals(CREATE))
|
||||||
logger.info(logString, stepBefore, OpCode.code(op).name(),
|
logger.info(logString, stepBefore, String.format("%-12s", op.name()), gasBefore,
|
||||||
gasBefore, program.invokeData.getCallDeep(), hint);
|
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();
|
// program.fullTrace();
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
program.stop();
|
|
||||||
if(e instanceof OutOfGasException)
|
if(e instanceof OutOfGasException)
|
||||||
logger.warn("OutOfGasException occurred", e);
|
logger.warn("OutOfGasException occurred", e);
|
||||||
else
|
else
|
||||||
logger.error("VM halted", e);
|
logger.error("VM halted", e);
|
||||||
|
program.stop();
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -830,4 +848,29 @@ public class VM {
|
||||||
program.setRuntimeFailure(e);
|
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;
|
package org.ethereum.vm;
|
||||||
|
|
||||||
import org.ethereum.db.Repository;
|
import org.ethereum.db.Repository;
|
||||||
|
import org.ethereum.vm.Program.OutOfGasException;
|
||||||
import org.junit.FixMethodOrder;
|
import org.junit.FixMethodOrder;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runners.MethodSorters;
|
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
|
@Test // ADDRESS OP
|
||||||
public void testADDRESS_1() {
|
public void testADDRESS_1() {
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue