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.util.AdvancedDeviceUtils;
import org.ethereum.vm.*;
import org.ethereum.vm.Program.OutOfGasException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.util.encoders.Hex;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@ -66,9 +64,6 @@ public class Blockchain {
// keep the index of the chain for
// convenient usage, <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();
@ -117,8 +112,8 @@ public class Blockchain {
if (block == null)
return;
if (block.getNumber() == 1984)
logger.debug("Block #1984");
if (block.getNumber() == 2111)
logger.debug("Block #2111");
// if it is the first block to add
// make sure the parent is genesis
@ -218,18 +213,21 @@ public class Blockchain {
/**
* Apply the transaction to the world state
* Apply the transaction to the world state.
*
* During this method changes to the repository are either permanent or possibly reverted by a VM exception.
*
* @param block - the block which contains the transactions
* @param tx - the transaction to be applied
* @return gasUsed
* @return gasUsed - the total amount of gas used for this transaction.
*/
public long applyTransaction(Block block, Transaction tx) {
byte[] coinbase = block.getCoinbase();
// VALIDATE THE SENDER
byte[] senderAddress = tx.getSender();
AccountState senderAccount = repository.getAccountState(senderAddress);
if (senderAccount == null) {
if (stateLogger.isWarnEnabled())
stateLogger.warn("No such address: {}",
@ -237,7 +235,7 @@ public class Blockchain {
return 0;
}
// 1. VALIDATE THE NONCE
// VALIDATE THE NONCE
BigInteger nonce = senderAccount.getNonce();
BigInteger txNonce = new BigInteger(1, tx.getNonce());
if (nonce.compareTo(txNonce) != 0) {
@ -246,24 +244,19 @@ public class Blockchain {
nonce, txNonce);
return 0;
}
// UPDATE THE NONCE
repository.increaseNonce(senderAddress);
// 3. FIND OUT THE TRANSACTION TYPE
// FIND OUT THE TRANSACTION TYPE
byte[] receiverAddress, code = null;
boolean isContractCreation = tx.isContractCreation();
if (isContractCreation) {
receiverAddress = tx.getContractAddress();
repository.createAccount(receiverAddress);
if(stateLogger.isDebugEnabled())
stateLogger.debug("new contract created address={}",
Hex.toHexString(receiverAddress));
code = tx.getData(); // init code
if (stateLogger.isDebugEnabled())
stateLogger.debug("running the init for contract: address={}",
Hex.toHexString(receiverAddress));
} else {
receiverAddress = tx.getReceiveAddress();
AccountState receiverState = repository.getAccountState(receiverAddress);
if (receiverState == null) {
if (repository.getAccountState(receiverAddress) == null) {
repository.createAccount(receiverAddress);
if (stateLogger.isDebugEnabled())
stateLogger.debug("new receiver account created address={}",
@ -278,12 +271,27 @@ public class Blockchain {
}
}
// 2.1 UPDATE THE NONCE
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
repository.increaseNonce(senderAddress);
// THE SIMPLE VALUE/BALANCE CHANGE
boolean isValueTx = tx.getValue() != null;
if (isValueTx) {
BigInteger txValue = new BigInteger(1, tx.getValue());
if (senderAccount.getBalance().compareTo(txValue) >= 0) {
senderAccount.subFromBalance(txValue); // balance will be read again below
repository.addBalance(senderAddress, txValue.negate());
if(!isContractCreation) // adding to new contract could be reverted
repository.addBalance(receiverAddress, txValue);
if (stateLogger.isDebugEnabled())
stateLogger.debug("Update value balance \n "
+ "sender={}, receiver={}, value={}",
Hex.toHexString(senderAddress),
Hex.toHexString(receiverAddress),
new BigInteger(tx.getValue()));
}
}
// 2.2 PERFORM THE GAS VALUE TX
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
// GET TOTAL ETHER VALUE AVAILABLE FOR TX FEE
BigInteger gasDebit = tx.getTotalGasValueDebit();
// Debit the actual total gas value from the sender
@ -291,14 +299,12 @@ public class Blockchain {
// the contract in the execution state,
// it can be retrieved using GAS op
if (gasDebit.signum() == 1) {
BigInteger balance = senderAccount.getBalance();
if (balance.compareTo(gasDebit) == -1) {
if (senderAccount.getBalance().compareTo(gasDebit) == -1) {
logger.debug("No gas to start the execution: sender={}",
Hex.toHexString(senderAddress));
return 0;
}
repository.addBalance(senderAddress, gasDebit.negate());
senderAccount.subFromBalance(gasDebit); // balance will be read again below
// The coinbase get the gas cost
if (coinbase != null)
@ -310,37 +316,28 @@ public class Blockchain {
+ "\n sender={} \n gas_debit= {}",
Hex.toHexString(senderAddress), gasDebit);
}
// 3. THE SIMPLE VALUE/BALANCE CHANGE
if (tx.getValue() != null) {
BigInteger senderBalance = senderAccount.getBalance();
if (senderBalance.compareTo(new BigInteger(1, tx.getValue())) >= 0) {
repository.addBalance(receiverAddress,
new BigInteger(1, tx.getValue()));
repository.addBalance(senderAddress,
new BigInteger(1, tx.getValue()).negate());
if (stateLogger.isDebugEnabled())
stateLogger.debug("Update value balance \n "
+ "sender={}, receiver={}, value={}",
Hex.toHexString(senderAddress),
Hex.toHexString(receiverAddress),
new BigInteger(tx.getValue()));
}
}
// 4. START TRACKING FOR REVERT CHANGES OPTION !!!
Repository trackRepository = repository.getTrack();
trackRepository.startTracking();
// CREATE AND/OR EXECUTE CONTRACT
long gasUsed = 0;
try {
// 5. CREATE OR EXECUTE PROGRAM
if (isContractCreation || code != null) {
if (isContractCreation || code != null) {
// START TRACKING FOR REVERT CHANGES OPTION
Repository trackRepository = repository.getTrack();
trackRepository.startTracking();
try {
// CREATE NEW CONTRACT ADDRESS AND ADD TX VALUE
if(isContractCreation) {
if (isValueTx) // adding to balance also creates the account
trackRepository.addBalance(receiverAddress, new BigInteger(1, tx.getValue()));
else
trackRepository.createAccount(receiverAddress);
if(stateLogger.isDebugEnabled())
stateLogger.debug("new contract created address={}",
Hex.toHexString(receiverAddress));
}
Block currBlock = (block == null) ? this.getLastBlock() : block;
ProgramInvoke programInvoke = ProgramInvokeFactory
@ -355,26 +352,24 @@ public class Blockchain {
applyProgramResult(result, gasDebit, trackRepository,
senderAddress, receiverAddress, coinbase, isContractCreation);
gasUsed = result.getGasUsed();
} else {
// refund everything except fee (500 + 5*txdata)
BigInteger gasPrice = new BigInteger(1, tx.getGasPrice());
long dataFee = tx.getData() == null ? 0: tx.getData().length * GasCost.TXDATA;
long minTxFee = GasCost.TRANSACTION + dataFee;
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(minTxFee).multiply(gasPrice));
if (refund.signum() > 0) {
// gas refund
repository.addBalance(senderAddress, refund);
repository.addBalance(coinbase, refund.negate());
}
}
} catch (RuntimeException e) {
trackRepository.rollback();
if(e instanceof OutOfGasException)
} catch (RuntimeException e) {
trackRepository.rollback();
return new BigInteger(1, tx.getGasLimit()).longValue();
return 0;
}
trackRepository.commit();
} else {
// REFUND GASDEBIT EXCEPT FOR FEE (500 + 5*TXDATA)
long dataCost = tx.getData() == null ? 0: tx.getData().length * GasCost.TXDATA;
gasUsed = GasCost.TRANSACTION + dataCost;
BigInteger gasPrice = new BigInteger(1, tx.getGasPrice());
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(gasUsed).multiply(gasPrice));
if (refund.signum() > 0) {
repository.addBalance(senderAddress, refund);
repository.addBalance(coinbase, refund.negate());
}
}
trackRepository.commit();
pendingTransactions.put(Hex.toHexString(tx.getHash()), tx);
return gasUsed;
}

View File

@ -44,7 +44,7 @@ public class Program {
this.invokeHash = invokeData.hashCode();
result.setRepository(invokeData.getRepository());
if (ops == null) throw new RuntimeException("program can not run with ops: null");
if (ops == null) ops = new byte[0]; //throw new RuntimeException("program can not run with ops: null");
this.invokeData = invokeData;
this.ops = ops;
@ -52,6 +52,8 @@ public class Program {
}
public byte getCurrentOp() {
if(ops.length == 0)
return 0;
return ops[pc];
}
@ -180,11 +182,10 @@ public class Program {
public ByteBuffer memoryChunk(DataWord offsetData, DataWord sizeData) {
int offset = offsetData.value().intValue();
int size = sizeData.value().intValue();
allocateMemory(offset, new byte[sizeData.intValue()]);
int offset = offsetData.intValue();
int size = sizeData.intValue();
byte[] chunk = new byte[size];
allocateMemory(offset, chunk);
if (memory != null) {
if (memory.limit() < offset + size) size = memory.limit() - offset;

View File

@ -140,7 +140,7 @@ public class ProgramInvokeImpl implements ProgramInvoke {
byte[] data = new byte[32];
int index = indexData.value().intValue();
int index = indexData.intValue();
int size = 32;
if (msgData == null) return new DataWord(data);
@ -155,7 +155,7 @@ public class ProgramInvokeImpl implements ProgramInvoke {
/* CALLDATASIZE */
public DataWord getDataSize() {
if (msgData == null || msgData.length == 0) return new DataWord(new byte[32]);
if (msgData == null || msgData.length == 0) return DataWord.ZERO;
int size = msgData.length;
return new DataWord(size);
}
@ -163,8 +163,8 @@ public class ProgramInvokeImpl implements ProgramInvoke {
/* CALLDATACOPY */
public byte[] getDataCopy(DataWord offsetData, DataWord lengthData) {
int offset = offsetData.value().intValue();
int length = lengthData.value().intValue();
int offset = offsetData.intValue();
int length = lengthData.intValue();
byte[] data = new byte[length];