Introducing StateTest json suite support:
+ passing: stInitCodeTest + passing: stLogTests + passing: stRecursiveCreate + passing: stRefundTest
This commit is contained in:
parent
c271346a4f
commit
aad53ba2c5
|
@ -156,10 +156,10 @@ public class AccountState {
|
|||
}
|
||||
|
||||
public String toString() {
|
||||
String ret = "Nonce: " + this.getNonce().toString() + "\n" +
|
||||
"Balance: " + Denomination.toFriendlyString(getBalance()) + "\n" +
|
||||
"State Root: " + Hex.toHexString(this.getStateRoot()) + "\n" +
|
||||
"Code Hash: " + Hex.toHexString(this.getCodeHash());
|
||||
String ret = " Nonce: " + this.getNonce().toString() + "\n" +
|
||||
" Balance: " + getBalance() + "\n" +
|
||||
" State Root: " + Hex.toHexString(this.getStateRoot()) + "\n" +
|
||||
" Code Hash: " + Hex.toHexString(this.getCodeHash());
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,11 +6,10 @@ import org.ethereum.facade.Blockchain;
|
|||
import org.ethereum.facade.Repository;
|
||||
import org.ethereum.listener.EthereumListener;
|
||||
import org.ethereum.manager.AdminInfo;
|
||||
import org.ethereum.manager.WorldManager;
|
||||
import org.ethereum.net.BlockQueue;
|
||||
import org.ethereum.net.server.ChannelManager;
|
||||
import org.ethereum.util.AdvancedDeviceUtils;
|
||||
import org.ethereum.vm.*;
|
||||
import org.ethereum.vm.ProgramInvokeFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
@ -24,12 +23,10 @@ import java.io.IOException;
|
|||
import java.math.BigInteger;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import static org.ethereum.config.SystemProperties.CONFIG;
|
||||
import static org.ethereum.core.Denomination.SZABO;
|
||||
import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY;
|
||||
|
||||
/**
|
||||
* The Ethereum blockchain is in many ways similar to the Bitcoin blockchain,
|
||||
|
@ -84,7 +81,10 @@ public class BlockchainImpl implements Blockchain {
|
|||
private BigInteger totalDifficulty = BigInteger.ZERO;
|
||||
|
||||
@Autowired
|
||||
private WorldManager worldManager;
|
||||
Wallet wallet;
|
||||
|
||||
@Autowired
|
||||
private EthereumListener listener;
|
||||
|
||||
@Autowired
|
||||
private BlockQueue blockQueue;
|
||||
|
@ -208,13 +208,11 @@ public class BlockchainImpl implements Blockchain {
|
|||
|
||||
|
||||
// Remove all wallet transactions as they already approved by the net
|
||||
worldManager.getWallet().removeTransactions(block.getTransactionsList());
|
||||
wallet.removeTransactions(block.getTransactionsList());
|
||||
|
||||
|
||||
EthereumListener listener = worldManager.getListener();
|
||||
listener.trace(String.format("Block chain size: [ %d ]", this.getSize()));
|
||||
|
||||
EthereumListener ethereumListener = worldManager.getListener();
|
||||
ethereumListener.onBlock(block);
|
||||
listener.onBlock(block);
|
||||
|
||||
if (blockQueue.size() == 0 &&
|
||||
!syncDoneCalled &&
|
||||
|
@ -222,7 +220,7 @@ public class BlockchainImpl implements Blockchain {
|
|||
|
||||
logger.info("Sync done");
|
||||
syncDoneCalled = true;
|
||||
ethereumListener.onSyncDone();
|
||||
listener.onSyncDone();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -299,7 +297,6 @@ public class BlockchainImpl implements Blockchain {
|
|||
if(isValid(block)) {
|
||||
if (!block.isGenesis()) {
|
||||
if (!CONFIG.blockChainOnly()) {
|
||||
Wallet wallet = worldManager.getWallet();
|
||||
wallet.addTransactions(block.getTransactionsList());
|
||||
receipts = this.applyBlock(block);
|
||||
wallet.processBlock(block);
|
||||
|
@ -320,7 +317,11 @@ public class BlockchainImpl implements Blockchain {
|
|||
for (Transaction tx : block.getTransactionsList()) {
|
||||
stateLogger.info("apply block: [{}] tx: [{}] ", block.getNumber(), i);
|
||||
|
||||
TransactionReceipt receipt = applyTransaction(block, tx);
|
||||
TransactionExecutor executor = new TransactionExecutor(tx, block.getCoinbase(), track,
|
||||
programInvokeFactory, bestBlock);
|
||||
executor.execute();
|
||||
|
||||
TransactionReceipt receipt = executor.getReceipt();
|
||||
totalGasUsed += receipt.getCumulativeGasLong();
|
||||
|
||||
track.commit();
|
||||
|
@ -403,235 +404,6 @@ public class BlockchainImpl implements Blockchain {
|
|||
logger.info("*** Last block added [ #{} ]", block.getNumber());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 - the total amount of gas used for this transaction.
|
||||
*/
|
||||
public TransactionReceipt applyTransaction(Block block, Transaction tx) {
|
||||
|
||||
logger.info("applyTransaction: [{}]", Hex.toHexString(tx.getHash()));
|
||||
|
||||
TransactionReceipt receipt = new TransactionReceipt();
|
||||
|
||||
byte[] coinbase = block.getCoinbase();
|
||||
|
||||
// VALIDATE THE SENDER
|
||||
byte[] senderAddress = tx.getSender();
|
||||
// AccountState senderAccount = repository.getAccountState(senderAddress);
|
||||
logger.info("tx.sender: [{}]", Hex.toHexString(tx.getSender()));
|
||||
|
||||
// VALIDATE THE NONCE
|
||||
BigInteger nonce = track.getNonce(senderAddress);
|
||||
BigInteger txNonce = new BigInteger(1, tx.getNonce());
|
||||
if (nonce.compareTo(txNonce) != 0) {
|
||||
if (stateLogger.isWarnEnabled())
|
||||
stateLogger.warn("Invalid nonce account.nonce={} tx.nonce={}",
|
||||
nonce, txNonce);
|
||||
|
||||
receipt.setCumulativeGas(0);
|
||||
return receipt;
|
||||
}
|
||||
|
||||
// UPDATE THE NONCE
|
||||
track.increaseNonce(senderAddress);
|
||||
logger.info("increased tx.nonce to: [{}]", track.getNonce(senderAddress));
|
||||
|
||||
// FIND OUT THE TRANSACTION TYPE
|
||||
byte[] receiverAddress, code = null;
|
||||
boolean isContractCreation = tx.isContractCreation();
|
||||
if (isContractCreation) {
|
||||
receiverAddress = tx.getContractAddress();
|
||||
code = tx.getData(); // init code
|
||||
} else {
|
||||
receiverAddress = tx.getReceiveAddress();
|
||||
code = track.getCode(receiverAddress);
|
||||
|
||||
// on invocation the contract is created event if doesn't exist.
|
||||
track.addBalance(receiverAddress, BigInteger.ZERO);
|
||||
if (code != EMPTY_BYTE_ARRAY) {
|
||||
if (stateLogger.isDebugEnabled())
|
||||
stateLogger.debug("calling for existing contract: address={}",
|
||||
Hex.toHexString(receiverAddress));
|
||||
}
|
||||
}
|
||||
|
||||
// THE SIMPLE VALUE/BALANCE CHANGE
|
||||
boolean isValueTx = tx.getValue() != null;
|
||||
BigInteger txValue = new BigInteger(1, tx.getValue());
|
||||
if (isValueTx && !isContractCreation) {
|
||||
if (track.getBalance(senderAddress).compareTo(txValue) >= 0) {
|
||||
|
||||
track.addBalance(receiverAddress, txValue); // balance will be read again below
|
||||
track.addBalance(senderAddress, txValue.negate());
|
||||
|
||||
// if(!isContractCreation) // adding to new contract could be reverted
|
||||
// track.addBalance(receiverAddress, txValue); todo: find out what is that ?
|
||||
|
||||
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 gasPrice = new BigInteger(1, tx.getGasPrice());
|
||||
BigInteger gasDebit = new BigInteger(1, tx.getGasLimit()).multiply(gasPrice);
|
||||
logger.info("Gas price limited to [{} wei]", gasDebit.toString());
|
||||
|
||||
// Debit the actual total gas value from the sender
|
||||
// the purchased gas will be available for
|
||||
// the contract in the execution state,
|
||||
// it can be retrieved using GAS op
|
||||
if (gasDebit.signum() == 1) {
|
||||
if (track.getBalance(senderAddress).compareTo(gasDebit) == -1) {
|
||||
logger.debug("No gas to start the execution: sender={}",
|
||||
Hex.toHexString(senderAddress));
|
||||
|
||||
receipt.setCumulativeGas(0);
|
||||
return receipt;
|
||||
}
|
||||
track.addBalance(senderAddress, gasDebit.negate());
|
||||
|
||||
// The coinbase get the gas cost
|
||||
if (coinbase != null)
|
||||
track.addBalance(coinbase, gasDebit);
|
||||
|
||||
if (stateLogger.isDebugEnabled())
|
||||
stateLogger.debug(
|
||||
"Before contract execution debit the sender address with gas total cost, "
|
||||
+ "\n sender={} \n gas_debit= {}",
|
||||
Hex.toHexString(senderAddress), gasDebit);
|
||||
}
|
||||
|
||||
// CREATE AND/OR EXECUTE CONTRACT
|
||||
long gasUsed = 0;
|
||||
if (isContractCreation || code != EMPTY_BYTE_ARRAY) {
|
||||
|
||||
// START TRACKING FOR REVERT CHANGES OPTION
|
||||
Repository trackTx = track.startTracking();
|
||||
trackTx.addBalance(receiverAddress, BigInteger.ZERO); // the contract created for anycase but SUICIDE call
|
||||
|
||||
trackTx.addBalance(receiverAddress, txValue);
|
||||
track.addBalance(senderAddress, txValue.negate()); // will not be reverted
|
||||
|
||||
logger.info("Start tracking VM run");
|
||||
try {
|
||||
|
||||
// CREATE NEW CONTRACT ADDRESS AND ADD TX VALUE
|
||||
if(isContractCreation) {
|
||||
if(stateLogger.isDebugEnabled())
|
||||
stateLogger.debug("new contract created address={}",
|
||||
Hex.toHexString(receiverAddress));
|
||||
}
|
||||
|
||||
Block currBlock = (block == null) ? this.getBestBlock() : block;
|
||||
|
||||
ProgramInvoke programInvoke =
|
||||
programInvokeFactory.createProgramInvoke(tx, currBlock, trackTx);
|
||||
|
||||
VM vm = new VM();
|
||||
Program program = new Program(code, programInvoke);
|
||||
|
||||
if (CONFIG.playVM())
|
||||
vm.play(program);
|
||||
|
||||
program.saveProgramTraceToFile(Hex.toHexString(tx.getHash()));
|
||||
ProgramResult result = program.getResult();
|
||||
applyProgramResult(result, gasDebit, gasPrice, trackTx,
|
||||
senderAddress, receiverAddress, coinbase, isContractCreation);
|
||||
gasUsed = result.getGasUsed();
|
||||
|
||||
List<LogInfo> logs = result.getLogInfoList();
|
||||
receipt.setLogInfoList(logs);
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
trackTx.rollback();
|
||||
receipt.setCumulativeGas(tx.getGasLimit());
|
||||
return receipt;
|
||||
}
|
||||
trackTx.commit();
|
||||
} else {
|
||||
// REFUND GASDEBIT EXCEPT FOR FEE (500 + 5*TX_NO_ZERO_DATA)
|
||||
long dataCost = tx.getData() == null ? 0: tx.getData().length * GasCost.TX_NO_ZERO_DATA;
|
||||
gasUsed = GasCost.TRANSACTION + dataCost;
|
||||
|
||||
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(gasUsed).multiply(gasPrice));
|
||||
if (refund.signum() > 0) {
|
||||
track.addBalance(senderAddress, refund);
|
||||
track.addBalance(coinbase, refund.negate());
|
||||
}
|
||||
}
|
||||
|
||||
receipt.setCumulativeGas(gasUsed);
|
||||
return receipt;
|
||||
}
|
||||
|
||||
/**
|
||||
* After any contract code finish the run the certain result should take
|
||||
* place, according the given circumstances
|
||||
*
|
||||
* @param result
|
||||
* @param gasDebit
|
||||
* @param senderAddress
|
||||
* @param contractAddress
|
||||
*/
|
||||
private void applyProgramResult(ProgramResult result, BigInteger gasDebit,
|
||||
BigInteger gasPrice, Repository repository, byte[] senderAddress,
|
||||
byte[] contractAddress, byte[] coinbase, boolean initResults) {
|
||||
|
||||
if (result.getException() != null) {
|
||||
stateLogger.debug("contract run halted by Exception: contract: [{}], exception: [{}]",
|
||||
Hex.toHexString(contractAddress),
|
||||
result.getException());
|
||||
throw result.getException();
|
||||
}
|
||||
|
||||
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(
|
||||
result.getGasUsed()).multiply(gasPrice));
|
||||
|
||||
if (refund.signum() > 0) {
|
||||
if (stateLogger.isDebugEnabled())
|
||||
stateLogger
|
||||
.debug("After contract execution the sender address refunded with gas leftover, "
|
||||
+ "\n sender={} \n contract={} \n gas_refund= {}",
|
||||
Hex.toHexString(senderAddress),
|
||||
Hex.toHexString(contractAddress), refund);
|
||||
// gas refund
|
||||
repository.addBalance(senderAddress, refund);
|
||||
repository.addBalance(coinbase, refund.negate());
|
||||
}
|
||||
|
||||
if (initResults) {
|
||||
// Save the code created by init
|
||||
byte[] bodyCode = null;
|
||||
if (result.getHReturn() != null && result.getHReturn().array().length > 0) {
|
||||
bodyCode = result.getHReturn().array();
|
||||
}
|
||||
|
||||
if (bodyCode != null) {
|
||||
if (stateLogger.isDebugEnabled())
|
||||
stateLogger
|
||||
.debug("saving code of the contract to the db:\n contract={} code={}",
|
||||
Hex.toHexString(contractAddress),
|
||||
Hex.toHexString(bodyCode));
|
||||
repository.saveCode(contractAddress, bodyCode);
|
||||
}
|
||||
}
|
||||
|
||||
// delete the marked to die accounts
|
||||
if (result.getDeleteAccounts() == null) return;
|
||||
for (DataWord address : result.getDeleteAccounts()){
|
||||
repository.delete(address.getNoLeadZeroesData());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasParentOnTheChain(Block block){
|
||||
return getParent(block.getHeader()) != null;
|
||||
|
@ -731,11 +503,23 @@ public class BlockchainImpl implements Blockchain {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public void setRepository(Repository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public void setProgramInvokeFactory(ProgramInvokeFactory factory){
|
||||
this.programInvokeFactory = factory;
|
||||
}
|
||||
|
||||
public void startTracking(){
|
||||
track = repository.startTracking();
|
||||
}
|
||||
|
||||
public void commitTracking(){
|
||||
track.commit();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -92,6 +92,8 @@ public class Transaction {
|
|||
if(receiveAddress == null) {
|
||||
this.receiveAddress = ByteUtil.EMPTY_BYTE_ARRAY;
|
||||
}
|
||||
|
||||
getEncoded();
|
||||
parsed = true;
|
||||
}
|
||||
|
||||
|
@ -143,6 +145,11 @@ public class Transaction {
|
|||
return nonce == null ? ZERO_BYTE_ARRAY : nonce ;
|
||||
}
|
||||
|
||||
public boolean isValueTx() {
|
||||
if (!parsed) rlpParse();
|
||||
return value != null ;
|
||||
}
|
||||
|
||||
public byte[] getValue() {
|
||||
if (!parsed) rlpParse();
|
||||
return value == null ? ZERO_BYTE_ARRAY : value;
|
||||
|
@ -155,7 +162,7 @@ public class Transaction {
|
|||
|
||||
public byte[] getGasPrice() {
|
||||
if (!parsed) rlpParse();
|
||||
return gasPrice;
|
||||
return gasPrice== null ? ZERO_BYTE_ARRAY : gasPrice ;
|
||||
}
|
||||
|
||||
public byte[] getGasLimit() {
|
||||
|
@ -222,9 +229,9 @@ public class Transaction {
|
|||
", receiveAddress=" + ByteUtil.toHexString(receiveAddress) +
|
||||
", value=" + ByteUtil.toHexString(value) +
|
||||
", data=" + ByteUtil.toHexString(data) +
|
||||
", signatureV=" + signature.v +
|
||||
", signatureR=" + ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.r)) +
|
||||
", signatureS=" + ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.s)) +
|
||||
", signatureV=" + (signature == null ? "" : signature.v) +
|
||||
", signatureR=" + (signature == null ? "" : ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.r))) +
|
||||
", signatureS=" + (signature == null ? "" : ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.s))) +
|
||||
"]";
|
||||
}
|
||||
|
||||
|
@ -286,6 +293,9 @@ public class Transaction {
|
|||
|
||||
this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit,
|
||||
receiveAddress, value, data, v, r, s);
|
||||
|
||||
this.hash = this.getHash();
|
||||
|
||||
return rlpEncoded;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,292 @@
|
|||
package org.ethereum.core;
|
||||
|
||||
import org.ethereum.facade.Repository;
|
||||
import org.ethereum.vm.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
import static org.ethereum.config.SystemProperties.CONFIG;
|
||||
import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY;
|
||||
|
||||
/**
|
||||
* www.ethergit.com
|
||||
*
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 19/12/2014 12:56
|
||||
*/
|
||||
|
||||
public class TransactionExecutor {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger("execute");
|
||||
private static final Logger stateLogger = LoggerFactory.getLogger("state");
|
||||
|
||||
private Transaction tx;
|
||||
private Repository track;
|
||||
private ProgramInvokeFactory programInvokeFactory;
|
||||
private byte[] coinbase;
|
||||
|
||||
private TransactionReceipt receipt;
|
||||
private ProgramResult result;
|
||||
private Block bestBlock;
|
||||
|
||||
|
||||
public TransactionExecutor(Transaction tx, byte[] coinbase, Repository track,
|
||||
ProgramInvokeFactory programInvokeFactory, Block bestBlock) {
|
||||
|
||||
this.tx = tx;
|
||||
this.coinbase = coinbase;
|
||||
this.track = track;
|
||||
this.programInvokeFactory = programInvokeFactory;
|
||||
this.bestBlock = bestBlock;
|
||||
}
|
||||
|
||||
public void execute(){
|
||||
|
||||
logger.info("applyTransaction: [{}]", Hex.toHexString(tx.getHash()));
|
||||
|
||||
TransactionReceipt receipt = new TransactionReceipt();
|
||||
|
||||
// VALIDATE THE SENDER
|
||||
byte[] senderAddress = tx.getSender();
|
||||
// AccountState senderAccount = repository.getAccountState(senderAddress);
|
||||
logger.info("tx.sender: [{}]", Hex.toHexString(tx.getSender()));
|
||||
|
||||
// VALIDATE THE NONCE
|
||||
BigInteger nonce = track.getNonce(senderAddress);
|
||||
BigInteger txNonce = new BigInteger(1, tx.getNonce());
|
||||
if (nonce.compareTo(txNonce) != 0) {
|
||||
if (stateLogger.isWarnEnabled())
|
||||
stateLogger.warn("Invalid nonce account.nonce={} tx.nonce={}",
|
||||
nonce, txNonce);
|
||||
|
||||
receipt.setCumulativeGas(0);
|
||||
this.receipt = receipt;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// FIND OUT THE TRANSACTION TYPE
|
||||
byte[] receiverAddress, code = null;
|
||||
boolean isContractCreation = tx.isContractCreation();
|
||||
if (isContractCreation) {
|
||||
receiverAddress = tx.getContractAddress();
|
||||
code = tx.getData(); // init code
|
||||
} else {
|
||||
receiverAddress = tx.getReceiveAddress();
|
||||
code = track.getCode(receiverAddress);
|
||||
|
||||
// on invocation the contract is created event if doesn't exist.
|
||||
track.addBalance(receiverAddress, BigInteger.ZERO);
|
||||
if (code != EMPTY_BYTE_ARRAY) {
|
||||
if (stateLogger.isDebugEnabled())
|
||||
stateLogger.debug("calling for existing contract: address={}",
|
||||
Hex.toHexString(receiverAddress));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// GET TOTAL ETHER VALUE AVAILABLE FOR TX FEE
|
||||
BigInteger gasPrice = new BigInteger(1, tx.getGasPrice());
|
||||
BigInteger gasDebit = new BigInteger(1, tx.getGasLimit()).multiply(gasPrice);
|
||||
logger.info("Gas price limited to [{} wei]", gasDebit.toString());
|
||||
|
||||
// Debit the actual total gas value from the sender
|
||||
// the purchased gas will be available for
|
||||
// the contract in the execution state,
|
||||
// it can be retrieved using GAS op
|
||||
if (track.getBalance(senderAddress).compareTo(gasDebit) == -1) {
|
||||
logger.debug("No gas to start the execution: sender={}",
|
||||
Hex.toHexString(senderAddress));
|
||||
|
||||
receipt.setCumulativeGas(0);
|
||||
this.receipt = receipt;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// THE SIMPLE VALUE/BALANCE CHANGE
|
||||
BigInteger txValue = new BigInteger(1, tx.getValue());
|
||||
if (tx.isValueTx()) {
|
||||
if (track.getBalance(senderAddress).compareTo(txValue) >= 0) {
|
||||
|
||||
track.addBalance(receiverAddress, txValue); // balance will be read again below
|
||||
track.addBalance(senderAddress, txValue.negate());
|
||||
|
||||
if (stateLogger.isDebugEnabled())
|
||||
stateLogger.debug("Update value balance \n "
|
||||
+ "sender={}, receiver={}, value={}",
|
||||
Hex.toHexString(senderAddress),
|
||||
Hex.toHexString(receiverAddress),
|
||||
new BigInteger(tx.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// UPDATE THE NONCE
|
||||
track.increaseNonce(senderAddress);
|
||||
logger.info("increased tx.nonce to: [{}]", track.getNonce(senderAddress));
|
||||
|
||||
// CHARGE FOR GAS
|
||||
track.addBalance(senderAddress, gasDebit.negate());
|
||||
|
||||
// The coinbase get the gas cost
|
||||
if (coinbase != null)
|
||||
track.addBalance(coinbase, gasDebit);
|
||||
|
||||
if (stateLogger.isDebugEnabled())
|
||||
stateLogger.debug(
|
||||
"Before contract execution debit the sender address with gas total cost, "
|
||||
+ "\n sender={} \n gas_debit= {}",
|
||||
Hex.toHexString(senderAddress), gasDebit);
|
||||
|
||||
// CREATE AND/OR EXECUTE CONTRACT
|
||||
long gasUsed = 0;
|
||||
if (isContractCreation || code != EMPTY_BYTE_ARRAY) {
|
||||
|
||||
// START TRACKING FOR REVERT CHANGES OPTION
|
||||
Repository trackTx = track.startTracking();
|
||||
trackTx.addBalance(receiverAddress, BigInteger.ZERO); // the contract created for anycase but SUICIDE call
|
||||
|
||||
logger.info("Start tracking VM run");
|
||||
try {
|
||||
|
||||
// CREATE NEW CONTRACT ADDRESS AND ADD TX VALUE
|
||||
if(isContractCreation) {
|
||||
if(stateLogger.isDebugEnabled())
|
||||
stateLogger.debug("new contract created address={}",
|
||||
Hex.toHexString(receiverAddress));
|
||||
}
|
||||
|
||||
ProgramInvoke programInvoke =
|
||||
programInvokeFactory.createProgramInvoke(tx, bestBlock, trackTx);
|
||||
|
||||
VM vm = new VM();
|
||||
Program program = new Program(code, programInvoke);
|
||||
|
||||
if (CONFIG.playVM())
|
||||
vm.play(program);
|
||||
|
||||
program.saveProgramTraceToFile(Hex.toHexString(tx.getHash()));
|
||||
result = program.getResult();
|
||||
applyProgramResult(result, gasDebit, gasPrice, trackTx,
|
||||
senderAddress, receiverAddress, coinbase, isContractCreation);
|
||||
gasUsed = result.getGasUsed();
|
||||
|
||||
List<LogInfo> logs = result.getLogInfoList();
|
||||
receipt.setLogInfoList(logs);
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
trackTx.rollback();
|
||||
receipt.setCumulativeGas(tx.getGasLimit());
|
||||
this.receipt = receipt;
|
||||
return;
|
||||
}
|
||||
trackTx.commit();
|
||||
} else {
|
||||
// REFUND GASDEBIT EXCEPT FOR FEE (500 + 5*TX_NO_ZERO_DATA)
|
||||
long dataCost = tx.getData() == null ? 0: tx.getData().length * GasCost.TX_NO_ZERO_DATA;
|
||||
gasUsed = GasCost.TRANSACTION + dataCost;
|
||||
|
||||
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(gasUsed).multiply(gasPrice));
|
||||
if (refund.signum() > 0) {
|
||||
track.addBalance(senderAddress, refund);
|
||||
track.addBalance(coinbase, refund.negate());
|
||||
}
|
||||
}
|
||||
|
||||
receipt.setCumulativeGas(gasUsed);
|
||||
this.receipt = receipt;
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* After any contract code finish the run the certain result should take
|
||||
* place, according the given circumstances
|
||||
*
|
||||
* @param result
|
||||
* @param gasDebit
|
||||
* @param senderAddress
|
||||
* @param contractAddress
|
||||
*/
|
||||
private void applyProgramResult(ProgramResult result, BigInteger gasDebit,
|
||||
BigInteger gasPrice, Repository repository, byte[] senderAddress,
|
||||
byte[] contractAddress, byte[] coinbase, boolean initResults) {
|
||||
|
||||
if (result.getException() != null) {
|
||||
stateLogger.debug("contract run halted by Exception: contract: [{}], exception: [{}]",
|
||||
Hex.toHexString(contractAddress),
|
||||
result.getException());
|
||||
throw result.getException();
|
||||
}
|
||||
|
||||
|
||||
BigInteger refund = gasDebit.subtract(BigInteger.valueOf(
|
||||
result.getGasUsed()).multiply(gasPrice));
|
||||
|
||||
if (refund.signum() > 0) {
|
||||
if (stateLogger.isDebugEnabled())
|
||||
stateLogger
|
||||
.debug("After contract execution the sender address refunded with gas leftover, "
|
||||
+ "\n sender={} \n contract={} \n gas_refund= {}",
|
||||
Hex.toHexString(senderAddress),
|
||||
Hex.toHexString(contractAddress), refund);
|
||||
// gas refund
|
||||
repository.addBalance(senderAddress, refund);
|
||||
repository.addBalance(coinbase, refund.negate());
|
||||
}
|
||||
|
||||
if (result.getFutureRefund() > 0){
|
||||
|
||||
long futureRefund = Math.min(result.getFutureRefund(), result.getGasUsed() / 2);
|
||||
BigInteger futureRefundBI = BigInteger.valueOf(futureRefund);
|
||||
BigInteger futureRefundVal = futureRefundBI.multiply(gasPrice);
|
||||
|
||||
if (stateLogger.isDebugEnabled())
|
||||
stateLogger
|
||||
.debug("After contract execution the sender address refunded with storage save refunds, "
|
||||
+ "\n sender={} \n contract={} \n gas_refund= {}",
|
||||
Hex.toHexString(senderAddress),
|
||||
Hex.toHexString(contractAddress), futureRefundVal);
|
||||
repository.addBalance(senderAddress, futureRefundVal);
|
||||
repository.addBalance(coinbase, futureRefundVal.negate());
|
||||
}
|
||||
|
||||
|
||||
if (initResults) {
|
||||
// Save the code created by init
|
||||
byte[] bodyCode = null;
|
||||
if (result.getHReturn() != null && result.getHReturn().array().length > 0) {
|
||||
bodyCode = result.getHReturn().array();
|
||||
}
|
||||
|
||||
if (bodyCode != null) {
|
||||
if (stateLogger.isDebugEnabled())
|
||||
stateLogger
|
||||
.debug("saving code of the contract to the db:\n contract={} code={}",
|
||||
Hex.toHexString(contractAddress),
|
||||
Hex.toHexString(bodyCode));
|
||||
repository.saveCode(contractAddress, bodyCode);
|
||||
}
|
||||
}
|
||||
|
||||
// delete the marked to die accounts
|
||||
if (result.getDeleteAccounts() == null) return;
|
||||
for (DataWord address : result.getDeleteAccounts()){
|
||||
repository.delete(address.getNoLeadZeroesData());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public TransactionReceipt getReceipt() {
|
||||
return receipt;
|
||||
}
|
||||
|
||||
public ProgramResult getResult() {
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import org.ethereum.trie.Trie;
|
|||
import org.ethereum.trie.TrieImpl;
|
||||
import org.ethereum.util.*;
|
||||
import org.ethereum.vm.DataWord;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
/**
|
||||
* @author: Roman Mandeleil
|
||||
|
@ -188,6 +189,23 @@ public class ContractDetails {
|
|||
this.storageValues = storageValues;
|
||||
}
|
||||
|
||||
public void setStorage(Map<DataWord, DataWord> storage) {
|
||||
|
||||
List<DataWord> keys = new ArrayList<>();
|
||||
keys.addAll(storageKeys);
|
||||
|
||||
List<DataWord> values = new ArrayList<>();
|
||||
for (DataWord key : keys){
|
||||
|
||||
DataWord value = storage.get(key);
|
||||
values.add(value);
|
||||
}
|
||||
|
||||
this.storageKeys = keys;
|
||||
this.storageValues = values;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public ContractDetails clone(){
|
||||
|
||||
|
@ -199,5 +217,13 @@ public class ContractDetails {
|
|||
return contractDetails;
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
|
||||
String ret = " Code: " + Hex.toHexString(code) + "\n";
|
||||
ret += " Storage: " + getStorage().toString();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,308 @@
|
|||
package org.ethereum.db;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.JsonNodeFactory;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import org.codehaus.plexus.util.FileUtils;
|
||||
import org.ethereum.core.AccountState;
|
||||
import org.ethereum.core.Block;
|
||||
import org.ethereum.facade.Repository;
|
||||
import org.ethereum.json.EtherObjectMapper;
|
||||
import org.ethereum.json.JSONHelper;
|
||||
import org.ethereum.trie.Trie;
|
||||
import org.ethereum.trie.TrieImpl;
|
||||
import org.ethereum.vm.DataWord;
|
||||
import org.iq80.leveldb.DBIterator;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.ethereum.config.SystemProperties.CONFIG;
|
||||
import static org.ethereum.crypto.SHA3Helper.sha3;
|
||||
import static org.ethereum.util.ByteUtil.wrap;
|
||||
|
||||
/**
|
||||
* www.etherj.com
|
||||
*
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 17/11/2014 21:15
|
||||
*/
|
||||
public class RepositoryDummy implements Repository {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger("repository");
|
||||
private Map<ByteArrayWrapper, AccountState> worldState = new HashMap<>();
|
||||
private Map<ByteArrayWrapper, ContractDetails> detailsDB = new HashMap<>() ;
|
||||
|
||||
|
||||
@Override
|
||||
public void reset() {
|
||||
|
||||
worldState.clear();
|
||||
detailsDB.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClosed() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void updateBatch(HashMap<ByteArrayWrapper, AccountState> stateCache, HashMap<ByteArrayWrapper,
|
||||
ContractDetails> detailsCache) {
|
||||
|
||||
for (ByteArrayWrapper hash : stateCache.keySet()) {
|
||||
|
||||
AccountState accountState = stateCache.get(hash);
|
||||
ContractDetails contractDetails = detailsCache.get(hash);
|
||||
|
||||
if (accountState.isDeleted()){
|
||||
worldState.remove( hash ) ;
|
||||
detailsDB.remove( hash );
|
||||
|
||||
logger.debug("delete: [{}]",
|
||||
Hex.toHexString(hash.getData()));
|
||||
|
||||
} else{
|
||||
|
||||
if (accountState.isDirty() || contractDetails.isDirty()){
|
||||
detailsDB.put( hash, contractDetails);
|
||||
accountState.setStateRoot(contractDetails.getStorageHash());
|
||||
accountState.setCodeHash(sha3(contractDetails.getCode()));
|
||||
worldState.put( hash, accountState);
|
||||
if (logger.isDebugEnabled()){
|
||||
logger.debug("update: [{}],nonce: [{}] balance: [{}] \n [{}]",
|
||||
Hex.toHexString(hash.getData()),
|
||||
accountState.getNonce(),
|
||||
accountState.getBalance(),
|
||||
contractDetails.getStorage());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
stateCache.clear();
|
||||
detailsCache.clear();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void syncToRoot(byte[] root) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository startTracking() {
|
||||
return new RepositoryTrack(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpState(Block block, long gasUsed, int txNumber, byte[] txHash) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public DBIterator getAccountsIterator() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public Set<ByteArrayWrapper> getFullAddressSet(){
|
||||
return worldState.keySet();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BigInteger addBalance(byte[] addr, BigInteger value) {
|
||||
AccountState account = getAccountState(addr);
|
||||
|
||||
if (account == null)
|
||||
account = createAccount(addr);
|
||||
|
||||
BigInteger result = account.addToBalance(value);
|
||||
worldState.put(wrap(addr), account);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger getBalance(byte[] addr) {
|
||||
AccountState account = getAccountState(addr);
|
||||
|
||||
if (account == null)
|
||||
return BigInteger.ZERO;
|
||||
|
||||
return account.getBalance();
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataWord getStorageValue(byte[] addr, DataWord key) {
|
||||
ContractDetails details = getContractDetails(addr);
|
||||
|
||||
if (details == null)
|
||||
return null;
|
||||
|
||||
return details.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStorageRow(byte[] addr, DataWord key, DataWord value) {
|
||||
ContractDetails details = getContractDetails(addr);
|
||||
|
||||
if (details == null){
|
||||
createAccount(addr);
|
||||
details = getContractDetails(addr);
|
||||
}
|
||||
|
||||
details.put(key, value);
|
||||
detailsDB.put(wrap(addr), details);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getCode(byte[] addr) {
|
||||
ContractDetails details = getContractDetails(addr);
|
||||
|
||||
if (details == null)
|
||||
return null;
|
||||
|
||||
return details.getCode();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void saveCode(byte[] addr, byte[] code) {
|
||||
ContractDetails details = getContractDetails(addr);
|
||||
|
||||
if (details == null){
|
||||
createAccount(addr);
|
||||
details = getContractDetails(addr);
|
||||
}
|
||||
|
||||
details.setCode(code);
|
||||
detailsDB.put(wrap( addr ), details);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger getNonce(byte[] addr) {
|
||||
AccountState account = getAccountState(addr);
|
||||
|
||||
if (account == null)
|
||||
account = createAccount(addr);
|
||||
|
||||
return account.getNonce();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger increaseNonce(byte[] addr) {
|
||||
AccountState account = getAccountState(addr);
|
||||
|
||||
if (account == null)
|
||||
account = createAccount(addr);
|
||||
|
||||
account.incrementNonce();
|
||||
worldState.put(wrap(addr), account);
|
||||
|
||||
return account.getNonce();
|
||||
}
|
||||
|
||||
public BigInteger setNonce(byte[] addr, BigInteger nonce) {
|
||||
|
||||
AccountState account = getAccountState(addr);
|
||||
|
||||
if (account == null)
|
||||
account = createAccount(addr);
|
||||
|
||||
account.setNonce(nonce);
|
||||
worldState.put(wrap(addr), account);
|
||||
|
||||
return account.getNonce();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void delete(byte[] addr) {
|
||||
worldState.remove(wrap(addr));
|
||||
detailsDB.remove(wrap(addr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ContractDetails getContractDetails(byte[] addr) {
|
||||
|
||||
return detailsDB.get(wrap(addr));
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public AccountState getAccountState(byte[] addr) {
|
||||
return worldState.get(wrap(addr));
|
||||
}
|
||||
|
||||
@Override
|
||||
public AccountState createAccount(byte[] addr) {
|
||||
AccountState accountState = new AccountState();
|
||||
worldState.put(wrap(addr), accountState);
|
||||
|
||||
ContractDetails contractDetails = new ContractDetails();
|
||||
detailsDB.put(wrap(addr), contractDetails);
|
||||
|
||||
return accountState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRoot() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAccount(byte[] addr, HashMap<ByteArrayWrapper, AccountState> cacheAccounts, HashMap<ByteArrayWrapper, ContractDetails> cacheDetails) {
|
||||
|
||||
AccountState account = getAccountState(addr);
|
||||
ContractDetails details = getContractDetails(addr);
|
||||
|
||||
if (account == null)
|
||||
account = new AccountState();
|
||||
else
|
||||
account = account.clone();
|
||||
|
||||
if (details == null)
|
||||
details = new ContractDetails();
|
||||
else
|
||||
details = details.clone();
|
||||
|
||||
cacheAccounts.put(wrap(addr), account);
|
||||
cacheDetails.put(wrap(addr), details);
|
||||
}
|
||||
}
|
|
@ -12,7 +12,6 @@ import org.ethereum.trie.Trie;
|
|||
import org.ethereum.trie.TrieImpl;
|
||||
import org.ethereum.vm.DataWord;
|
||||
import org.iq80.leveldb.DBIterator;
|
||||
import org.iq80.leveldb.WriteBatch;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
@ -27,7 +26,7 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
|
||||
import static org.ethereum.config.SystemProperties.CONFIG;
|
||||
import static org.ethereum.crypto.SHA3Helper.*;
|
||||
import static org.ethereum.crypto.SHA3Helper.sha3;
|
||||
import static org.ethereum.util.ByteUtil.wrap;
|
||||
|
||||
/**
|
||||
|
@ -103,7 +102,7 @@ public class RepositoryImpl implements Repository {
|
|||
|
||||
} else{
|
||||
|
||||
if (contractDetails.isDirty()){
|
||||
if (accountState.isDirty() || contractDetails.isDirty()){
|
||||
detailsDB.put(hash.getData(), contractDetails.getEncoded());
|
||||
accountState.setStateRoot(contractDetails.getStorageHash());
|
||||
accountState.setCodeHash(sha3(contractDetails.getCode()));
|
||||
|
@ -118,22 +117,12 @@ public class RepositoryImpl implements Repository {
|
|||
|
||||
}
|
||||
|
||||
if (!contractDetails.isDirty() && accountState.isDirty()){
|
||||
worldState.update(hash.getData(), accountState.getEncoded());
|
||||
|
||||
if (logger.isDebugEnabled()){
|
||||
logger.debug("update: [{}],nonce: [{}] balance: [{}]",
|
||||
Hex.toHexString(hash.getData()),
|
||||
accountState.getNonce(),
|
||||
accountState.getBalance());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
detailsCache.remove(hash.getData());
|
||||
stateCache.remove(hash.getData());
|
||||
}
|
||||
|
||||
stateCache.clear();
|
||||
detailsCache.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -362,6 +351,22 @@ public class RepositoryImpl implements Repository {
|
|||
return account.getNonce();
|
||||
}
|
||||
|
||||
public BigInteger setNonce(byte[] addr, BigInteger nonce) {
|
||||
|
||||
AccountState account = getAccountState(addr);
|
||||
|
||||
if (account == null)
|
||||
account = createAccount(addr);
|
||||
|
||||
account.setNonce(nonce);
|
||||
worldState.update(addr, account.getEncoded());
|
||||
|
||||
return account.getNonce();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public void delete(byte[] addr) {
|
||||
worldState.delete(addr);
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.spongycastle.util.encoders.Hex;
|
|||
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.ethereum.crypto.SHA3Helper.sha3;
|
||||
import static org.ethereum.util.ByteUtil.wrap;
|
||||
|
@ -26,12 +27,15 @@ public class RepositoryTrack implements Repository {
|
|||
|
||||
private static final Logger logger = LoggerFactory.getLogger("repository");
|
||||
|
||||
|
||||
HashMap<ByteArrayWrapper, AccountState> cacheAccounts = new HashMap<>();
|
||||
HashMap<ByteArrayWrapper, ContractDetails> cacheDetails = new HashMap<>();
|
||||
|
||||
Repository repository;
|
||||
|
||||
public RepositoryTrack(){
|
||||
this.repository = new RepositoryDummy();
|
||||
}
|
||||
|
||||
public RepositoryTrack(Repository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
@ -116,6 +120,23 @@ public class RepositoryTrack implements Repository {
|
|||
return accountState.getNonce();
|
||||
}
|
||||
|
||||
public BigInteger setNonce(byte[] addr, BigInteger bigInteger) {
|
||||
AccountState accountState = getAccountState(addr);
|
||||
|
||||
if (accountState == null)
|
||||
accountState = createAccount(addr);
|
||||
|
||||
BigInteger saveNonce = accountState.getNonce();
|
||||
accountState.setNonce(bigInteger);
|
||||
|
||||
logger.trace("increase nonce addr: [{}], from: [{}], to: [{}]", Hex.toHexString(addr),
|
||||
saveNonce, accountState.getNonce());
|
||||
|
||||
return accountState.getNonce();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public BigInteger getNonce(byte[] addr) {
|
||||
AccountState accountState = getAccountState(addr);
|
||||
|
@ -177,6 +198,10 @@ public class RepositoryTrack implements Repository {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public Set<ByteArrayWrapper> getFullAddressSet(){
|
||||
return cacheAccounts.keySet();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void dumpState(Block block, long gasUsed, int txNumber, byte[] txHash) {
|
||||
|
|
|
@ -192,4 +192,5 @@ public interface Repository {
|
|||
|
||||
void loadAccount(byte[] addr, HashMap<ByteArrayWrapper, AccountState> cacheAccounts,
|
||||
HashMap<ByteArrayWrapper, ContractDetails> cacheDetails);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package org.ethereum.jsontestsuite;
|
||||
|
||||
import org.ethereum.db.ByteArrayWrapper;
|
||||
import org.ethereum.db.ContractDetails;
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.vm.DataWord;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
|
@ -24,7 +25,7 @@ public class AccountState {
|
|||
byte[] code;
|
||||
byte[] nonce;
|
||||
|
||||
Map<ByteArrayWrapper, ByteArrayWrapper> storage = new HashMap<>();
|
||||
Map<DataWord, DataWord> storage = new HashMap<>();
|
||||
|
||||
|
||||
public AccountState(byte[] address, JSONObject accountState) {
|
||||
|
@ -52,28 +53,9 @@ public class AccountState {
|
|||
String keyS = keys[i].toString();
|
||||
String valS = store.get(keys[i]).toString();
|
||||
|
||||
ByteArrayWrapper key;
|
||||
boolean hexVal = Pattern.matches("0[xX][0-9a-fA-F]+", keyS);
|
||||
if (hexVal) {
|
||||
key = new ByteArrayWrapper(Hex.decode(keyS.substring(2)));
|
||||
} else {
|
||||
byte[] data;
|
||||
if (keyS != null && keyS.length() > 2)
|
||||
data = Hex.decode(keyS);
|
||||
else
|
||||
data = ByteUtil.EMPTY_BYTE_ARRAY;
|
||||
key = new ByteArrayWrapper(data);
|
||||
}
|
||||
|
||||
ByteArrayWrapper value;
|
||||
hexVal = Pattern.matches("0[xX][0-9a-fA-F]+", valS);
|
||||
if (hexVal) {
|
||||
value = new ByteArrayWrapper(Hex.decode(valS.substring(2)));
|
||||
} else {
|
||||
byte[] data = ByteUtil.bigIntegerToBytes(new BigInteger(valS));
|
||||
value = new ByteArrayWrapper(data);
|
||||
}
|
||||
storage.put(key, value);
|
||||
byte[] key = Utils.parseData(keyS);
|
||||
byte[] value = Utils.parseData(valS);
|
||||
storage.put(new DataWord(key), new DataWord(value));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,10 +85,81 @@ public class AccountState {
|
|||
}
|
||||
|
||||
|
||||
public Map<ByteArrayWrapper, ByteArrayWrapper> getStorage() {
|
||||
public Map<DataWord, DataWord> getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
public List<String> compareToReal(org.ethereum.core.AccountState state, ContractDetails details){
|
||||
|
||||
List<String> results = new ArrayList<>();
|
||||
|
||||
BigInteger expectedBalance = new BigInteger(1, this.getBalance());
|
||||
if (!state.getBalance().equals(expectedBalance)){
|
||||
String formatedString = String.format("Account: %s: has unexpected balance, expected balance: %s found balance: %s",
|
||||
Hex.toHexString(this.address), expectedBalance.toString(), state.getBalance().toString());
|
||||
results.add(formatedString);
|
||||
}
|
||||
|
||||
BigInteger expectedNonce = new BigInteger(1, this.getNonce());
|
||||
if (!state.getNonce().equals(expectedNonce)) {
|
||||
state.getNonce();
|
||||
this.getNonce();
|
||||
String formatedString = String.format("Account: %s: has unexpected nonce, expected nonce: %s found nonce: %s",
|
||||
Hex.toHexString(this.address), expectedNonce.toString(), state.getNonce().toString() );
|
||||
results.add(formatedString);
|
||||
}
|
||||
|
||||
if (!Arrays.equals(details.getCode(),this.getCode())) {
|
||||
String formatedString = String.format("Account: %s: has unexpected nonce, expected nonce: %s found nonce: %s",
|
||||
Hex.toHexString(this.address), Hex.toHexString( this.getCode() ), Hex.toHexString(details.getCode()));
|
||||
results.add(formatedString);
|
||||
}
|
||||
|
||||
|
||||
// compare storage
|
||||
Set<DataWord> keys = details.getStorage().keySet();
|
||||
Set<DataWord> expectedKeys = this.getStorage().keySet();
|
||||
Set<DataWord> checked = new HashSet<>();
|
||||
|
||||
for (DataWord key : keys){
|
||||
|
||||
DataWord value = details.getStorage().get(key);
|
||||
DataWord expectedValue = this.getStorage().get(key);
|
||||
if (expectedValue == null) {
|
||||
|
||||
String formatedString = String.format("Account: %s: has unexpected storage data: %s = %s",
|
||||
Hex.toHexString(this.address),
|
||||
key.toString(),
|
||||
value.toString());
|
||||
|
||||
results.add(formatedString);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!expectedValue.equals(value)){
|
||||
|
||||
String formatedString = String.format("Account: %s: has unexpected value, for key: %s , expectedValue: %s real value: %s",
|
||||
Hex.toHexString(this.address), key.toString(),
|
||||
expectedValue.toString(), value.toString());
|
||||
results.add(formatedString);
|
||||
continue;
|
||||
}
|
||||
|
||||
checked.add(key);
|
||||
}
|
||||
|
||||
for (DataWord key : expectedKeys){
|
||||
if (!checked.contains(key)){
|
||||
String formatedString = String.format("Account: %s: doesn't exist expected storage key: %s",
|
||||
Hex.toHexString(this.address), key.toString());
|
||||
results.add(formatedString);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AccountState{" +
|
||||
|
|
|
@ -7,7 +7,7 @@ import org.spongycastle.util.encoders.Hex;
|
|||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* www.ethereumJ.com
|
||||
* www.etherj.com
|
||||
*
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 28/06/2014 10:25
|
||||
|
|
|
@ -41,4 +41,68 @@ public class Logs {
|
|||
public Iterator<LogInfo> getIterator(){
|
||||
return logs.iterator();
|
||||
}
|
||||
|
||||
|
||||
public List<String> compareToReal(List<LogInfo> logs){
|
||||
|
||||
List<String> results = new ArrayList<>();
|
||||
|
||||
int i = 0;
|
||||
for (LogInfo postLog : this.logs){
|
||||
|
||||
LogInfo realLog = logs.get(i);
|
||||
|
||||
String postAddress = Hex.toHexString(postLog.getAddress());
|
||||
String realAddress = Hex.toHexString(realLog.getAddress());
|
||||
|
||||
if (!postAddress.equals(realAddress)){
|
||||
|
||||
String formatedString = String.format("Log: %s: has unexpected address, expected address: %s found address: %s",
|
||||
i, postAddress, realAddress);
|
||||
results.add(formatedString);
|
||||
}
|
||||
|
||||
String postData = Hex.toHexString(postLog.getData());
|
||||
String realData = Hex.toHexString(realLog.getData());
|
||||
|
||||
if (!postData.equals(realData)){
|
||||
|
||||
String formatedString = String.format("Log: %s: has unexpected data, expected data: %s found data: %s",
|
||||
i, postData, realData);
|
||||
results.add(formatedString);
|
||||
}
|
||||
|
||||
String postBloom = Hex.toHexString(postLog.getBloom().getData());
|
||||
String realBloom = Hex.toHexString(realLog.getBloom().getData());
|
||||
|
||||
if (!postData.equals(realData)){
|
||||
|
||||
String formatedString = String.format("Log: %s: has unexpected bloom, expected bloom: %s found bloom: %s",
|
||||
i, postBloom, realBloom);
|
||||
results.add(formatedString);
|
||||
}
|
||||
|
||||
List<DataWord> postTopics = postLog.getTopics();
|
||||
List<DataWord> realTopics = realLog.getTopics();
|
||||
|
||||
int j = 0;
|
||||
for (DataWord postTopic : postTopics){
|
||||
|
||||
DataWord realTopic = realTopics.get(j);
|
||||
|
||||
if (!postTopic.equals(realTopic)){
|
||||
|
||||
String formatedString = String.format("Log: %s: has unexpected topic: %s, expected topic: %s found topic: %s",
|
||||
i, j, postTopic, realTopic);
|
||||
results.add(formatedString);
|
||||
}
|
||||
++j;
|
||||
}
|
||||
|
||||
++i;
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
package org.ethereum.jsontestsuite;
|
||||
|
||||
import org.ethereum.db.ByteArrayWrapper;
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* www.etherj.com
|
||||
*
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 15/12/2014 12:24
|
||||
*/
|
||||
|
||||
public class StateTestCase {
|
||||
|
||||
private String name = "";
|
||||
|
||||
private Env env;
|
||||
private Logs logs;
|
||||
private byte[] out;
|
||||
|
||||
// "pre": { ... },
|
||||
private Map<ByteArrayWrapper, AccountState> pre = new HashMap<>();
|
||||
|
||||
// "post": { ... },
|
||||
private Map<ByteArrayWrapper, AccountState> post = new HashMap<>();
|
||||
|
||||
private Transaction transaction;
|
||||
|
||||
public StateTestCase(String name, JSONObject testCaseJSONObj) throws ParseException {
|
||||
|
||||
this(testCaseJSONObj);
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public StateTestCase(JSONObject testCaseJSONObj) throws ParseException{
|
||||
|
||||
try {
|
||||
|
||||
JSONObject envJSON = (JSONObject)testCaseJSONObj.get("env");
|
||||
JSONArray logsJSON = (JSONArray)testCaseJSONObj.get("logs");
|
||||
String outStr = testCaseJSONObj.get("out").toString();
|
||||
JSONObject txJSON = (JSONObject)testCaseJSONObj.get("transaction");
|
||||
|
||||
JSONObject preJSON = (JSONObject)testCaseJSONObj.get("pre");
|
||||
JSONObject postJSON = (JSONObject)testCaseJSONObj.get("post");
|
||||
|
||||
this.env = new Env(envJSON);
|
||||
this.logs = new Logs(logsJSON);
|
||||
this.out = Utils.parseData(outStr);
|
||||
this.transaction = new Transaction(txJSON);
|
||||
|
||||
for (Object key : preJSON.keySet()){
|
||||
|
||||
byte[] keyBytes = Hex.decode(key.toString());
|
||||
AccountState accountState =
|
||||
new AccountState(keyBytes, (JSONObject) preJSON.get(key));
|
||||
|
||||
pre.put(new ByteArrayWrapper(keyBytes), accountState);
|
||||
}
|
||||
|
||||
for (Object key : postJSON.keySet()){
|
||||
|
||||
byte[] keyBytes = Hex.decode(key.toString());
|
||||
AccountState accountState =
|
||||
new AccountState(keyBytes, (JSONObject) postJSON.get(key));
|
||||
|
||||
post.put(new ByteArrayWrapper(keyBytes), accountState);
|
||||
}
|
||||
|
||||
|
||||
} catch (Throwable e) {
|
||||
throw new ParseException(0, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Env getEnv() {
|
||||
return env;
|
||||
}
|
||||
|
||||
public Logs getLogs() {
|
||||
return logs;
|
||||
}
|
||||
|
||||
public byte[] getOut() {
|
||||
return out;
|
||||
}
|
||||
|
||||
public Map<ByteArrayWrapper, AccountState> getPre() {
|
||||
return pre;
|
||||
}
|
||||
|
||||
public Map<ByteArrayWrapper, AccountState> getPost() {
|
||||
return post;
|
||||
}
|
||||
|
||||
public Transaction getTransaction() {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StateTestCase{" +
|
||||
"name='" + name + '\'' +
|
||||
", env=" + env +
|
||||
", logs=" + logs +
|
||||
", out=" + Arrays.toString(out) +
|
||||
", pre=" + pre +
|
||||
", post=" + post +
|
||||
", transaction=" + transaction +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package org.ethereum.jsontestsuite;
|
||||
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* www.ethereumJ.com
|
||||
*
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 10/07/2014 09:46
|
||||
*/
|
||||
|
||||
public class StateTestSuite {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger("TCK-Test");
|
||||
Map<String, StateTestCase> testCases = new HashMap<>();
|
||||
|
||||
public StateTestSuite(JSONObject testCaseJSONObj) throws ParseException {
|
||||
|
||||
for (Object key: testCaseJSONObj.keySet()){
|
||||
|
||||
Object testCaseJSON = testCaseJSONObj.get(key);
|
||||
|
||||
StateTestCase testCase = new StateTestCase(key.toString(), (JSONObject) testCaseJSON);
|
||||
|
||||
testCases.put(key.toString(), testCase);
|
||||
}
|
||||
}
|
||||
|
||||
public StateTestCase getTestCase(String name){
|
||||
|
||||
StateTestCase testCase = testCases.get(name);
|
||||
if (testCase == null) throw new NullPointerException("Test cases doesn't exist: " + name);
|
||||
|
||||
return testCase;
|
||||
}
|
||||
|
||||
public Collection<StateTestCase> getAllTests(){
|
||||
return testCases.values();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package org.ethereum.jsontestsuite;
|
||||
|
||||
import org.ethereum.core.*;
|
||||
import org.ethereum.core.Transaction;
|
||||
import org.ethereum.facade.Repository;
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.vm.*;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* www.ethergit.com
|
||||
*
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 19/12/2014 12:22
|
||||
*/
|
||||
|
||||
public class TestProgramInvokeFactory implements ProgramInvokeFactory{
|
||||
|
||||
Env env;
|
||||
|
||||
TestProgramInvokeFactory(Env env){
|
||||
this.env = env;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository repository) {
|
||||
return generalInvoke(tx, repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, DataWord inValue, DataWord inGas,
|
||||
BigInteger balanceInt, byte[] dataIn, Repository repository) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private ProgramInvoke generalInvoke(Transaction tx, Repository repository){
|
||||
|
||||
/*** ADDRESS op ***/
|
||||
// YP: Get address of currently executing account.
|
||||
byte[] address = tx.isContractCreation() ? tx.getContractAddress(): tx.getReceiveAddress();
|
||||
|
||||
/*** ORIGIN op ***/
|
||||
// YP: This is the sender of original transaction; it is never a contract.
|
||||
byte[] origin = tx.getSender();
|
||||
|
||||
/*** CALLER op ***/
|
||||
// YP: This is the address of the account that is directly responsible for this execution.
|
||||
byte[] caller = tx.getSender();
|
||||
|
||||
/*** BALANCE op ***/
|
||||
byte[] balance = repository.getBalance(address).toByteArray();
|
||||
|
||||
/*** GASPRICE op ***/
|
||||
byte[] gasPrice = tx.getGasPrice();
|
||||
|
||||
/*** GAS op ***/
|
||||
byte[] gas = tx.getGasLimit();
|
||||
|
||||
/*** CALLVALUE op ***/
|
||||
byte[] callValue = tx.getValue() == null ? new byte[]{0} : tx.getValue();
|
||||
|
||||
/*** CALLDATALOAD op ***/
|
||||
/*** CALLDATACOPY op ***/
|
||||
/*** CALLDATASIZE op ***/
|
||||
byte[] data = tx.getData() == null ? ByteUtil.EMPTY_BYTE_ARRAY : tx.getData();
|
||||
|
||||
/*** PREVHASH op ***/
|
||||
byte[] lastHash = env.getPreviousHash();
|
||||
|
||||
/*** COINBASE op ***/
|
||||
byte[] coinbase = env.getCurrentCoinbase();
|
||||
|
||||
/*** TIMESTAMP op ***/
|
||||
long timestamp = ByteUtil.byteArrayToLong(env.getCurrentTimestamp());
|
||||
|
||||
/*** NUMBER op ***/
|
||||
long number = ByteUtil.byteArrayToLong(env.getCurrentNumber());
|
||||
|
||||
/*** DIFFICULTY op ***/
|
||||
byte[] difficulty = env.getCurrentDifficlty();
|
||||
|
||||
/*** GASLIMIT op ***/
|
||||
long gaslimit = ByteUtil.byteArrayToLong( env.getCurrentGasLimit() );
|
||||
|
||||
return new ProgramInvokeImpl(address, origin, caller, balance,
|
||||
gasPrice, gas, callValue, data, lastHash, coinbase,
|
||||
timestamp, number, difficulty, gaslimit, repository);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
package org.ethereum.jsontestsuite;
|
||||
|
||||
import org.ethereum.core.BlockchainImpl;
|
||||
import org.ethereum.core.TransactionExecutor;
|
||||
import org.ethereum.db.ByteArrayWrapper;
|
||||
import org.ethereum.db.ContractDetails;
|
||||
import org.ethereum.db.RepositoryImpl;
|
||||
import org.ethereum.db.RepositoryDummy;
|
||||
import org.ethereum.db.RepositoryTrack;
|
||||
import org.ethereum.facade.Repository;
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.vm.*;
|
||||
|
@ -14,6 +17,9 @@ import org.spongycastle.util.encoders.Hex;
|
|||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
|
||||
import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY;
|
||||
import static org.ethereum.util.ByteUtil.wrap;
|
||||
|
||||
/**
|
||||
* www.ethereumJ.com
|
||||
*
|
||||
|
@ -23,7 +29,7 @@ import java.util.*;
|
|||
|
||||
public class TestRunner {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger("JSONTest");
|
||||
private Logger logger = LoggerFactory.getLogger("TCK-Test");
|
||||
private ProgramTrace trace = null;
|
||||
|
||||
public List<String> runTestSuite(TestSuite testSuite) {
|
||||
|
@ -43,30 +49,96 @@ public class TestRunner {
|
|||
return resultCollector;
|
||||
}
|
||||
|
||||
public List<String> runTestCase(StateTestCase testCase) {
|
||||
|
||||
List<String> results = null;
|
||||
logger.info("\n***");
|
||||
logger.info(" Running test case: [" + testCase.getName() + "]") ;
|
||||
logger.info("***\n");
|
||||
results = new ArrayList<>();
|
||||
|
||||
logger.info("--------- PRE ---------");
|
||||
RepositoryDummy repository = loadRepository(testCase.getPre());
|
||||
|
||||
logger.info("loaded repository");
|
||||
|
||||
org.ethereum.core.Transaction tx = createTransaction(testCase.getTransaction());
|
||||
logger.info("transaction: {}", tx.toString());
|
||||
|
||||
byte[] secretKey = testCase.getTransaction().secretKey;
|
||||
logger.info("sign tx with: {}", Hex.toHexString(secretKey));
|
||||
tx.sign(secretKey);
|
||||
|
||||
BlockchainImpl blockchain = new BlockchainImpl();
|
||||
blockchain.setRepository(repository);
|
||||
|
||||
byte[] coinbase = testCase.getEnv().getCurrentCoinbase();
|
||||
ProgramInvokeFactory invokeFactory = new TestProgramInvokeFactory(testCase.getEnv());
|
||||
|
||||
blockchain.setProgramInvokeFactory(invokeFactory);
|
||||
blockchain.startTracking();
|
||||
|
||||
Repository track = repository.startTracking();
|
||||
TransactionExecutor executor = new TransactionExecutor(tx, coinbase, track,
|
||||
invokeFactory, null);
|
||||
executor.execute();
|
||||
track.commit();
|
||||
|
||||
logger.info("compare results");
|
||||
|
||||
// todo: perform logs comparision
|
||||
List<LogInfo> logs = executor.getResult().getLogInfoList();
|
||||
List<String> logResults = testCase.getLogs().compareToReal(logs);
|
||||
results.addAll(logResults);
|
||||
|
||||
Set<ByteArrayWrapper> fullAddressSet = repository.getFullAddressSet();
|
||||
int repoSize = 0;
|
||||
for (ByteArrayWrapper addrWrapped : fullAddressSet){
|
||||
|
||||
byte[] addr = addrWrapped.getData();
|
||||
|
||||
org.ethereum.core.AccountState accountState = repository.getAccountState(addr);
|
||||
ContractDetails contractDetails = repository.getContractDetails(addr);
|
||||
|
||||
logger.info("{} \n{} \n{}", Hex.toHexString(addr),
|
||||
accountState.toString(), contractDetails.toString() );
|
||||
logger.info("");
|
||||
|
||||
AccountState expectedAccountState = testCase.getPost().get(wrap(addr));
|
||||
if (expectedAccountState == null){
|
||||
String formatedString = String.format("Unexpected account state: address: %s", Hex.toHexString(addr));
|
||||
results.add(formatedString);
|
||||
continue;
|
||||
}
|
||||
|
||||
List<String> result = expectedAccountState.compareToReal(accountState, contractDetails);
|
||||
results.addAll(result);
|
||||
|
||||
++repoSize;
|
||||
}
|
||||
|
||||
int postRepoSize = testCase.getPost().size();
|
||||
|
||||
if (postRepoSize > repoSize){
|
||||
results.add("ERROR: Post repository contains more accounts than executed repository ");
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
public List<String> runTestCase(TestCase testCase) {
|
||||
|
||||
Repository repository = new RepositoryImpl();
|
||||
logger.info("\n***");
|
||||
logger.info(" Running test case: [" + testCase.getName() + "]") ;
|
||||
logger.info("***\n");
|
||||
List<String> results = new ArrayList<>();
|
||||
|
||||
|
||||
logger.info("--------- PRE ---------");
|
||||
RepositoryDummy repository = loadRepository(testCase.getPre());
|
||||
|
||||
try {
|
||||
System.out.println("\n***");
|
||||
System.out.println(" Running test case: [" + testCase.getName() + "]") ;
|
||||
System.out.println("***\n");
|
||||
List<String> results = new ArrayList<>();
|
||||
|
||||
System.out.println("--------- PRE ---------");
|
||||
/* 1. Store pre-exist accounts - Pre */
|
||||
for (ByteArrayWrapper key : testCase.getPre().keySet()) {
|
||||
|
||||
AccountState accountState = testCase.getPre().get(key);
|
||||
|
||||
repository.createAccount(key.getData());
|
||||
repository.saveCode(key.getData(), accountState.getCode());
|
||||
repository.addBalance(key.getData(), new BigInteger(accountState.getBalance()));
|
||||
|
||||
for (long i = 0; i < accountState.getNonceLong(); ++i)
|
||||
repository.increaseNonce(key.getData());
|
||||
}
|
||||
|
||||
/* 2. Create ProgramInvoke - Env/Exec */
|
||||
Env env = testCase.getEnv();
|
||||
|
@ -191,8 +263,8 @@ public class TestRunner {
|
|||
}
|
||||
|
||||
// assert storage
|
||||
Map<ByteArrayWrapper, ByteArrayWrapper> storage = accountState.getStorage();
|
||||
for (ByteArrayWrapper storageKey : storage.keySet()) {
|
||||
Map<DataWord, DataWord> storage = accountState.getStorage();
|
||||
for (DataWord storageKey : storage.keySet()) {
|
||||
|
||||
byte[] expectedStValue = storage.get(storageKey).getData();
|
||||
|
||||
|
@ -381,7 +453,7 @@ public class TestRunner {
|
|||
|
||||
// assert out
|
||||
byte[] expectedHReturn = testCase.getOut();
|
||||
byte[] actualHReturn = ByteUtil.EMPTY_BYTE_ARRAY;
|
||||
byte[] actualHReturn = EMPTY_BYTE_ARRAY;
|
||||
if (program.getResult().getHReturn() != null) {
|
||||
actualHReturn = program.getResult().getHReturn().array();
|
||||
}
|
||||
|
@ -420,6 +492,47 @@ public class TestRunner {
|
|||
}
|
||||
}
|
||||
|
||||
public org.ethereum.core.Transaction createTransaction(Transaction tx){
|
||||
|
||||
byte[] nonceBytes = ByteUtil.longToBytes(tx.nonce);
|
||||
byte[] gasPriceBytes = ByteUtil.longToBytes(tx.gasPrice);
|
||||
byte[] gasBytes = ByteUtil.longToBytes(tx.gasLimit);
|
||||
byte[] valueBytes = ByteUtil.longToBytes(tx.value);
|
||||
byte[] toAddr = tx.getTo();
|
||||
byte[] data = tx.getData();
|
||||
|
||||
org.ethereum.core.Transaction transaction = new org.ethereum.core.Transaction(
|
||||
nonceBytes, gasPriceBytes, gasBytes,
|
||||
toAddr, valueBytes, data);
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
public RepositoryDummy loadRepository(Map<ByteArrayWrapper, AccountState> pre){
|
||||
|
||||
|
||||
RepositoryDummy track = new RepositoryDummy();
|
||||
|
||||
/* 1. Store pre-exist accounts - Pre */
|
||||
for (ByteArrayWrapper key : pre.keySet()) {
|
||||
|
||||
AccountState accountState = pre.get(key);
|
||||
byte[] addr = key.getData();
|
||||
|
||||
track.addBalance(addr, new BigInteger(1, accountState.getBalance()));
|
||||
track.setNonce(key.getData(), new BigInteger(1, accountState.getNonce()));
|
||||
|
||||
track.saveCode(addr, accountState.getCode());
|
||||
|
||||
for (DataWord storageKey : accountState.getStorage().keySet()){
|
||||
track.addStorageRow(addr, storageKey, accountState.getStorage().get(storageKey));
|
||||
}
|
||||
}
|
||||
|
||||
return track;
|
||||
}
|
||||
|
||||
|
||||
public ProgramTrace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
|
|
|
@ -21,9 +21,7 @@ public class TestSuite {
|
|||
for (Object key: testCaseJSONObj.keySet()){
|
||||
|
||||
Object testCaseJSON = testCaseJSONObj.get(key);
|
||||
|
||||
TestCase testCase = new TestCase(key.toString(), (JSONObject) testCaseJSON);
|
||||
|
||||
testList.add(testCase);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,101 @@
|
|||
package org.ethereum.jsontestsuite;
|
||||
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.ethereum.util.ByteUtil.toHexString;
|
||||
|
||||
/**
|
||||
* www.etherj.com
|
||||
*
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 28/06/2014 10:25
|
||||
*/
|
||||
|
||||
public class Transaction {
|
||||
|
||||
byte[] data;
|
||||
long gasLimit;
|
||||
long gasPrice;
|
||||
long nonce;
|
||||
byte[] secretKey;
|
||||
byte[] to;
|
||||
long value;
|
||||
|
||||
/* e.g.
|
||||
"transaction" : {
|
||||
"data" : "",
|
||||
"gasLimit" : "10000",
|
||||
"gasPrice" : "1",
|
||||
"nonce" : "0",
|
||||
"secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8",
|
||||
"to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87",
|
||||
"value" : "100000"
|
||||
}
|
||||
*/
|
||||
|
||||
public Transaction(JSONObject callCreateJSON) {
|
||||
|
||||
String dataStr = callCreateJSON.get("data").toString();
|
||||
String gasLimitStr = callCreateJSON.get("gasLimit").toString();
|
||||
String gasPriceStr = callCreateJSON.get("gasPrice").toString();
|
||||
String nonceStr = callCreateJSON.get("nonce").toString();
|
||||
String secretKeyStr = callCreateJSON.get("secretKey").toString();
|
||||
String toStr = callCreateJSON.get("to").toString();
|
||||
String valueStr = callCreateJSON.get("value").toString();
|
||||
|
||||
|
||||
this.data = Utils.parseData(dataStr);
|
||||
this.gasLimit = Utils.parseLong(gasLimitStr);
|
||||
this.gasPrice = Utils.parseLong(gasPriceStr);
|
||||
this.nonce = Utils.parseLong(nonceStr);
|
||||
this.secretKey = Utils.parseData(secretKeyStr);
|
||||
this.to = Utils.parseData(toStr);
|
||||
this.value = Utils.parseLong(valueStr);
|
||||
}
|
||||
|
||||
public byte[] getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public long getGasLimit() {
|
||||
return gasLimit;
|
||||
}
|
||||
|
||||
public long getGasPrice() {
|
||||
return gasPrice;
|
||||
}
|
||||
|
||||
public long getNonce() {
|
||||
return nonce;
|
||||
}
|
||||
|
||||
public byte[] getSecretKey() {
|
||||
return secretKey;
|
||||
}
|
||||
|
||||
public byte[] getTo() {
|
||||
return to;
|
||||
}
|
||||
|
||||
public long getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Transaction{" +
|
||||
"data=" + toHexString(data) +
|
||||
", gasLimit=" + gasLimit +
|
||||
", gasPrice=" + gasPrice +
|
||||
", nonce=" + nonce +
|
||||
", secretKey=" + toHexString(secretKey) +
|
||||
", to=" + toHexString(to) +
|
||||
", value=" + value +
|
||||
'}';
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package org.ethereum.jsontestsuite;
|
||||
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.spongycastle.pqc.math.linearalgebra.ByteUtils;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
/**
|
||||
* www.ethergit.com
|
||||
*
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 15/12/2014 12:41
|
||||
*/
|
||||
|
||||
public class Utils {
|
||||
|
||||
|
||||
public static byte[] parseData(String data){
|
||||
if (data == null) return ByteUtil.EMPTY_BYTE_ARRAY;
|
||||
if (data.startsWith("0x")) data = data.substring(2);
|
||||
return Hex.decode(data);
|
||||
}
|
||||
|
||||
public static long parseLong(String data){
|
||||
return data.equals("") ? 0 : Long.parseLong(data);
|
||||
}
|
||||
|
||||
}
|
|
@ -3,6 +3,7 @@ package org.ethereum.listener;
|
|||
import org.ethereum.core.Block;
|
||||
import org.ethereum.core.Transaction;
|
||||
import org.ethereum.net.message.Message;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
@ -12,7 +13,7 @@ import java.util.Set;
|
|||
* @author: Roman Mandeleil
|
||||
* Created on: 12/11/2014 08:34
|
||||
*/
|
||||
|
||||
@Component(value="EthereumListener")
|
||||
public class EthereumListenerWrapper implements EthereumListener{
|
||||
|
||||
EthereumListener listener;
|
||||
|
|
|
@ -60,8 +60,8 @@ public class WorldManager {
|
|||
|
||||
private final Set<Transaction> pendingTransactions = Collections.synchronizedSet(new HashSet<Transaction>());
|
||||
|
||||
|
||||
private EthereumListener listener = new EthereumListenerWrapper();
|
||||
@Autowired
|
||||
private EthereumListener listener;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
|
|
|
@ -139,6 +139,21 @@ public class ByteUtil {
|
|||
return new BigInteger(1, b).intValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast hex encoded value from byte[] to int
|
||||
*
|
||||
* Limited to Integer.MAX_VALUE: 2^32-1 (4 bytes)
|
||||
*
|
||||
* @param b array contains the values
|
||||
* @return unsigned positive long value.
|
||||
*/
|
||||
public static long byteArrayToLong(byte[] b) {
|
||||
if (b == null || b.length == 0)
|
||||
return 0;
|
||||
return new BigInteger(1, b).longValue();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Turn nibbles to a pretty looking output string
|
||||
*
|
||||
|
|
|
@ -26,6 +26,8 @@ public class GasCost {
|
|||
public static int SSTORE = 300;
|
||||
/** Cost 100 gas */
|
||||
public static int RESET_SSTORE = 100;
|
||||
/** Cost 100 gas */
|
||||
public static int REFUND_SSTORE = 100;
|
||||
/** Cost 100 gas */
|
||||
public static int CREATE = 100;
|
||||
/** Cost 20 gas */
|
||||
|
|
|
@ -13,6 +13,7 @@ import org.ethereum.vmtrace.ProgramTrace;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
|
@ -34,6 +35,8 @@ public class Program {
|
|||
private static final Logger logger = LoggerFactory.getLogger("VM");
|
||||
private static final Logger gasLogger = LoggerFactory.getLogger("gas");
|
||||
|
||||
ProgramInvokeFactory programInvokeFactory = new ProgramInvokeFactoryImpl();
|
||||
|
||||
private int invokeHash;
|
||||
private ProgramListener listener;
|
||||
|
||||
|
@ -181,7 +184,7 @@ public class Program {
|
|||
public void stackRequire(int stackSize) {
|
||||
if (stack.size() < stackSize) {
|
||||
throw new StackTooSmallException("Expected: " + stackSize
|
||||
+ ", found" + stack.size());
|
||||
+ ", found: " + stack.size());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -318,14 +321,14 @@ public class Program {
|
|||
result.getRepository().addBalance(senderAddress, endowment.negate());
|
||||
BigInteger newBalance = result.getRepository().addBalance(newAddress, endowment);
|
||||
|
||||
Repository track = result.getRepository().startTracking();
|
||||
|
||||
// [3] UPDATE THE NONCE
|
||||
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
|
||||
track.increaseNonce(senderAddress);
|
||||
result.getRepository().increaseNonce(senderAddress);
|
||||
|
||||
Repository track = result.getRepository().startTracking();
|
||||
|
||||
// [5] COOK THE INVOKE AND EXECUTE
|
||||
ProgramInvoke programInvoke = ProgramInvokeFactory.createProgramInvoke(
|
||||
ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke(
|
||||
this, new DataWord(newAddress), DataWord.ZERO, gasLimit,
|
||||
newBalance, null, track);
|
||||
|
||||
|
@ -427,7 +430,7 @@ public class Program {
|
|||
this.spendGas(msg.getGas().longValue(), "internal call");
|
||||
|
||||
Repository trackRepository = result.getRepository().startTracking();
|
||||
ProgramInvoke programInvoke = ProgramInvokeFactory.createProgramInvoke(
|
||||
ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke(
|
||||
this, new DataWord(contextAddress), msg.getEndowment(),
|
||||
msg.getGas(), contextBalance, data, trackRepository);
|
||||
|
||||
|
@ -507,6 +510,10 @@ public class Program {
|
|||
result.refundGas(gasValue);
|
||||
}
|
||||
|
||||
public void futureRefundGas(long gasValue) {
|
||||
result.futureRefundGas(gasValue);
|
||||
}
|
||||
|
||||
public void storageSave(DataWord word1, DataWord word2) {
|
||||
storageSave(word1.getData(), word2.getData());
|
||||
}
|
||||
|
|
|
@ -2,196 +2,24 @@ package org.ethereum.vm;
|
|||
|
||||
import org.ethereum.core.Block;
|
||||
import org.ethereum.core.Transaction;
|
||||
import org.ethereum.facade.Blockchain;
|
||||
import org.ethereum.facade.Repository;
|
||||
import org.ethereum.manager.WorldManager;
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* www.ethereumJ.com
|
||||
* www.etherj.com
|
||||
*
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 08/06/2014 09:59
|
||||
* Created on: 19/12/2014 12:14
|
||||
*/
|
||||
@Component
|
||||
public class ProgramInvokeFactory {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger("VM");
|
||||
public interface ProgramInvokeFactory {
|
||||
|
||||
@Autowired
|
||||
private Blockchain blockchain;
|
||||
|
||||
/**
|
||||
* This attribute defines the number of recursive calls allowed in the EVM
|
||||
* Note: For the JVM to reach this level without a StackOverflow exception,
|
||||
* ethereumj may need to be started with a JVM argument to increase
|
||||
* the stack size. For example: -Xss10m
|
||||
*/
|
||||
private static final int MAX_DEPTH = 1024;
|
||||
|
||||
// Invocation by the wire tx
|
||||
public ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository repository) {
|
||||
|
||||
// https://ethereum.etherpad.mozilla.org/26
|
||||
Block lastBlock = blockchain.getBestBlock();
|
||||
|
||||
/*** ADDRESS op ***/
|
||||
// YP: Get address of currently executing account.
|
||||
byte[] address = tx.isContractCreation() ? tx.getContractAddress(): tx.getReceiveAddress();
|
||||
|
||||
/*** ORIGIN op ***/
|
||||
// YP: This is the sender of original transaction; it is never a contract.
|
||||
byte[] origin = tx.getSender();
|
||||
|
||||
/*** CALLER op ***/
|
||||
// YP: This is the address of the account that is directly responsible for this execution.
|
||||
byte[] caller = tx.getSender();
|
||||
|
||||
/*** BALANCE op ***/
|
||||
byte[] balance = repository.getBalance(address).toByteArray();
|
||||
|
||||
/*** GASPRICE op ***/
|
||||
byte[] gasPrice = tx.getGasPrice();
|
||||
|
||||
/*** GAS op ***/
|
||||
byte[] gas = tx.getGasLimit();
|
||||
|
||||
/*** CALLVALUE op ***/
|
||||
byte[] callValue = tx.getValue() == null ? new byte[]{0} : tx.getValue();
|
||||
|
||||
/*** CALLDATALOAD op ***/
|
||||
/*** CALLDATACOPY op ***/
|
||||
/*** CALLDATASIZE op ***/
|
||||
byte[] data = tx.getData() == null ? ByteUtil.EMPTY_BYTE_ARRAY : tx.getData();
|
||||
|
||||
/*** PREVHASH op ***/
|
||||
byte[] lastHash = lastBlock.getHash();
|
||||
|
||||
/*** COINBASE op ***/
|
||||
byte[] coinbase = block.getCoinbase();
|
||||
|
||||
/*** TIMESTAMP op ***/
|
||||
long timestamp = block.getTimestamp();
|
||||
|
||||
/*** NUMBER op ***/
|
||||
long number = block.getNumber();
|
||||
|
||||
/*** DIFFICULTY op ***/
|
||||
byte[] difficulty = block.getDifficulty();
|
||||
|
||||
/*** GASLIMIT op ***/
|
||||
long gaslimit = block.getGasLimit();
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Top level call: \n" +
|
||||
"address={}\n" +
|
||||
"origin={}\n" +
|
||||
"caller={}\n" +
|
||||
"balance={}\n" +
|
||||
"gasPrice={}\n" +
|
||||
"gas={}\n" +
|
||||
"callValue={}\n" +
|
||||
"data={}\n" +
|
||||
"lastHash={}\n" +
|
||||
"coinbase={}\n" +
|
||||
"timestamp={}\n" +
|
||||
"blockNumber={}\n" +
|
||||
"difficulty={}\n" +
|
||||
"gaslimit={}\n",
|
||||
|
||||
Hex.toHexString(address),
|
||||
Hex.toHexString(origin),
|
||||
Hex.toHexString(caller),
|
||||
new BigInteger(balance).longValue(),
|
||||
new BigInteger(gasPrice).longValue(),
|
||||
new BigInteger(gas).longValue(),
|
||||
new BigInteger(callValue).longValue(),
|
||||
Hex.toHexString(data),
|
||||
Hex.toHexString(lastHash),
|
||||
Hex.toHexString(coinbase),
|
||||
timestamp,
|
||||
number,
|
||||
Hex.toHexString(difficulty),
|
||||
gaslimit);
|
||||
}
|
||||
|
||||
ProgramInvoke programInvoke =
|
||||
new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue, data,
|
||||
lastHash, coinbase, timestamp, number, difficulty, gaslimit,
|
||||
repository);
|
||||
|
||||
return programInvoke;
|
||||
}
|
||||
|
||||
/**
|
||||
* This invocation created for contract call contract
|
||||
*/
|
||||
public static ProgramInvoke createProgramInvoke(Program program, DataWord toAddress,
|
||||
public ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository repository);
|
||||
public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress,
|
||||
DataWord inValue, DataWord inGas,
|
||||
BigInteger balanceInt, byte[] dataIn,
|
||||
Repository repository) {
|
||||
Repository repository);
|
||||
|
||||
DataWord address = toAddress;
|
||||
DataWord origin = program.getOriginAddress();
|
||||
DataWord caller = program.getOwnerAddress();
|
||||
|
||||
DataWord balance = new DataWord(balanceInt.toByteArray());
|
||||
DataWord gasPrice = program.getGasPrice();
|
||||
DataWord gas = inGas;
|
||||
DataWord callValue = inValue;
|
||||
|
||||
byte[] data = dataIn;
|
||||
DataWord lastHash = program.getPrevHash();
|
||||
DataWord coinbase = program.getCoinbase();
|
||||
DataWord timestamp = program.getTimestamp();
|
||||
DataWord number = program.getNumber();
|
||||
DataWord difficulty = program.getDifficulty();
|
||||
DataWord gasLimit = program.getGaslimit();
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Internal call: \n" +
|
||||
"address={}\n" +
|
||||
"origin={}\n" +
|
||||
"caller={}\n" +
|
||||
"balance={}\n" +
|
||||
"gasPrice={}\n" +
|
||||
"gas={}\n" +
|
||||
"callValue={}\n" +
|
||||
"data={}\n" +
|
||||
"lastHash={}\n" +
|
||||
"coinbase={}\n" +
|
||||
"timestamp={}\n" +
|
||||
"blockNumber={}\n" +
|
||||
"difficulty={}\n" +
|
||||
"gaslimit={}\n",
|
||||
Hex.toHexString(address.getLast20Bytes()),
|
||||
Hex.toHexString(origin.getLast20Bytes()),
|
||||
Hex.toHexString(caller.getLast20Bytes()),
|
||||
balance.longValue(),
|
||||
gasPrice.longValue(),
|
||||
gas.longValue(),
|
||||
callValue.longValue(),
|
||||
data == null ? "": Hex.toHexString(data),
|
||||
Hex.toHexString(lastHash.getData()),
|
||||
Hex.toHexString(coinbase.getLast20Bytes()),
|
||||
timestamp.longValue(),
|
||||
number.longValue(),
|
||||
Hex.toHexString(difficulty.getNoLeadZeroesData()),
|
||||
gasLimit.longValue());
|
||||
}
|
||||
|
||||
if (program.invokeData.getCallDeep() >= MAX_DEPTH)
|
||||
throw program.new OutOfGasException();
|
||||
|
||||
return new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue,
|
||||
data, lastHash, coinbase, timestamp, number, difficulty, gasLimit,
|
||||
repository, program.invokeData.getCallDeep()+1);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,199 @@
|
|||
package org.ethereum.vm;
|
||||
|
||||
import org.ethereum.core.Block;
|
||||
import org.ethereum.core.Transaction;
|
||||
import org.ethereum.facade.Blockchain;
|
||||
import org.ethereum.facade.Repository;
|
||||
import org.ethereum.manager.WorldManager;
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
/**
|
||||
* www.ethereumJ.com
|
||||
*
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 08/06/2014 09:59
|
||||
*/
|
||||
@Component("ProgramInvokeFactory")
|
||||
public class ProgramInvokeFactoryImpl implements ProgramInvokeFactory {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger("VM");
|
||||
|
||||
@Autowired
|
||||
private Blockchain blockchain;
|
||||
|
||||
/**
|
||||
* This attribute defines the number of recursive calls allowed in the EVM
|
||||
* Note: For the JVM to reach this level without a StackOverflow exception,
|
||||
* ethereumj may need to be started with a JVM argument to increase
|
||||
* the stack size. For example: -Xss10m
|
||||
*/
|
||||
private static final int MAX_DEPTH = 1024;
|
||||
|
||||
// Invocation by the wire tx
|
||||
@Override
|
||||
public ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository repository) {
|
||||
|
||||
// https://ethereum.etherpad.mozilla.org/26
|
||||
Block lastBlock = blockchain.getBestBlock();
|
||||
|
||||
/*** ADDRESS op ***/
|
||||
// YP: Get address of currently executing account.
|
||||
byte[] address = tx.isContractCreation() ? tx.getContractAddress(): tx.getReceiveAddress();
|
||||
|
||||
/*** ORIGIN op ***/
|
||||
// YP: This is the sender of original transaction; it is never a contract.
|
||||
byte[] origin = tx.getSender();
|
||||
|
||||
/*** CALLER op ***/
|
||||
// YP: This is the address of the account that is directly responsible for this execution.
|
||||
byte[] caller = tx.getSender();
|
||||
|
||||
/*** BALANCE op ***/
|
||||
byte[] balance = repository.getBalance(address).toByteArray();
|
||||
|
||||
/*** GASPRICE op ***/
|
||||
byte[] gasPrice = tx.getGasPrice();
|
||||
|
||||
/*** GAS op ***/
|
||||
byte[] gas = tx.getGasLimit();
|
||||
|
||||
/*** CALLVALUE op ***/
|
||||
byte[] callValue = tx.getValue() == null ? new byte[]{0} : tx.getValue();
|
||||
|
||||
/*** CALLDATALOAD op ***/
|
||||
/*** CALLDATACOPY op ***/
|
||||
/*** CALLDATASIZE op ***/
|
||||
byte[] data = tx.getData() == null ? ByteUtil.EMPTY_BYTE_ARRAY : tx.getData();
|
||||
|
||||
/*** PREVHASH op ***/
|
||||
byte[] lastHash = lastBlock.getHash();
|
||||
|
||||
/*** COINBASE op ***/
|
||||
byte[] coinbase = block.getCoinbase();
|
||||
|
||||
/*** TIMESTAMP op ***/
|
||||
long timestamp = block.getTimestamp();
|
||||
|
||||
/*** NUMBER op ***/
|
||||
long number = block.getNumber();
|
||||
|
||||
/*** DIFFICULTY op ***/
|
||||
byte[] difficulty = block.getDifficulty();
|
||||
|
||||
/*** GASLIMIT op ***/
|
||||
long gaslimit = block.getGasLimit();
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Top level call: \n" +
|
||||
"address={}\n" +
|
||||
"origin={}\n" +
|
||||
"caller={}\n" +
|
||||
"balance={}\n" +
|
||||
"gasPrice={}\n" +
|
||||
"gas={}\n" +
|
||||
"callValue={}\n" +
|
||||
"data={}\n" +
|
||||
"lastHash={}\n" +
|
||||
"coinbase={}\n" +
|
||||
"timestamp={}\n" +
|
||||
"blockNumber={}\n" +
|
||||
"difficulty={}\n" +
|
||||
"gaslimit={}\n",
|
||||
|
||||
Hex.toHexString(address),
|
||||
Hex.toHexString(origin),
|
||||
Hex.toHexString(caller),
|
||||
new BigInteger(balance).longValue(),
|
||||
new BigInteger(gasPrice).longValue(),
|
||||
new BigInteger(gas).longValue(),
|
||||
new BigInteger(callValue).longValue(),
|
||||
Hex.toHexString(data),
|
||||
Hex.toHexString(lastHash),
|
||||
Hex.toHexString(coinbase),
|
||||
timestamp,
|
||||
number,
|
||||
Hex.toHexString(difficulty),
|
||||
gaslimit);
|
||||
}
|
||||
|
||||
ProgramInvoke programInvoke =
|
||||
new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue, data,
|
||||
lastHash, coinbase, timestamp, number, difficulty, gaslimit,
|
||||
repository);
|
||||
|
||||
return programInvoke;
|
||||
}
|
||||
|
||||
/**
|
||||
* This invocation created for contract call contract
|
||||
*/
|
||||
@Override
|
||||
public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress,
|
||||
DataWord inValue, DataWord inGas,
|
||||
BigInteger balanceInt, byte[] dataIn,
|
||||
Repository repository) {
|
||||
|
||||
DataWord address = toAddress;
|
||||
DataWord origin = program.getOriginAddress();
|
||||
DataWord caller = program.getOwnerAddress();
|
||||
|
||||
DataWord balance = new DataWord(balanceInt.toByteArray());
|
||||
DataWord gasPrice = program.getGasPrice();
|
||||
DataWord gas = inGas;
|
||||
DataWord callValue = inValue;
|
||||
|
||||
byte[] data = dataIn;
|
||||
DataWord lastHash = program.getPrevHash();
|
||||
DataWord coinbase = program.getCoinbase();
|
||||
DataWord timestamp = program.getTimestamp();
|
||||
DataWord number = program.getNumber();
|
||||
DataWord difficulty = program.getDifficulty();
|
||||
DataWord gasLimit = program.getGaslimit();
|
||||
|
||||
if (logger.isInfoEnabled()) {
|
||||
logger.info("Internal call: \n" +
|
||||
"address={}\n" +
|
||||
"origin={}\n" +
|
||||
"caller={}\n" +
|
||||
"balance={}\n" +
|
||||
"gasPrice={}\n" +
|
||||
"gas={}\n" +
|
||||
"callValue={}\n" +
|
||||
"data={}\n" +
|
||||
"lastHash={}\n" +
|
||||
"coinbase={}\n" +
|
||||
"timestamp={}\n" +
|
||||
"blockNumber={}\n" +
|
||||
"difficulty={}\n" +
|
||||
"gaslimit={}\n",
|
||||
Hex.toHexString(address.getLast20Bytes()),
|
||||
Hex.toHexString(origin.getLast20Bytes()),
|
||||
Hex.toHexString(caller.getLast20Bytes()),
|
||||
balance.longValue(),
|
||||
gasPrice.longValue(),
|
||||
gas.longValue(),
|
||||
callValue.longValue(),
|
||||
data == null ? "": Hex.toHexString(data),
|
||||
Hex.toHexString(lastHash.getData()),
|
||||
Hex.toHexString(coinbase.getLast20Bytes()),
|
||||
timestamp.longValue(),
|
||||
number.longValue(),
|
||||
Hex.toHexString(difficulty.getNoLeadZeroesData()),
|
||||
gasLimit.longValue());
|
||||
}
|
||||
|
||||
if (program.invokeData.getCallDeep() >= MAX_DEPTH)
|
||||
throw program.new OutOfGasException();
|
||||
|
||||
return new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue,
|
||||
data, lastHash, coinbase, timestamp, number, difficulty, gasLimit,
|
||||
repository, program.invokeData.getCallDeep()+1);
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ public class ProgramResult {
|
|||
private RuntimeException exception;
|
||||
private List<DataWord> deleteAccounts;
|
||||
private List<LogInfo> logInfoList;
|
||||
private long futureRefund = 0;
|
||||
|
||||
private Repository repository = null;
|
||||
|
||||
|
@ -105,4 +106,12 @@ public class ProgramResult {
|
|||
callCreateList = new ArrayList<>();
|
||||
callCreateList.add(new CallCreate(data, destination, gasLimit, value));
|
||||
}
|
||||
|
||||
public void futureRefundGas(long gasValue) {
|
||||
futureRefund += gasValue;
|
||||
}
|
||||
|
||||
public long getFutureRefund(){
|
||||
return futureRefund;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import static org.ethereum.config.SystemProperties.CONFIG;
|
|||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.BigIntegers;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
import org.ethereum.vm.MessageCall.MsgType;
|
||||
|
||||
|
@ -106,10 +107,13 @@ public class VM {
|
|||
DataWord oldValue = program.storageLoad(stack.peek());
|
||||
if (oldValue == null && !newValue.isZero())
|
||||
gasCost = GasCost.SSTORE;
|
||||
else if (oldValue != null && newValue.isZero())
|
||||
else if (oldValue != null && newValue.isZero()) {
|
||||
// todo: GASREFUND counter policy
|
||||
System.currentTimeMillis();
|
||||
else
|
||||
|
||||
// refund step cost policy.
|
||||
program.futureRefundGas( GasCost.REFUND_SSTORE );
|
||||
gasCost = 0;
|
||||
} else
|
||||
gasCost = GasCost.RESET_SSTORE;
|
||||
break;
|
||||
case SLOAD:
|
||||
|
@ -170,10 +174,16 @@ public class VM {
|
|||
|
||||
int nTopics = op.val() - OpCode.LOG0.val();
|
||||
newMemSize = memNeeded(stack.peek(), stack.get(stack.size()-2));
|
||||
|
||||
BigInteger dataSize = stack.get(stack.size() - 2).value();
|
||||
BigInteger dataCost = dataSize.multiply(BigInteger.valueOf(GasCost.LOG_DATA_GAS));
|
||||
if (program.getGas().value().compareTo(dataCost) < 0){
|
||||
throw program.new OutOfGasException();
|
||||
}
|
||||
|
||||
gasCost = GasCost.LOG_GAS +
|
||||
GasCost.LOG_TOPIC_GAS * nTopics +
|
||||
GasCost.LOG_DATA_GAS * stack.get(stack.size()-2).longValue();
|
||||
|
||||
break;
|
||||
case EXP:
|
||||
|
||||
|
@ -632,18 +642,22 @@ public class VM {
|
|||
BigInteger codeOffsetData = program.stackPop().value();
|
||||
BigInteger lengthData = program.stackPop().value();
|
||||
|
||||
if (fullCode == null
|
||||
|| BigInteger.valueOf(fullCode.length).compareTo(
|
||||
codeOffsetData.add(lengthData)) < 0) {
|
||||
/*
|
||||
todo: find out what to do where params are exits the actual code
|
||||
if (fullCode == null ||
|
||||
BigInteger.valueOf(fullCode.length).
|
||||
compareTo(
|
||||
codeOffsetData.add(lengthData)) > 0) {
|
||||
program.stop();
|
||||
break;
|
||||
}
|
||||
*/
|
||||
|
||||
int length = lengthData.intValue();
|
||||
int codeOffset = codeOffsetData.intValue();
|
||||
|
||||
byte[] codeCopy = new byte[length];
|
||||
System.arraycopy(fullCode, codeOffset, codeCopy, 0, length);
|
||||
byte[] codeCopy = new byte[fullCode.length - codeOffset];
|
||||
System.arraycopy(fullCode, codeOffset, codeCopy, 0, fullCode.length - codeOffset);
|
||||
|
||||
if (logger.isInfoEnabled())
|
||||
hint = "code: " + Hex.toHexString(codeCopy);
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package test.ethereum.jsontestsuite;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import org.ethereum.jsontestsuite.TestCase;
|
||||
import org.ethereum.jsontestsuite.TestRunner;
|
||||
import org.ethereum.jsontestsuite.TestSuite;
|
||||
import org.ethereum.jsontestsuite.*;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.parser.JSONParser;
|
||||
import org.json.simple.parser.ParseException;
|
||||
|
@ -14,6 +13,8 @@ import org.junit.Assume;
|
|||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
import org.junit.runners.Suite.SuiteClasses;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Test file specific for tests maintained in the GitHub repository
|
||||
|
@ -27,6 +28,9 @@ import org.junit.runners.Suite.SuiteClasses;
|
|||
})
|
||||
public class GitHubJSONTestSuite {
|
||||
|
||||
private static Logger logger = LoggerFactory.getLogger("TCK-Test");
|
||||
|
||||
|
||||
protected static void runGitHubJsonTest(String json) throws ParseException {
|
||||
Assume.assumeFalse("Online test is not available", json.equals(""));
|
||||
|
||||
|
@ -45,4 +49,59 @@ public class GitHubJSONTestSuite {
|
|||
Assert.assertTrue(result.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
protected static void runGitHubJsonStateTest(String json, String testName) throws ParseException {
|
||||
Assume.assumeFalse("Online test is not available", json.equals(""));
|
||||
|
||||
JSONParser parser = new JSONParser();
|
||||
JSONObject testSuiteObj = (JSONObject)parser.parse(json);
|
||||
|
||||
StateTestSuite testSuite = new StateTestSuite(testSuiteObj);
|
||||
|
||||
for(StateTestCase testCase : testSuite.getAllTests()){
|
||||
if (testCase.getName().equals(testName))
|
||||
logger.info(" => " + testCase.getName());
|
||||
else
|
||||
logger.info(" " + testCase.getName());
|
||||
}
|
||||
|
||||
StateTestCase testCase = testSuite.getTestCase(testName);
|
||||
TestRunner runner = new TestRunner();
|
||||
List<String> result = runner.runTestCase(testCase);
|
||||
|
||||
if (!result.isEmpty()){
|
||||
for (String single : result){
|
||||
logger.info(single);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertTrue(result.isEmpty());
|
||||
}
|
||||
|
||||
protected static void runGitHubJsonStateTest(String json) throws ParseException {
|
||||
Assume.assumeFalse("Online test is not available", json.equals(""));
|
||||
|
||||
JSONParser parser = new JSONParser();
|
||||
JSONObject testSuiteObj = (JSONObject)parser.parse(json);
|
||||
|
||||
StateTestSuite testSuite = new StateTestSuite(testSuiteObj);
|
||||
Collection<StateTestCase> testCollection = testSuite.getAllTests();
|
||||
|
||||
|
||||
for (StateTestCase testCase : testCollection){
|
||||
|
||||
TestRunner runner = new TestRunner();
|
||||
List<String> result = runner.runTestCase(testCase);
|
||||
|
||||
if (!result.isEmpty()){
|
||||
for (String single : result){
|
||||
logger.info(single);
|
||||
}
|
||||
}
|
||||
|
||||
Assert.assertTrue(result.isEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
package test.ethereum.jsontestsuite;
|
||||
|
||||
import org.ethereum.jsontestsuite.JSONReader;
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class GitHubStateTest {
|
||||
|
||||
@Test
|
||||
public void stSingleTest() throws ParseException {
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stSystemOperationsTest.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json, "CallRecursiveBombLog");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stExample() throws ParseException {
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stExample.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stInitCodeTest() throws ParseException { // [V]
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stInitCodeTest.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stLogTests() throws ParseException { // [V]
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stLogTests.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stPreCompiledContracts() throws ParseException {
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stPreCompiledContracts.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stRecursiveCreate() throws ParseException { // [V]
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stRecursiveCreate.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stRefundTest() throws ParseException { // [V]
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stRefundTest.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void stSpecialTest() throws ParseException {
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stSpecialTest.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void stSystemOperationsTest() throws ParseException {
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stSystemOperationsTest.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void stTransactionTest() throws ParseException {
|
||||
|
||||
String json = JSONReader.loadJSON("StateTests/stTransactionTest.json");
|
||||
GitHubJSONTestSuite.runGitHubJsonStateTest(json);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue