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() {
|
public String toString() {
|
||||||
String ret = "Nonce: " + this.getNonce().toString() + "\n" +
|
String ret = " Nonce: " + this.getNonce().toString() + "\n" +
|
||||||
"Balance: " + Denomination.toFriendlyString(getBalance()) + "\n" +
|
" Balance: " + getBalance() + "\n" +
|
||||||
"State Root: " + Hex.toHexString(this.getStateRoot()) + "\n" +
|
" State Root: " + Hex.toHexString(this.getStateRoot()) + "\n" +
|
||||||
"Code Hash: " + Hex.toHexString(this.getCodeHash());
|
" Code Hash: " + Hex.toHexString(this.getCodeHash());
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,11 +6,10 @@ import org.ethereum.facade.Blockchain;
|
||||||
import org.ethereum.facade.Repository;
|
import org.ethereum.facade.Repository;
|
||||||
import org.ethereum.listener.EthereumListener;
|
import org.ethereum.listener.EthereumListener;
|
||||||
import org.ethereum.manager.AdminInfo;
|
import org.ethereum.manager.AdminInfo;
|
||||||
import org.ethereum.manager.WorldManager;
|
|
||||||
import org.ethereum.net.BlockQueue;
|
import org.ethereum.net.BlockQueue;
|
||||||
import org.ethereum.net.server.ChannelManager;
|
import org.ethereum.net.server.ChannelManager;
|
||||||
import org.ethereum.util.AdvancedDeviceUtils;
|
import org.ethereum.util.AdvancedDeviceUtils;
|
||||||
import org.ethereum.vm.*;
|
import org.ethereum.vm.ProgramInvokeFactory;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.spongycastle.util.encoders.Hex;
|
import org.spongycastle.util.encoders.Hex;
|
||||||
|
@ -24,12 +23,10 @@ import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.ethereum.config.SystemProperties.CONFIG;
|
import static org.ethereum.config.SystemProperties.CONFIG;
|
||||||
import static org.ethereum.core.Denomination.SZABO;
|
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,
|
* 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;
|
private BigInteger totalDifficulty = BigInteger.ZERO;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private WorldManager worldManager;
|
Wallet wallet;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private EthereumListener listener;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private BlockQueue blockQueue;
|
private BlockQueue blockQueue;
|
||||||
|
@ -208,13 +208,11 @@ public class BlockchainImpl implements Blockchain {
|
||||||
|
|
||||||
|
|
||||||
// Remove all wallet transactions as they already approved by the net
|
// 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()));
|
listener.trace(String.format("Block chain size: [ %d ]", this.getSize()));
|
||||||
|
listener.onBlock(block);
|
||||||
EthereumListener ethereumListener = worldManager.getListener();
|
|
||||||
ethereumListener.onBlock(block);
|
|
||||||
|
|
||||||
if (blockQueue.size() == 0 &&
|
if (blockQueue.size() == 0 &&
|
||||||
!syncDoneCalled &&
|
!syncDoneCalled &&
|
||||||
|
@ -222,7 +220,7 @@ public class BlockchainImpl implements Blockchain {
|
||||||
|
|
||||||
logger.info("Sync done");
|
logger.info("Sync done");
|
||||||
syncDoneCalled = true;
|
syncDoneCalled = true;
|
||||||
ethereumListener.onSyncDone();
|
listener.onSyncDone();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,7 +297,6 @@ public class BlockchainImpl implements Blockchain {
|
||||||
if(isValid(block)) {
|
if(isValid(block)) {
|
||||||
if (!block.isGenesis()) {
|
if (!block.isGenesis()) {
|
||||||
if (!CONFIG.blockChainOnly()) {
|
if (!CONFIG.blockChainOnly()) {
|
||||||
Wallet wallet = worldManager.getWallet();
|
|
||||||
wallet.addTransactions(block.getTransactionsList());
|
wallet.addTransactions(block.getTransactionsList());
|
||||||
receipts = this.applyBlock(block);
|
receipts = this.applyBlock(block);
|
||||||
wallet.processBlock(block);
|
wallet.processBlock(block);
|
||||||
|
@ -320,7 +317,11 @@ public class BlockchainImpl implements Blockchain {
|
||||||
for (Transaction tx : block.getTransactionsList()) {
|
for (Transaction tx : block.getTransactionsList()) {
|
||||||
stateLogger.info("apply block: [{}] tx: [{}] ", block.getNumber(), i);
|
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();
|
totalGasUsed += receipt.getCumulativeGasLong();
|
||||||
|
|
||||||
track.commit();
|
track.commit();
|
||||||
|
@ -403,235 +404,6 @@ public class BlockchainImpl implements Blockchain {
|
||||||
logger.info("*** Last block added [ #{} ]", block.getNumber());
|
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){
|
public boolean hasParentOnTheChain(Block block){
|
||||||
return getParent(block.getHeader()) != null;
|
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) {
|
if(receiveAddress == null) {
|
||||||
this.receiveAddress = ByteUtil.EMPTY_BYTE_ARRAY;
|
this.receiveAddress = ByteUtil.EMPTY_BYTE_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEncoded();
|
||||||
parsed = true;
|
parsed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,6 +145,11 @@ public class Transaction {
|
||||||
return nonce == null ? ZERO_BYTE_ARRAY : nonce ;
|
return nonce == null ? ZERO_BYTE_ARRAY : nonce ;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isValueTx() {
|
||||||
|
if (!parsed) rlpParse();
|
||||||
|
return value != null ;
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] getValue() {
|
public byte[] getValue() {
|
||||||
if (!parsed) rlpParse();
|
if (!parsed) rlpParse();
|
||||||
return value == null ? ZERO_BYTE_ARRAY : value;
|
return value == null ? ZERO_BYTE_ARRAY : value;
|
||||||
|
@ -155,7 +162,7 @@ public class Transaction {
|
||||||
|
|
||||||
public byte[] getGasPrice() {
|
public byte[] getGasPrice() {
|
||||||
if (!parsed) rlpParse();
|
if (!parsed) rlpParse();
|
||||||
return gasPrice;
|
return gasPrice== null ? ZERO_BYTE_ARRAY : gasPrice ;
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getGasLimit() {
|
public byte[] getGasLimit() {
|
||||||
|
@ -222,9 +229,9 @@ public class Transaction {
|
||||||
", receiveAddress=" + ByteUtil.toHexString(receiveAddress) +
|
", receiveAddress=" + ByteUtil.toHexString(receiveAddress) +
|
||||||
", value=" + ByteUtil.toHexString(value) +
|
", value=" + ByteUtil.toHexString(value) +
|
||||||
", data=" + ByteUtil.toHexString(data) +
|
", data=" + ByteUtil.toHexString(data) +
|
||||||
", signatureV=" + signature.v +
|
", signatureV=" + (signature == null ? "" : signature.v) +
|
||||||
", signatureR=" + ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.r)) +
|
", signatureR=" + (signature == null ? "" : ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.r))) +
|
||||||
", signatureS=" + ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.s)) +
|
", signatureS=" + (signature == null ? "" : ByteUtil.toHexString(BigIntegers.asUnsignedByteArray(signature.s))) +
|
||||||
"]";
|
"]";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -286,6 +293,9 @@ public class Transaction {
|
||||||
|
|
||||||
this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit,
|
this.rlpEncoded = RLP.encodeList(nonce, gasPrice, gasLimit,
|
||||||
receiveAddress, value, data, v, r, s);
|
receiveAddress, value, data, v, r, s);
|
||||||
|
|
||||||
|
this.hash = this.getHash();
|
||||||
|
|
||||||
return rlpEncoded;
|
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.trie.TrieImpl;
|
||||||
import org.ethereum.util.*;
|
import org.ethereum.util.*;
|
||||||
import org.ethereum.vm.DataWord;
|
import org.ethereum.vm.DataWord;
|
||||||
|
import org.spongycastle.util.encoders.Hex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author: Roman Mandeleil
|
* @author: Roman Mandeleil
|
||||||
|
@ -188,6 +189,23 @@ public class ContractDetails {
|
||||||
this.storageValues = storageValues;
|
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(){
|
public ContractDetails clone(){
|
||||||
|
|
||||||
|
@ -199,5 +217,13 @@ public class ContractDetails {
|
||||||
return 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.trie.TrieImpl;
|
||||||
import org.ethereum.vm.DataWord;
|
import org.ethereum.vm.DataWord;
|
||||||
import org.iq80.leveldb.DBIterator;
|
import org.iq80.leveldb.DBIterator;
|
||||||
import org.iq80.leveldb.WriteBatch;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.spongycastle.util.encoders.Hex;
|
import org.spongycastle.util.encoders.Hex;
|
||||||
|
@ -27,7 +26,7 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.ethereum.config.SystemProperties.CONFIG;
|
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;
|
import static org.ethereum.util.ByteUtil.wrap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -103,7 +102,7 @@ public class RepositoryImpl implements Repository {
|
||||||
|
|
||||||
} else{
|
} else{
|
||||||
|
|
||||||
if (contractDetails.isDirty()){
|
if (accountState.isDirty() || contractDetails.isDirty()){
|
||||||
detailsDB.put(hash.getData(), contractDetails.getEncoded());
|
detailsDB.put(hash.getData(), contractDetails.getEncoded());
|
||||||
accountState.setStateRoot(contractDetails.getStorageHash());
|
accountState.setStateRoot(contractDetails.getStorageHash());
|
||||||
accountState.setCodeHash(sha3(contractDetails.getCode()));
|
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
|
@Override
|
||||||
|
@ -362,6 +351,22 @@ public class RepositoryImpl implements Repository {
|
||||||
return account.getNonce();
|
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
|
@Override
|
||||||
public void delete(byte[] addr) {
|
public void delete(byte[] addr) {
|
||||||
worldState.delete(addr);
|
worldState.delete(addr);
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.spongycastle.util.encoders.Hex;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.ethereum.crypto.SHA3Helper.sha3;
|
import static org.ethereum.crypto.SHA3Helper.sha3;
|
||||||
import static org.ethereum.util.ByteUtil.wrap;
|
import static org.ethereum.util.ByteUtil.wrap;
|
||||||
|
@ -26,12 +27,15 @@ public class RepositoryTrack implements Repository {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger("repository");
|
private static final Logger logger = LoggerFactory.getLogger("repository");
|
||||||
|
|
||||||
|
|
||||||
HashMap<ByteArrayWrapper, AccountState> cacheAccounts = new HashMap<>();
|
HashMap<ByteArrayWrapper, AccountState> cacheAccounts = new HashMap<>();
|
||||||
HashMap<ByteArrayWrapper, ContractDetails> cacheDetails = new HashMap<>();
|
HashMap<ByteArrayWrapper, ContractDetails> cacheDetails = new HashMap<>();
|
||||||
|
|
||||||
Repository repository;
|
Repository repository;
|
||||||
|
|
||||||
|
public RepositoryTrack(){
|
||||||
|
this.repository = new RepositoryDummy();
|
||||||
|
}
|
||||||
|
|
||||||
public RepositoryTrack(Repository repository) {
|
public RepositoryTrack(Repository repository) {
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
@ -116,6 +120,23 @@ public class RepositoryTrack implements Repository {
|
||||||
return accountState.getNonce();
|
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
|
@Override
|
||||||
public BigInteger getNonce(byte[] addr) {
|
public BigInteger getNonce(byte[] addr) {
|
||||||
AccountState accountState = getAccountState(addr);
|
AccountState accountState = getAccountState(addr);
|
||||||
|
@ -177,6 +198,10 @@ public class RepositoryTrack implements Repository {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Set<ByteArrayWrapper> getFullAddressSet(){
|
||||||
|
return cacheAccounts.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dumpState(Block block, long gasUsed, int txNumber, byte[] txHash) {
|
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,
|
void loadAccount(byte[] addr, HashMap<ByteArrayWrapper, AccountState> cacheAccounts,
|
||||||
HashMap<ByteArrayWrapper, ContractDetails> cacheDetails);
|
HashMap<ByteArrayWrapper, ContractDetails> cacheDetails);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
package org.ethereum.jsontestsuite;
|
package org.ethereum.jsontestsuite;
|
||||||
|
|
||||||
import org.ethereum.db.ByteArrayWrapper;
|
import org.ethereum.db.ByteArrayWrapper;
|
||||||
|
import org.ethereum.db.ContractDetails;
|
||||||
import org.ethereum.util.ByteUtil;
|
import org.ethereum.util.ByteUtil;
|
||||||
|
import org.ethereum.vm.DataWord;
|
||||||
import org.json.simple.JSONObject;
|
import org.json.simple.JSONObject;
|
||||||
import org.spongycastle.util.encoders.Hex;
|
import org.spongycastle.util.encoders.Hex;
|
||||||
|
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,7 +25,7 @@ public class AccountState {
|
||||||
byte[] code;
|
byte[] code;
|
||||||
byte[] nonce;
|
byte[] nonce;
|
||||||
|
|
||||||
Map<ByteArrayWrapper, ByteArrayWrapper> storage = new HashMap<>();
|
Map<DataWord, DataWord> storage = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
public AccountState(byte[] address, JSONObject accountState) {
|
public AccountState(byte[] address, JSONObject accountState) {
|
||||||
|
@ -52,28 +53,9 @@ public class AccountState {
|
||||||
String keyS = keys[i].toString();
|
String keyS = keys[i].toString();
|
||||||
String valS = store.get(keys[i]).toString();
|
String valS = store.get(keys[i]).toString();
|
||||||
|
|
||||||
ByteArrayWrapper key;
|
byte[] key = Utils.parseData(keyS);
|
||||||
boolean hexVal = Pattern.matches("0[xX][0-9a-fA-F]+", keyS);
|
byte[] value = Utils.parseData(valS);
|
||||||
if (hexVal) {
|
storage.put(new DataWord(key), new DataWord(value));
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,10 +85,81 @@ public class AccountState {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public Map<ByteArrayWrapper, ByteArrayWrapper> getStorage() {
|
public Map<DataWord, DataWord> getStorage() {
|
||||||
return storage;
|
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
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "AccountState{" +
|
return "AccountState{" +
|
||||||
|
|
|
@ -7,7 +7,7 @@ import org.spongycastle.util.encoders.Hex;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* www.ethereumJ.com
|
* www.etherj.com
|
||||||
*
|
*
|
||||||
* @author: Roman Mandeleil
|
* @author: Roman Mandeleil
|
||||||
* Created on: 28/06/2014 10:25
|
* Created on: 28/06/2014 10:25
|
||||||
|
|
|
@ -41,4 +41,68 @@ public class Logs {
|
||||||
public Iterator<LogInfo> getIterator(){
|
public Iterator<LogInfo> getIterator(){
|
||||||
return logs.iterator();
|
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;
|
package org.ethereum.jsontestsuite;
|
||||||
|
|
||||||
|
import org.ethereum.core.BlockchainImpl;
|
||||||
|
import org.ethereum.core.TransactionExecutor;
|
||||||
import org.ethereum.db.ByteArrayWrapper;
|
import org.ethereum.db.ByteArrayWrapper;
|
||||||
import org.ethereum.db.ContractDetails;
|
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.facade.Repository;
|
||||||
import org.ethereum.util.ByteUtil;
|
import org.ethereum.util.ByteUtil;
|
||||||
import org.ethereum.vm.*;
|
import org.ethereum.vm.*;
|
||||||
|
@ -14,6 +17,9 @@ import org.spongycastle.util.encoders.Hex;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY;
|
||||||
|
import static org.ethereum.util.ByteUtil.wrap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* www.ethereumJ.com
|
* www.ethereumJ.com
|
||||||
*
|
*
|
||||||
|
@ -23,7 +29,7 @@ import java.util.*;
|
||||||
|
|
||||||
public class TestRunner {
|
public class TestRunner {
|
||||||
|
|
||||||
private Logger logger = LoggerFactory.getLogger("JSONTest");
|
private Logger logger = LoggerFactory.getLogger("TCK-Test");
|
||||||
private ProgramTrace trace = null;
|
private ProgramTrace trace = null;
|
||||||
|
|
||||||
public List<String> runTestSuite(TestSuite testSuite) {
|
public List<String> runTestSuite(TestSuite testSuite) {
|
||||||
|
@ -43,36 +49,102 @@ public class TestRunner {
|
||||||
return resultCollector;
|
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) {
|
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 {
|
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 */
|
/* 2. Create ProgramInvoke - Env/Exec */
|
||||||
Env env = testCase.getEnv();
|
Env env = testCase.getEnv();
|
||||||
Exec exec = testCase.getExec();
|
Exec exec = testCase.getExec();
|
||||||
Logs logs = testCase.getLogs();
|
Logs logs = testCase.getLogs();
|
||||||
|
|
||||||
byte[] address = exec.getAddress();
|
byte[] address = exec.getAddress();
|
||||||
byte[] origin = exec.getOrigin();
|
byte[] origin = exec.getOrigin();
|
||||||
byte[] caller = exec.getCaller();
|
byte[] caller = exec.getCaller();
|
||||||
|
@ -87,17 +159,17 @@ public class TestRunner {
|
||||||
long number = new BigInteger(env.getCurrentNumber()).longValue();
|
long number = new BigInteger(env.getCurrentNumber()).longValue();
|
||||||
byte[] difficulty = env.getCurrentDifficlty();
|
byte[] difficulty = env.getCurrentDifficlty();
|
||||||
long gaslimit = new BigInteger(env.getCurrentGasLimit()).longValue();
|
long gaslimit = new BigInteger(env.getCurrentGasLimit()).longValue();
|
||||||
|
|
||||||
// Origin and caller need to exist in order to be able to execute
|
// Origin and caller need to exist in order to be able to execute
|
||||||
if(repository.getAccountState(origin) == null)
|
if(repository.getAccountState(origin) == null)
|
||||||
repository.createAccount(origin);
|
repository.createAccount(origin);
|
||||||
if(repository.getAccountState(caller) == null)
|
if(repository.getAccountState(caller) == null)
|
||||||
repository.createAccount(caller);
|
repository.createAccount(caller);
|
||||||
|
|
||||||
ProgramInvoke programInvoke = new ProgramInvokeImpl(address, origin, caller, balance,
|
ProgramInvoke programInvoke = new ProgramInvokeImpl(address, origin, caller, balance,
|
||||||
gasPrice, gas, callValue, msgData, lastHash, coinbase,
|
gasPrice, gas, callValue, msgData, lastHash, coinbase,
|
||||||
timestamp, number, difficulty, gaslimit, repository, true);
|
timestamp, number, difficulty, gaslimit, repository, true);
|
||||||
|
|
||||||
/* 3. Create Program - exec.code */
|
/* 3. Create Program - exec.code */
|
||||||
/* 4. run VM */
|
/* 4. run VM */
|
||||||
VM vm = new VM();
|
VM vm = new VM();
|
||||||
|
@ -113,7 +185,7 @@ public class TestRunner {
|
||||||
e = ex;
|
e = ex;
|
||||||
}
|
}
|
||||||
program.saveProgramTraceToFile(testCase.getName());
|
program.saveProgramTraceToFile(testCase.getName());
|
||||||
|
|
||||||
if(testCase.getPost().size() == 0) {
|
if(testCase.getPost().size() == 0) {
|
||||||
if(vmDidThrowAnEception != true) {
|
if(vmDidThrowAnEception != true) {
|
||||||
String output =
|
String output =
|
||||||
|
@ -132,22 +204,22 @@ public class TestRunner {
|
||||||
results.add(output);
|
results.add(output);
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.trace = program.getProgramTrace();
|
this.trace = program.getProgramTrace();
|
||||||
|
|
||||||
System.out.println("--------- POST --------");
|
System.out.println("--------- POST --------");
|
||||||
/* 5. Assert Post values */
|
/* 5. Assert Post values */
|
||||||
for (ByteArrayWrapper key : testCase.getPost().keySet()) {
|
for (ByteArrayWrapper key : testCase.getPost().keySet()) {
|
||||||
|
|
||||||
AccountState accountState = testCase.getPost().get(key);
|
AccountState accountState = testCase.getPost().get(key);
|
||||||
|
|
||||||
long expectedNonce = accountState.getNonceLong();
|
long expectedNonce = accountState.getNonceLong();
|
||||||
BigInteger expectedBalance = accountState.getBigIntegerBalance();
|
BigInteger expectedBalance = accountState.getBigIntegerBalance();
|
||||||
byte[] expectedCode = accountState.getCode();
|
byte[] expectedCode = accountState.getCode();
|
||||||
|
|
||||||
boolean accountExist = (null != repository.getAccountState(key.getData()));
|
boolean accountExist = (null != repository.getAccountState(key.getData()));
|
||||||
if (!accountExist) {
|
if (!accountExist) {
|
||||||
|
|
||||||
String output =
|
String output =
|
||||||
String.format("The expected account does not exist. key: [ %s ]",
|
String.format("The expected account does not exist. key: [ %s ]",
|
||||||
Hex.toHexString(key.getData()));
|
Hex.toHexString(key.getData()));
|
||||||
|
@ -155,32 +227,32 @@ public class TestRunner {
|
||||||
results.add(output);
|
results.add(output);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
long actualNonce = repository.getNonce(key.getData()).longValue();
|
long actualNonce = repository.getNonce(key.getData()).longValue();
|
||||||
BigInteger actualBalance = repository.getBalance(key.getData());
|
BigInteger actualBalance = repository.getBalance(key.getData());
|
||||||
byte[] actualCode = repository.getCode(key.getData());
|
byte[] actualCode = repository.getCode(key.getData());
|
||||||
if (actualCode == null) actualCode = "".getBytes();
|
if (actualCode == null) actualCode = "".getBytes();
|
||||||
|
|
||||||
if (expectedNonce != actualNonce) {
|
if (expectedNonce != actualNonce) {
|
||||||
|
|
||||||
String output =
|
String output =
|
||||||
String.format("The nonce result is different. key: [ %s ], expectedNonce: [ %d ] is actualNonce: [ %d ] ",
|
String.format("The nonce result is different. key: [ %s ], expectedNonce: [ %d ] is actualNonce: [ %d ] ",
|
||||||
Hex.toHexString(key.getData()), expectedNonce, actualNonce);
|
Hex.toHexString(key.getData()), expectedNonce, actualNonce);
|
||||||
logger.info(output);
|
logger.info(output);
|
||||||
results.add(output);
|
results.add(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!expectedBalance.equals(actualBalance)) {
|
if (!expectedBalance.equals(actualBalance)) {
|
||||||
|
|
||||||
String output =
|
String output =
|
||||||
String.format("The balance result is different. key: [ %s ], expectedBalance: [ %s ] is actualBalance: [ %s ] ",
|
String.format("The balance result is different. key: [ %s ], expectedBalance: [ %s ] is actualBalance: [ %s ] ",
|
||||||
Hex.toHexString(key.getData()), expectedBalance.toString(), actualBalance.toString());
|
Hex.toHexString(key.getData()), expectedBalance.toString(), actualBalance.toString());
|
||||||
logger.info(output);
|
logger.info(output);
|
||||||
results.add(output);
|
results.add(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Arrays.equals(expectedCode, actualCode)) {
|
if (!Arrays.equals(expectedCode, actualCode)) {
|
||||||
|
|
||||||
String output =
|
String output =
|
||||||
String.format("The code result is different. account: [ %s ], expectedCode: [ %s ] is actualCode: [ %s ] ",
|
String.format("The code result is different. account: [ %s ], expectedCode: [ %s ] is actualCode: [ %s ] ",
|
||||||
Hex.toHexString(key.getData()),
|
Hex.toHexString(key.getData()),
|
||||||
|
@ -189,18 +261,18 @@ public class TestRunner {
|
||||||
logger.info(output);
|
logger.info(output);
|
||||||
results.add(output);
|
results.add(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
// assert storage
|
// assert storage
|
||||||
Map<ByteArrayWrapper, ByteArrayWrapper> storage = accountState.getStorage();
|
Map<DataWord, DataWord> storage = accountState.getStorage();
|
||||||
for (ByteArrayWrapper storageKey : storage.keySet()) {
|
for (DataWord storageKey : storage.keySet()) {
|
||||||
|
|
||||||
byte[] expectedStValue = storage.get(storageKey).getData();
|
byte[] expectedStValue = storage.get(storageKey).getData();
|
||||||
|
|
||||||
ContractDetails contractDetails =
|
ContractDetails contractDetails =
|
||||||
program.getResult().getRepository().getContractDetails(accountState.getAddress());
|
program.getResult().getRepository().getContractDetails(accountState.getAddress());
|
||||||
|
|
||||||
if (contractDetails == null) {
|
if (contractDetails == null) {
|
||||||
|
|
||||||
String output =
|
String output =
|
||||||
String.format("Storage raw doesn't exist: key [ %s ], expectedValue: [ %s ]",
|
String.format("Storage raw doesn't exist: key [ %s ], expectedValue: [ %s ]",
|
||||||
Hex.toHexString(storageKey.getData()),
|
Hex.toHexString(storageKey.getData()),
|
||||||
|
@ -210,13 +282,13 @@ public class TestRunner {
|
||||||
results.add(output);
|
results.add(output);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<DataWord, DataWord> testStorage = contractDetails.getStorage();
|
Map<DataWord, DataWord> testStorage = contractDetails.getStorage();
|
||||||
DataWord actualValue = testStorage.get(new DataWord(storageKey.getData()));
|
DataWord actualValue = testStorage.get(new DataWord(storageKey.getData()));
|
||||||
|
|
||||||
if (actualValue == null ||
|
if (actualValue == null ||
|
||||||
!Arrays.equals(expectedStValue, actualValue.getNoLeadZeroesData())) {
|
!Arrays.equals(expectedStValue, actualValue.getNoLeadZeroesData())) {
|
||||||
|
|
||||||
String output =
|
String output =
|
||||||
String.format("Storage value different: key [ %s ], expectedValue: [ %s ], actualValue: [ %s ]",
|
String.format("Storage value different: key [ %s ], expectedValue: [ %s ], actualValue: [ %s ]",
|
||||||
Hex.toHexString(storageKey.getData()),
|
Hex.toHexString(storageKey.getData()),
|
||||||
|
@ -226,7 +298,7 @@ public class TestRunner {
|
||||||
results.add(output);
|
results.add(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* asset logs */
|
/* asset logs */
|
||||||
List<LogInfo> logResult = program.getResult().getLogInfoList();
|
List<LogInfo> logResult = program.getResult().getLogInfoList();
|
||||||
|
|
||||||
|
@ -296,26 +368,26 @@ public class TestRunner {
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: assert that you have no extra accounts in the repository
|
// TODO: assert that you have no extra accounts in the repository
|
||||||
// TODO: -> basically the deleted by suicide should be deleted
|
// TODO: -> basically the deleted by suicide should be deleted
|
||||||
// TODO: -> and no unexpected created
|
// TODO: -> and no unexpected created
|
||||||
|
|
||||||
List<org.ethereum.vm.CallCreate> resultCallCreates =
|
List<org.ethereum.vm.CallCreate> resultCallCreates =
|
||||||
program.getResult().getCallCreateList();
|
program.getResult().getCallCreateList();
|
||||||
|
|
||||||
// assert call creates
|
// assert call creates
|
||||||
for (int i = 0; i < testCase.getCallCreateList().size(); ++i) {
|
for (int i = 0; i < testCase.getCallCreateList().size(); ++i) {
|
||||||
|
|
||||||
org.ethereum.vm.CallCreate resultCallCreate = null;
|
org.ethereum.vm.CallCreate resultCallCreate = null;
|
||||||
if (resultCallCreates != null && resultCallCreates.size() > i) {
|
if (resultCallCreates != null && resultCallCreates.size() > i) {
|
||||||
resultCallCreate = resultCallCreates.get(i);
|
resultCallCreate = resultCallCreates.get(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
CallCreate expectedCallCreate = testCase.getCallCreateList().get(i);
|
CallCreate expectedCallCreate = testCase.getCallCreateList().get(i);
|
||||||
|
|
||||||
if (resultCallCreate == null && expectedCallCreate != null) {
|
if (resultCallCreate == null && expectedCallCreate != null) {
|
||||||
|
|
||||||
String output =
|
String output =
|
||||||
String.format("Missing call/create invoke: to: [ %s ], data: [ %s ], gas: [ %s ], value: [ %s ]",
|
String.format("Missing call/create invoke: to: [ %s ], data: [ %s ], gas: [ %s ], value: [ %s ]",
|
||||||
Hex.toHexString(expectedCallCreate.getDestination()),
|
Hex.toHexString(expectedCallCreate.getDestination()),
|
||||||
|
@ -324,15 +396,15 @@ public class TestRunner {
|
||||||
Hex.toHexString(expectedCallCreate.getValue()));
|
Hex.toHexString(expectedCallCreate.getValue()));
|
||||||
logger.info(output);
|
logger.info(output);
|
||||||
results.add(output);
|
results.add(output);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean assertDestination = Arrays.equals(
|
boolean assertDestination = Arrays.equals(
|
||||||
expectedCallCreate.getDestination(),
|
expectedCallCreate.getDestination(),
|
||||||
resultCallCreate.getDestination());
|
resultCallCreate.getDestination());
|
||||||
if (!assertDestination) {
|
if (!assertDestination) {
|
||||||
|
|
||||||
String output =
|
String output =
|
||||||
String.format("Call/Create destination is different. Expected: [ %s ], result: [ %s ]",
|
String.format("Call/Create destination is different. Expected: [ %s ], result: [ %s ]",
|
||||||
Hex.toHexString(expectedCallCreate.getDestination()),
|
Hex.toHexString(expectedCallCreate.getDestination()),
|
||||||
|
@ -340,12 +412,12 @@ public class TestRunner {
|
||||||
logger.info(output);
|
logger.info(output);
|
||||||
results.add(output);
|
results.add(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean assertData = Arrays.equals(
|
boolean assertData = Arrays.equals(
|
||||||
expectedCallCreate.getData(),
|
expectedCallCreate.getData(),
|
||||||
resultCallCreate.getData());
|
resultCallCreate.getData());
|
||||||
if (!assertData) {
|
if (!assertData) {
|
||||||
|
|
||||||
String output =
|
String output =
|
||||||
String.format("Call/Create data is different. Expected: [ %s ], result: [ %s ]",
|
String.format("Call/Create data is different. Expected: [ %s ], result: [ %s ]",
|
||||||
Hex.toHexString(expectedCallCreate.getData()),
|
Hex.toHexString(expectedCallCreate.getData()),
|
||||||
|
@ -353,7 +425,7 @@ public class TestRunner {
|
||||||
logger.info(output);
|
logger.info(output);
|
||||||
results.add(output);
|
results.add(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean assertGasLimit = Arrays.equals(
|
boolean assertGasLimit = Arrays.equals(
|
||||||
expectedCallCreate.getGasLimit(),
|
expectedCallCreate.getGasLimit(),
|
||||||
resultCallCreate.getGasLimit());
|
resultCallCreate.getGasLimit());
|
||||||
|
@ -365,7 +437,7 @@ public class TestRunner {
|
||||||
logger.info(output);
|
logger.info(output);
|
||||||
results.add(output);
|
results.add(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean assertValue = Arrays.equals(
|
boolean assertValue = Arrays.equals(
|
||||||
expectedCallCreate.getValue(),
|
expectedCallCreate.getValue(),
|
||||||
resultCallCreate.getValue());
|
resultCallCreate.getValue());
|
||||||
|
@ -378,16 +450,16 @@ public class TestRunner {
|
||||||
results.add(output);
|
results.add(output);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// assert out
|
// assert out
|
||||||
byte[] expectedHReturn = testCase.getOut();
|
byte[] expectedHReturn = testCase.getOut();
|
||||||
byte[] actualHReturn = ByteUtil.EMPTY_BYTE_ARRAY;
|
byte[] actualHReturn = EMPTY_BYTE_ARRAY;
|
||||||
if (program.getResult().getHReturn() != null) {
|
if (program.getResult().getHReturn() != null) {
|
||||||
actualHReturn = program.getResult().getHReturn().array();
|
actualHReturn = program.getResult().getHReturn().array();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Arrays.equals(expectedHReturn, actualHReturn)) {
|
if (!Arrays.equals(expectedHReturn, actualHReturn)) {
|
||||||
|
|
||||||
String output =
|
String output =
|
||||||
String.format("HReturn is different. Expected hReturn: [ %s ], actual hReturn: [ %s ]",
|
String.format("HReturn is different. Expected hReturn: [ %s ], actual hReturn: [ %s ]",
|
||||||
Hex.toHexString(expectedHReturn),
|
Hex.toHexString(expectedHReturn),
|
||||||
|
@ -395,13 +467,13 @@ public class TestRunner {
|
||||||
logger.info(output);
|
logger.info(output);
|
||||||
results.add(output);
|
results.add(output);
|
||||||
}
|
}
|
||||||
|
|
||||||
// assert gas
|
// assert gas
|
||||||
BigInteger expectedGas = new BigInteger(testCase.getGas());
|
BigInteger expectedGas = new BigInteger(testCase.getGas());
|
||||||
BigInteger actualGas = new BigInteger(gas).subtract(BigInteger.valueOf(program.getResult().getGasUsed()));
|
BigInteger actualGas = new BigInteger(gas).subtract(BigInteger.valueOf(program.getResult().getGasUsed()));
|
||||||
|
|
||||||
if (!expectedGas.equals(actualGas)) {
|
if (!expectedGas.equals(actualGas)) {
|
||||||
|
|
||||||
String output =
|
String output =
|
||||||
String.format("Gas remaining is different. Expected gas remaining: [ %s ], actual gas remaining: [ %s ]",
|
String.format("Gas remaining is different. Expected gas remaining: [ %s ], actual gas remaining: [ %s ]",
|
||||||
expectedGas.toString() ,
|
expectedGas.toString() ,
|
||||||
|
@ -413,13 +485,54 @@ public class TestRunner {
|
||||||
* end of if(testCase.getPost().size() == 0)
|
* end of if(testCase.getPost().size() == 0)
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
} finally {
|
} finally {
|
||||||
repository.close();
|
repository.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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() {
|
public ProgramTrace getTrace() {
|
||||||
return trace;
|
return trace;
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,9 +21,7 @@ public class TestSuite {
|
||||||
for (Object key: testCaseJSONObj.keySet()){
|
for (Object key: testCaseJSONObj.keySet()){
|
||||||
|
|
||||||
Object testCaseJSON = testCaseJSONObj.get(key);
|
Object testCaseJSON = testCaseJSONObj.get(key);
|
||||||
|
|
||||||
TestCase testCase = new TestCase(key.toString(), (JSONObject) testCaseJSON);
|
TestCase testCase = new TestCase(key.toString(), (JSONObject) testCaseJSON);
|
||||||
|
|
||||||
testList.add(testCase);
|
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.Block;
|
||||||
import org.ethereum.core.Transaction;
|
import org.ethereum.core.Transaction;
|
||||||
import org.ethereum.net.message.Message;
|
import org.ethereum.net.message.Message;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -12,7 +13,7 @@ import java.util.Set;
|
||||||
* @author: Roman Mandeleil
|
* @author: Roman Mandeleil
|
||||||
* Created on: 12/11/2014 08:34
|
* Created on: 12/11/2014 08:34
|
||||||
*/
|
*/
|
||||||
|
@Component(value="EthereumListener")
|
||||||
public class EthereumListenerWrapper implements EthereumListener{
|
public class EthereumListenerWrapper implements EthereumListener{
|
||||||
|
|
||||||
EthereumListener listener;
|
EthereumListener listener;
|
||||||
|
|
|
@ -60,8 +60,8 @@ public class WorldManager {
|
||||||
|
|
||||||
private final Set<Transaction> pendingTransactions = Collections.synchronizedSet(new HashSet<Transaction>());
|
private final Set<Transaction> pendingTransactions = Collections.synchronizedSet(new HashSet<Transaction>());
|
||||||
|
|
||||||
|
@Autowired
|
||||||
private EthereumListener listener = new EthereumListenerWrapper();
|
private EthereumListener listener;
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void init() {
|
public void init() {
|
||||||
|
|
|
@ -138,6 +138,21 @@ public class ByteUtil {
|
||||||
return 0;
|
return 0;
|
||||||
return new BigInteger(1, b).intValue();
|
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
|
* Turn nibbles to a pretty looking output string
|
||||||
|
|
|
@ -26,6 +26,8 @@ public class GasCost {
|
||||||
public static int SSTORE = 300;
|
public static int SSTORE = 300;
|
||||||
/** Cost 100 gas */
|
/** Cost 100 gas */
|
||||||
public static int RESET_SSTORE = 100;
|
public static int RESET_SSTORE = 100;
|
||||||
|
/** Cost 100 gas */
|
||||||
|
public static int REFUND_SSTORE = 100;
|
||||||
/** Cost 100 gas */
|
/** Cost 100 gas */
|
||||||
public static int CREATE = 100;
|
public static int CREATE = 100;
|
||||||
/** Cost 20 gas */
|
/** Cost 20 gas */
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.ethereum.vmtrace.ProgramTrace;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.spongycastle.util.encoders.Hex;
|
import org.spongycastle.util.encoders.Hex;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -33,7 +34,9 @@ public class Program {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger("VM");
|
private static final Logger logger = LoggerFactory.getLogger("VM");
|
||||||
private static final Logger gasLogger = LoggerFactory.getLogger("gas");
|
private static final Logger gasLogger = LoggerFactory.getLogger("gas");
|
||||||
|
|
||||||
|
ProgramInvokeFactory programInvokeFactory = new ProgramInvokeFactoryImpl();
|
||||||
|
|
||||||
private int invokeHash;
|
private int invokeHash;
|
||||||
private ProgramListener listener;
|
private ProgramListener listener;
|
||||||
|
|
||||||
|
@ -181,7 +184,7 @@ public class Program {
|
||||||
public void stackRequire(int stackSize) {
|
public void stackRequire(int stackSize) {
|
||||||
if (stack.size() < stackSize) {
|
if (stack.size() < stackSize) {
|
||||||
throw new StackTooSmallException("Expected: " + 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());
|
result.getRepository().addBalance(senderAddress, endowment.negate());
|
||||||
BigInteger newBalance = result.getRepository().addBalance(newAddress, endowment);
|
BigInteger newBalance = result.getRepository().addBalance(newAddress, endowment);
|
||||||
|
|
||||||
Repository track = result.getRepository().startTracking();
|
|
||||||
|
|
||||||
// [3] UPDATE THE NONCE
|
// [3] UPDATE THE NONCE
|
||||||
// (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION)
|
// (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
|
// [5] COOK THE INVOKE AND EXECUTE
|
||||||
ProgramInvoke programInvoke = ProgramInvokeFactory.createProgramInvoke(
|
ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke(
|
||||||
this, new DataWord(newAddress), DataWord.ZERO, gasLimit,
|
this, new DataWord(newAddress), DataWord.ZERO, gasLimit,
|
||||||
newBalance, null, track);
|
newBalance, null, track);
|
||||||
|
|
||||||
|
@ -427,7 +430,7 @@ public class Program {
|
||||||
this.spendGas(msg.getGas().longValue(), "internal call");
|
this.spendGas(msg.getGas().longValue(), "internal call");
|
||||||
|
|
||||||
Repository trackRepository = result.getRepository().startTracking();
|
Repository trackRepository = result.getRepository().startTracking();
|
||||||
ProgramInvoke programInvoke = ProgramInvokeFactory.createProgramInvoke(
|
ProgramInvoke programInvoke = programInvokeFactory.createProgramInvoke(
|
||||||
this, new DataWord(contextAddress), msg.getEndowment(),
|
this, new DataWord(contextAddress), msg.getEndowment(),
|
||||||
msg.getGas(), contextBalance, data, trackRepository);
|
msg.getGas(), contextBalance, data, trackRepository);
|
||||||
|
|
||||||
|
@ -507,6 +510,10 @@ public class Program {
|
||||||
result.refundGas(gasValue);
|
result.refundGas(gasValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void futureRefundGas(long gasValue) {
|
||||||
|
result.futureRefundGas(gasValue);
|
||||||
|
}
|
||||||
|
|
||||||
public void storageSave(DataWord word1, DataWord word2) {
|
public void storageSave(DataWord word1, DataWord word2) {
|
||||||
storageSave(word1.getData(), word2.getData());
|
storageSave(word1.getData(), word2.getData());
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,196 +2,24 @@ package org.ethereum.vm;
|
||||||
|
|
||||||
import org.ethereum.core.Block;
|
import org.ethereum.core.Block;
|
||||||
import org.ethereum.core.Transaction;
|
import org.ethereum.core.Transaction;
|
||||||
import org.ethereum.facade.Blockchain;
|
|
||||||
import org.ethereum.facade.Repository;
|
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;
|
import java.math.BigInteger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* www.ethereumJ.com
|
* www.etherj.com
|
||||||
*
|
*
|
||||||
* @author: Roman Mandeleil
|
* @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
|
public ProgramInvoke createProgramInvoke(Transaction tx, Block block, Repository repository);
|
||||||
private Blockchain blockchain;
|
public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress,
|
||||||
|
|
||||||
/**
|
|
||||||
* 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,
|
|
||||||
DataWord inValue, DataWord inGas,
|
DataWord inValue, DataWord inGas,
|
||||||
BigInteger balanceInt, byte[] dataIn,
|
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 RuntimeException exception;
|
||||||
private List<DataWord> deleteAccounts;
|
private List<DataWord> deleteAccounts;
|
||||||
private List<LogInfo> logInfoList;
|
private List<LogInfo> logInfoList;
|
||||||
|
private long futureRefund = 0;
|
||||||
|
|
||||||
private Repository repository = null;
|
private Repository repository = null;
|
||||||
|
|
||||||
|
@ -105,4 +106,12 @@ public class ProgramResult {
|
||||||
callCreateList = new ArrayList<>();
|
callCreateList = new ArrayList<>();
|
||||||
callCreateList.add(new CallCreate(data, destination, gasLimit, value));
|
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.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.spongycastle.util.BigIntegers;
|
||||||
import org.spongycastle.util.encoders.Hex;
|
import org.spongycastle.util.encoders.Hex;
|
||||||
import org.ethereum.vm.MessageCall.MsgType;
|
import org.ethereum.vm.MessageCall.MsgType;
|
||||||
|
|
||||||
|
@ -106,10 +107,13 @@ public class VM {
|
||||||
DataWord oldValue = program.storageLoad(stack.peek());
|
DataWord oldValue = program.storageLoad(stack.peek());
|
||||||
if (oldValue == null && !newValue.isZero())
|
if (oldValue == null && !newValue.isZero())
|
||||||
gasCost = GasCost.SSTORE;
|
gasCost = GasCost.SSTORE;
|
||||||
else if (oldValue != null && newValue.isZero())
|
else if (oldValue != null && newValue.isZero()) {
|
||||||
// todo: GASREFUND counter policy
|
// todo: GASREFUND counter policy
|
||||||
System.currentTimeMillis();
|
|
||||||
else
|
// refund step cost policy.
|
||||||
|
program.futureRefundGas( GasCost.REFUND_SSTORE );
|
||||||
|
gasCost = 0;
|
||||||
|
} else
|
||||||
gasCost = GasCost.RESET_SSTORE;
|
gasCost = GasCost.RESET_SSTORE;
|
||||||
break;
|
break;
|
||||||
case SLOAD:
|
case SLOAD:
|
||||||
|
@ -170,10 +174,16 @@ public class VM {
|
||||||
|
|
||||||
int nTopics = op.val() - OpCode.LOG0.val();
|
int nTopics = op.val() - OpCode.LOG0.val();
|
||||||
newMemSize = memNeeded(stack.peek(), stack.get(stack.size()-2));
|
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 = GasCost.LOG_GAS +
|
||||||
GasCost.LOG_TOPIC_GAS * nTopics +
|
GasCost.LOG_TOPIC_GAS * nTopics +
|
||||||
GasCost.LOG_DATA_GAS * stack.get(stack.size()-2).longValue();
|
GasCost.LOG_DATA_GAS * stack.get(stack.size()-2).longValue();
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case EXP:
|
case EXP:
|
||||||
|
|
||||||
|
@ -632,18 +642,22 @@ public class VM {
|
||||||
BigInteger codeOffsetData = program.stackPop().value();
|
BigInteger codeOffsetData = program.stackPop().value();
|
||||||
BigInteger lengthData = program.stackPop().value();
|
BigInteger lengthData = program.stackPop().value();
|
||||||
|
|
||||||
if (fullCode == null
|
/*
|
||||||
|| BigInteger.valueOf(fullCode.length).compareTo(
|
todo: find out what to do where params are exits the actual code
|
||||||
codeOffsetData.add(lengthData)) < 0) {
|
if (fullCode == null ||
|
||||||
|
BigInteger.valueOf(fullCode.length).
|
||||||
|
compareTo(
|
||||||
|
codeOffsetData.add(lengthData)) > 0) {
|
||||||
program.stop();
|
program.stop();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
int length = lengthData.intValue();
|
int length = lengthData.intValue();
|
||||||
int codeOffset = codeOffsetData.intValue();
|
int codeOffset = codeOffsetData.intValue();
|
||||||
|
|
||||||
byte[] codeCopy = new byte[length];
|
byte[] codeCopy = new byte[fullCode.length - codeOffset];
|
||||||
System.arraycopy(fullCode, codeOffset, codeCopy, 0, length);
|
System.arraycopy(fullCode, codeOffset, codeCopy, 0, fullCode.length - codeOffset);
|
||||||
|
|
||||||
if (logger.isInfoEnabled())
|
if (logger.isInfoEnabled())
|
||||||
hint = "code: " + Hex.toHexString(codeCopy);
|
hint = "code: " + Hex.toHexString(codeCopy);
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package test.ethereum.jsontestsuite;
|
package test.ethereum.jsontestsuite;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.ethereum.jsontestsuite.TestCase;
|
import org.ethereum.jsontestsuite.*;
|
||||||
import org.ethereum.jsontestsuite.TestRunner;
|
|
||||||
import org.ethereum.jsontestsuite.TestSuite;
|
|
||||||
import org.json.simple.JSONObject;
|
import org.json.simple.JSONObject;
|
||||||
import org.json.simple.parser.JSONParser;
|
import org.json.simple.parser.JSONParser;
|
||||||
import org.json.simple.parser.ParseException;
|
import org.json.simple.parser.ParseException;
|
||||||
|
@ -14,6 +13,8 @@ import org.junit.Assume;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.junit.runners.Suite;
|
import org.junit.runners.Suite;
|
||||||
import org.junit.runners.Suite.SuiteClasses;
|
import org.junit.runners.Suite.SuiteClasses;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test file specific for tests maintained in the GitHub repository
|
* Test file specific for tests maintained in the GitHub repository
|
||||||
|
@ -26,6 +27,9 @@ import org.junit.runners.Suite.SuiteClasses;
|
||||||
GitHubVMTest.class,
|
GitHubVMTest.class,
|
||||||
})
|
})
|
||||||
public class GitHubJSONTestSuite {
|
public class GitHubJSONTestSuite {
|
||||||
|
|
||||||
|
private static Logger logger = LoggerFactory.getLogger("TCK-Test");
|
||||||
|
|
||||||
|
|
||||||
protected static void runGitHubJsonTest(String json) throws ParseException {
|
protected static void runGitHubJsonTest(String json) throws ParseException {
|
||||||
Assume.assumeFalse("Online test is not available", json.equals(""));
|
Assume.assumeFalse("Online test is not available", json.equals(""));
|
||||||
|
@ -45,4 +49,59 @@ public class GitHubJSONTestSuite {
|
||||||
Assert.assertTrue(result.isEmpty());
|
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