Introducing StateTest json suite support:

+ passing: stInitCodeTest
+ passing: stLogTests
+ passing: stRecursiveCreate
+ passing: stRefundTest
This commit is contained in:
romanman 2014-12-20 23:09:48 +02:00
parent c271346a4f
commit aad53ba2c5
30 changed files with 1868 additions and 573 deletions

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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) {

View File

@ -192,4 +192,5 @@ public interface Repository {
void loadAccount(byte[] addr, HashMap<ByteArrayWrapper, AccountState> cacheAccounts,
HashMap<ByteArrayWrapper, ContractDetails> cacheDetails);
}

View File

@ -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{" +

View File

@ -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

View File

@ -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;
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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);
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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() {

View File

@ -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
*

View File

@ -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 */

View File

@ -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());
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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());
}
}
}

View File

@ -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);
}
}