Revert contract creation in event of VM exception and Program with 0 ops is allowed

This commit is contained in:
nicksavers 2014-08-23 19:39:16 +02:00
parent dbaf79670d
commit b61e40494f
3 changed files with 82 additions and 86 deletions

View File

@ -6,14 +6,12 @@ import org.ethereum.manager.WorldManager;
import org.ethereum.net.BlockQueue; import org.ethereum.net.BlockQueue;
import org.ethereum.util.AdvancedDeviceUtils; import org.ethereum.util.AdvancedDeviceUtils;
import org.ethereum.vm.*; import org.ethereum.vm.*;
import org.ethereum.vm.Program.OutOfGasException;
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;
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;
@ -67,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) {
@ -117,8 +112,8 @@ public class Blockchain {
if (block == null) if (block == null)
return; return;
if (block.getNumber() == 1984) if (block.getNumber() == 2111)
logger.debug("Block #1984"); logger.debug("Block #2111");
// 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
@ -218,18 +213,21 @@ public class Blockchain {
/** /**
* Apply the transaction to the world state * Apply the transaction to the world state.
*
* During this method changes to the repository are either permanent or possibly reverted by a VM exception.
* *
* @param block - the block which contains the transactions * @param 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: {}",
@ -237,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) {
@ -247,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={}",
@ -278,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
@ -291,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)
@ -311,36 +317,27 @@ public class Blockchain {
Hex.toHexString(senderAddress), gasDebit); Hex.toHexString(senderAddress), gasDebit);
} }
// CREATE AND/OR EXECUTE CONTRACT
// 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();
long gasUsed = 0; 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; Block currBlock = (block == null) ? this.getLastBlock() : block;
ProgramInvoke programInvoke = ProgramInvokeFactory ProgramInvoke programInvoke = ProgramInvokeFactory
@ -355,26 +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();
} else {
// refund everything except fee (500 + 5*txdata) } catch (RuntimeException e) {
BigInteger gasPrice = new BigInteger(1, tx.getGasPrice()); trackRepository.rollback();
long dataFee = tx.getData() == null ? 0: tx.getData().length * GasCost.TXDATA;
long minTxFee = GasCost.TRANSACTION + dataFee;
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(minTxFee).multiply(gasPrice));
if (refund.signum() > 0) {
// gas refund
repository.addBalance(senderAddress, refund);
repository.addBalance(coinbase, refund.negate());
}
}
} catch (RuntimeException e) {
trackRepository.rollback();
if(e instanceof OutOfGasException)
return new BigInteger(1, tx.getGasLimit()).longValue(); return new BigInteger(1, tx.getGasLimit()).longValue();
return 0; }
trackRepository.commit();
} else {
// REFUND GASDEBIT EXCEPT FOR FEE (500 + 5*TXDATA)
long dataCost = tx.getData() == null ? 0: tx.getData().length * GasCost.TXDATA;
gasUsed = GasCost.TRANSACTION + dataCost;
BigInteger gasPrice = new BigInteger(1, tx.getGasPrice());
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(gasUsed).multiply(gasPrice));
if (refund.signum() > 0) {
repository.addBalance(senderAddress, refund);
repository.addBalance(coinbase, refund.negate());
}
} }
trackRepository.commit();
pendingTransactions.put(Hex.toHexString(tx.getHash()), tx);
return gasUsed; return gasUsed;
} }

View File

@ -44,7 +44,7 @@ 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;
@ -52,6 +52,8 @@ public class Program {
} }
public byte getCurrentOp() { public byte getCurrentOp() {
if(ops.length == 0)
return 0;
return ops[pc]; return ops[pc];
} }
@ -180,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;

View File

@ -140,7 +140,7 @@ public class ProgramInvokeImpl implements ProgramInvoke {
byte[] data = new byte[32]; byte[] data = new byte[32];
int index = indexData.value().intValue(); int index = indexData.intValue();
int size = 32; int size = 32;
if (msgData == null) return new DataWord(data); if (msgData == null) return new DataWord(data);
@ -155,7 +155,7 @@ public class ProgramInvokeImpl implements ProgramInvoke {
/* 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 +163,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];