Revert contract creation in event of VM exception and Program with 0 ops is allowed
This commit is contained in:
parent
dbaf79670d
commit
b61e40494f
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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];
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue