From 8cad126c0c73041c28137d640afdf9bce8d0818d Mon Sep 17 00:00:00 2001 From: romanman Date: Thu, 12 Jun 2014 16:18:01 +0100 Subject: [PATCH] CALL: first draft + recursive contract tested + tracking changes for trie introduced + genesis updated to protocol 20 --- .../java/org/ethereum/core/Blockchain.java | 5 +- .../org/ethereum/core/ContractDetails.java | 6 +- .../main/java/org/ethereum/core/Genesis.java | 28 +-- .../java/org/ethereum/core/Transaction.java | 2 +- .../main/java/org/ethereum/db/Database.java | 98 +------- .../java/org/ethereum/db/DatabaseImpl.java | 95 ++++++++ .../java/org/ethereum/db/TrackDatabase.java | 2 +- .../org/ethereum/gui/ProgramPlayDialog.java | 15 +- .../org/ethereum/manager/WorldManager.java | 220 +++++++++++------- .../ethereum/net/message/StaticMessages.java | 4 +- .../java/org/ethereum/trie/TrackTrie.java | 83 +++++++ .../src/main/java/org/ethereum/trie/Trie.java | 14 +- .../java/org/ethereum/trie/TrieFacade.java | 17 ++ .../main/java/org/ethereum/util/ByteUtil.java | 19 ++ .../main/java/org/ethereum/vm/DataWord.java | 7 +- .../main/java/org/ethereum/vm/Program.java | 171 ++++++++++++-- .../java/org/ethereum/vm/ProgramInvoke.java | 8 + .../org/ethereum/vm/ProgramInvokeFactory.java | 123 +++++++++- .../org/ethereum/vm/ProgramInvokeImpl.java | 61 ++++- .../java/org/ethereum/vm/ProgramResult.java | 32 ++- .../src/main/java/org/ethereum/vm/VM.java | 12 +- .../java/org/ethereum/core/BlockTest.java | 4 +- .../org/ethereum/db/TrackDatabaseTest.java | 2 +- .../ethereum/vm/ProgramInvokeMockImpl.java | 17 ++ 24 files changed, 802 insertions(+), 243 deletions(-) create mode 100644 ethereumj-core/src/main/java/org/ethereum/db/DatabaseImpl.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/trie/TrackTrie.java create mode 100644 ethereumj-core/src/main/java/org/ethereum/trie/TrieFacade.java diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java b/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java index 08803e31..e85fb122 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Blockchain.java @@ -1,6 +1,6 @@ package org.ethereum.core; -import org.ethereum.db.Database; +import org.ethereum.db.DatabaseImpl; import org.ethereum.manager.WorldManager; import org.ethereum.net.message.StaticMessages; import org.ethereum.net.submit.WalletTransaction; @@ -10,7 +10,6 @@ import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; import java.io.IOException; -import java.nio.ByteBuffer; import java.util.*; import static org.ethereum.core.Denomination.*; @@ -31,7 +30,7 @@ public class Blockchain { // to avoid using minGasPrice=0 from Genesis for the wallet private static long INITIAL_MIN_GAS_PRICE = 10 * SZABO.longValue(); - private Database db; + private DatabaseImpl db; private Wallet wallet; private long gasPrice = 1000; diff --git a/ethereumj-core/src/main/java/org/ethereum/core/ContractDetails.java b/ethereumj-core/src/main/java/org/ethereum/core/ContractDetails.java index dd5083a1..3aee5995 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/ContractDetails.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/ContractDetails.java @@ -48,8 +48,6 @@ public class ContractDetails { RLPItem rlpItem = (RLPItem)values.get(i); storageValues.add(new DataWord(rlpItem.getRLPData())); } - - System.out.println(); } public ContractDetails(Map storage) { @@ -97,7 +95,9 @@ public class ContractDetails { Map storage = new HashMap<>(); - for (int i = 0; i < storageKeys.size(); ++i){ + for (int i = 0; + storageKeys != null && + i < storageKeys.size(); ++i){ storage.put(storageKeys.get(i), storageValues.get(i)); } diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Genesis.java b/ethereumj-core/src/main/java/org/ethereum/core/Genesis.java index 9db273e7..4ebef084 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Genesis.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Genesis.java @@ -26,22 +26,24 @@ import org.spongycastle.util.encoders.Hex; * See Yellow Paper: http://www.gavwood.com/Paper.pdf (Appendix I. Genesis Block) */ public class Genesis extends Block { - + + private String[] premine = new String[] { + "51ba59315b3a95761d0863b05ccc7a7f54703d99", + "e4157b34ea9615cfbde6b4fda419828124b70c78", // # (CH) + "1e12515ce3e0f817a4ddef9ca55788a1d66bd2df", // # (V) + "6c386a4b26f73c802f34673f7248bb118f97424a", // # (HH) + "cd2a3d9f938e13cd947ec05abc7fe734df8dd826", // # (R) + "2ef47100e0787b915105fd5e3f4ff6752079d5cb", // # (M) + "e6716f9544a56c530d868e4bfbacb172315bdead", // # (J) + "1a26338f0d905e295fccb71fa9ea849ffa12aaf4", // # (A) + }; + Logger logger = LoggerFactory.getLogger(this.getClass()); - - // The proof-of-concept series include a development premine, making the state root hash + // The proof-of-concept series include a development premine, making the state root hash // some value stateRoot. The latest documentation should be consulted for the value of the state root. private AccountState acct = new AccountState(BigInteger.ZERO, BigInteger.valueOf(2).pow(200)); - private String[] premine = new String[] { - "2ef47100e0787b915105fd5e3f4ff6752079d5cb", // # (M) - "1a26338f0d905e295fccb71fa9ea849ffa12aaf4", // # (A) - "e6716f9544a56c530d868e4bfbacb172315bdead", // # (J) - "8a40bfaa73256b60764c1bf40675a99083efb075", // # (G) - "e4157b34ea9615cfbde6b4fda419828124b70c78", // # (CH) - "1e12515ce3e0f817a4ddef9ca55788a1d66bd2df", // # (V) - "6c386a4b26f73c802f34673f7248bb118f97424a", // # (HH) - "cd2a3d9f938e13cd947ec05abc7fe734df8dd826" }; // # (R) - + + private static byte[] zeroHash256 = new byte[32]; private static byte[] zeroHash160 = new byte[20]; private static byte[] sha3EmptyList = HashUtil.sha3(RLP.encodeList()); diff --git a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java index 3438cb5b..a1ccf1c7 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/Transaction.java @@ -156,7 +156,7 @@ public class Transaction { } // TODO: performance improve multiply without BigInteger - public BigInteger getTotalGasDebit(){ + public BigInteger getTotalGasValueDebit(){ return new BigInteger(1, gasLimit).multiply(new BigInteger(1,gasPrice)); } diff --git a/ethereumj-core/src/main/java/org/ethereum/db/Database.java b/ethereumj-core/src/main/java/org/ethereum/db/Database.java index 78e61c28..b41bdd22 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/Database.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/Database.java @@ -1,95 +1,15 @@ package org.ethereum.db; -import static org.iq80.leveldb.impl.Iq80DBFactory.factory; - -import java.io.File; -import java.io.IOException; - -import org.ethereum.config.SystemProperties; -import org.iq80.leveldb.DB; -import org.iq80.leveldb.DBIterator; -import org.iq80.leveldb.Options; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - /** - * Generic interface for Ethereum database - * - * LevelDB key/value pair DB implementation will be used. - * Choice must be made between: - * Pure Java: https://github.com/dain/leveldb - * JNI binding: https://github.com/fusesource/leveldbjni + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 11/06/2014 15:38 */ -public class Database { - - private static Logger logger = LoggerFactory.getLogger(Database.class); - private DB db; - private String name; - - public Database(String name) { - // Initialize Database - this.name = name; - Options options = new Options(); - options.createIfMissing(true); - try { - logger.debug("Opening database"); - if(SystemProperties.CONFIG.databaseReset()) { - logger.debug("Destroying '" + name + "' DB on startup ENABLED"); - destroyDB(name); - } - logger.debug("Initializing new or existing DB: '" + name + "'"); - options.createIfMissing(true); - db = factory.open(new File(name), options); -// logger.debug("Showing database stats"); -// String stats = DATABASE.getProperty("leveldb.stats"); -// logger.debug(stats); - } catch (IOException ioe) { - logger.error(ioe.getMessage(), ioe); - throw new RuntimeException("Can't initialize database"); - } - } - - public void destroyDB(String name) { - logger.debug("Destroying existing DB"); - Options options = new Options(); - try { - factory.destroy(new File(name), options); - } catch (IOException e) { - logger.error(e.getMessage(), e); - } - } - - /** Insert object(value) (key = sha3(value)) */ - public void put(byte[] key, byte[] value) { - db.put(key, value); - } - - /** Get object (key) -> value */ - public byte[] get(byte[] key) { - return db.get(key); - } - - /** Delete object (key) from db **/ - public void delete(byte[] key) { - delete(key); - } - - public DBIterator iterator() { - return db.iterator(); - } - - public DB getDb() { - return this.db; - } +public interface Database { - public void close(){ - try { - logger.info("Release DB: {}", name); - db.close(); - } catch (IOException e) { - logger.error("failed to find the db file on the close: {} ", name); - } - } - -} \ No newline at end of file + public void put(byte[] key, byte[] value); + public byte[] get(byte[] key); + public void delete(byte[] key); +} diff --git a/ethereumj-core/src/main/java/org/ethereum/db/DatabaseImpl.java b/ethereumj-core/src/main/java/org/ethereum/db/DatabaseImpl.java new file mode 100644 index 00000000..3e702b9e --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/db/DatabaseImpl.java @@ -0,0 +1,95 @@ +package org.ethereum.db; + +import static org.iq80.leveldb.impl.Iq80DBFactory.factory; + +import java.io.File; +import java.io.IOException; + +import org.ethereum.config.SystemProperties; +import org.iq80.leveldb.DB; +import org.iq80.leveldb.DBIterator; +import org.iq80.leveldb.Options; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Generic interface for Ethereum database + * + * LevelDB key/value pair DB implementation will be used. + * Choice must be made between: + * Pure Java: https://github.com/dain/leveldb + * JNI binding: https://github.com/fusesource/leveldbjni + */ +public class DatabaseImpl implements Database{ + + private static Logger logger = LoggerFactory.getLogger(DatabaseImpl.class); + private DB db; + private String name; + + + public DatabaseImpl(String name) { + // Initialize Database + this.name = name; + Options options = new Options(); + options.createIfMissing(true); + try { + logger.debug("Opening database"); + if(SystemProperties.CONFIG.databaseReset()) { + logger.debug("Destroying '" + name + "' DB on startup ENABLED"); + destroyDB(name); + } + logger.debug("Initializing new or existing DB: '" + name + "'"); + options.createIfMissing(true); + db = factory.open(new File(name), options); +// logger.debug("Showing database stats"); +// String stats = DATABASE.getProperty("leveldb.stats"); +// logger.debug(stats); + } catch (IOException ioe) { + logger.error(ioe.getMessage(), ioe); + throw new RuntimeException("Can't initialize database"); + } + } + + public void destroyDB(String name) { + logger.debug("Destroying existing DB"); + Options options = new Options(); + try { + factory.destroy(new File(name), options); + } catch (IOException e) { + logger.error(e.getMessage(), e); + } + } + + /** Insert object(value) (key = sha3(value)) */ + public void put(byte[] key, byte[] value) { + db.put(key, value); + } + + /** Get object (key) -> value */ + public byte[] get(byte[] key) { + return db.get(key); + } + + /** Delete object (key) from db **/ + public void delete(byte[] key) { + delete(key); + } + + public DBIterator iterator() { + return db.iterator(); + } + + public DB getDb() { + return this.db; + } + + public void close(){ + try { + logger.info("Release DB: {}", name); + db.close(); + } catch (IOException e) { + logger.error("failed to find the db file on the close: {} ", name); + } + } + +} \ No newline at end of file diff --git a/ethereumj-core/src/main/java/org/ethereum/db/TrackDatabase.java b/ethereumj-core/src/main/java/org/ethereum/db/TrackDatabase.java index 221ec255..afcffcfa 100644 --- a/ethereumj-core/src/main/java/org/ethereum/db/TrackDatabase.java +++ b/ethereumj-core/src/main/java/org/ethereum/db/TrackDatabase.java @@ -10,7 +10,7 @@ import java.util.Map; * Created on: 11/06/2014 14:09 */ -public class TrackDatabase { +public class TrackDatabase implements Database{ private Database db; diff --git a/ethereumj-core/src/main/java/org/ethereum/gui/ProgramPlayDialog.java b/ethereumj-core/src/main/java/org/ethereum/gui/ProgramPlayDialog.java index 5a05579f..dcb4ea47 100644 --- a/ethereumj-core/src/main/java/org/ethereum/gui/ProgramPlayDialog.java +++ b/ethereumj-core/src/main/java/org/ethereum/gui/ProgramPlayDialog.java @@ -3,6 +3,9 @@ package org.ethereum.gui; import org.ethereum.core.Block; import org.ethereum.core.ContractDetails; import org.ethereum.core.Transaction; +import org.ethereum.db.TrackDatabase; +import org.ethereum.manager.WorldManager; +import org.ethereum.trie.TrackTrie; import org.ethereum.vm.Program; import org.ethereum.vm.ProgramInvokeFactory; import org.ethereum.vm.ProgramInvokeImpl; @@ -40,8 +43,17 @@ public class ProgramPlayDialog extends JPanel implements ActionListener, outputList = new ArrayList(); VM vm = new VM(); + TrackDatabase trackDetailDB = new TrackDatabase( WorldManager.instance.detaildDB ); + TrackDatabase trackChainDb = new TrackDatabase( WorldManager.instance.chainDB); + TrackTrie trackStateDB = new TrackTrie(WorldManager.instance.worldState ); + Program program = new Program(code , - ProgramInvokeFactory.createProgramInvoke(tx, lastBlock, contractDetails)); + ProgramInvokeFactory.createProgramInvoke(tx, lastBlock, contractDetails, + trackDetailDB, trackChainDb, trackStateDB)); + + trackDetailDB.rollbackTrack(); + trackChainDb.rollbackTrack(); + trackStateDB.rollbackTrack(); program.addListener(this); program.fullTrace(); @@ -53,7 +65,6 @@ public class ProgramPlayDialog extends JPanel implements ActionListener, stepSlider = new JSlider(JSlider.HORIZONTAL, 0, outputList.size() - 1, 0); - stepSlider.addChangeListener(this); //Turn on labels at major tick marks. diff --git a/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java b/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java index d9215caf..b410c930 100644 --- a/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java +++ b/ethereumj-core/src/main/java/org/ethereum/manager/WorldManager.java @@ -5,7 +5,9 @@ import org.ethereum.core.Block; import org.ethereum.core.ContractDetails; import org.ethereum.core.Transaction; import org.ethereum.crypto.HashUtil; -import org.ethereum.db.Database; +import org.ethereum.db.DatabaseImpl; +import org.ethereum.db.TrackDatabase; +import org.ethereum.trie.TrackTrie; import org.ethereum.trie.Trie; import org.ethereum.vm.*; import org.slf4j.Logger; @@ -37,9 +39,9 @@ public class WorldManager { private Map pendingTransactions = Collections.synchronizedMap(new HashMap()); - public Database chainDB = new Database("blockchain"); - public Database stateDB = new Database("state"); - public Database detaildDB = new Database("details"); + public DatabaseImpl chainDB = new DatabaseImpl("blockchain"); + public DatabaseImpl stateDB = new DatabaseImpl("state"); + public DatabaseImpl detaildDB = new DatabaseImpl("details"); public Trie worldState = new Trie(stateDB.getDb()); @@ -48,6 +50,8 @@ public class WorldManager { // TODO: refactor the wallet transactions to the world manager MainData.instance.getBlockchain().addWalletTransaction(tx); + // TODO: what is going on with simple wallet transfer + // 1. VALIDATE THE NONCE byte[] senderAddress = tx.getSender(); byte[] stateData = worldState.get(senderAddress); @@ -66,18 +70,26 @@ public class WorldManager { return; } - // 2. THE SIMPLE BALANCE CHANGE SHOULD HAPPEN ANYWAY - AccountState receiverState = null; + // 2.1 PERFORM THE GAS VALUE TX + // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) - // Check if the receive is a new contract + // first of all debit the gas from the issuer + AccountState receiverState = null; + BigInteger gasDebit = tx.getTotalGasValueDebit(); + byte[] contractAddress; + + // Contract creation or existing Contract call if (tx.isContractCreation()) { - byte[] contractAddress = tx.getContractAddress(); + + // credit the receiver + contractAddress = tx.getContractAddress(); receiverState = new AccountState(); worldState.update(contractAddress, receiverState.getEncoded()); stateLogger.info("New contract created address={}", Hex.toHexString(contractAddress)); } else { - // receiver was not set by creation of contract + + contractAddress = tx.getReceiveAddress(); byte[] accountData = this.worldState.get(tx.getReceiveAddress()); if (accountData.length == 0){ receiverState = new AccountState(); @@ -87,98 +99,134 @@ public class WorldManager { } else { receiverState = new AccountState(accountData); if (stateLogger.isInfoEnabled()) - stateLogger.info("Account updated address={}", + stateLogger.info("Account found address={}", Hex.toHexString(tx.getReceiveAddress())); } } - if(tx.getValue() != null) { - receiverState.addToBalance(new BigInteger(1, tx.getValue())); - senderState.addToBalance(new BigInteger(1, tx.getValue()).negate()); - } + // 2.2 UPDATE THE NONCE + // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) if (senderState.getBalance().compareTo(BigInteger.ZERO) == 1) { senderState.incrementNonce(); worldState.update(tx.getSender(), senderState.getEncoded()); - worldState.update(tx.getReceiveAddress(), receiverState.getEncoded()); - } - - // 3. FIND OUT WHAT IS THE TRANSACTION TYPE - if (tx.isContractCreation()) { - - byte[] initCode = tx.getData(); - - Block lastBlock = - MainData.instance.getBlockchain().getLastBlock(); - - ProgramInvoke programInvoke = - ProgramInvokeFactory.createProgramInvoke(tx, lastBlock, null); - - if (logger.isInfoEnabled()) - logger.info("running the init for contract: addres={}" , - Hex.toHexString(tx.getContractAddress())); - - // first of all debit the gas from the issuer - BigInteger gasDebit = tx.getTotalGasDebit(); - senderState.addToBalance(gasDebit.negate()); - if (senderState.getBalance().signum() == -1){ - // todo: the sender can't afford this contract do Out-Of-Gas - - } if(stateLogger.isInfoEnabled()) - stateLogger.info("Before contract execution the sender address debit with gas total cost, \n sender={} \n contract={} \n gas_debit= {}", - Hex.toHexString( tx.getSender() ), Hex.toHexString(tx.getContractAddress()), gasDebit); + stateLogger.info("Before contract execution the sender address debit with gas total cost, " + + "\n sender={} \n gas_debit= {}", + Hex.toHexString( tx.getSender() ), gasDebit); + + } + + // actual gas value debit from the sender + // the purchase gas will be available for the + // contract in the execution state, and + // can be validate using GAS op + if (gasDebit.signum() == 1){ + + if (senderState.getBalance().subtract(gasDebit).signum() == -1){ + logger.info("No gas to start the execution: sender={}" , Hex.toHexString(tx.getSender())); + return; + } + senderState.addToBalance(gasDebit.negate()); worldState.update(senderAddress, senderState.getEncoded()); + } - VM vm = new VM(); - Program program = new Program(initCode, programInvoke); - vm.play(program); - ProgramResult result = program.getResult(); - applyProgramResult(result, gasDebit, senderState, receiverState, senderAddress, tx.getContractAddress()); + // 3. START TRACKING FOR REVERT CHANGES OPTION !!! + TrackDatabase trackDetailDB = new TrackDatabase( WorldManager.instance.detaildDB ); + TrackDatabase trackChainDb = new TrackDatabase( WorldManager.instance.chainDB); + TrackTrie trackStateDB = new TrackTrie(WorldManager.instance.worldState ); - } else { + trackDetailDB.startTrack(); + trackChainDb.startTrack(); + trackStateDB.startTrack(); - if (receiverState.getCodeHash() != HashUtil.EMPTY_DATA_HASH){ + try { - byte[] programCode = chainDB.get(receiverState.getCodeHash()); - if (programCode != null && programCode.length != 0){ + // 4. THE SIMPLE VALUE/BALANCE CHANGE + if(tx.getValue() != null) { - Block lastBlock = - MainData.instance.getBlockchain().getLastBlock(); + if (senderState.getBalance().subtract(new BigInteger(1, tx.getValue())).signum() >= 0){ + receiverState.addToBalance(new BigInteger(1, tx.getValue())); + senderState.addToBalance(new BigInteger(1, tx.getValue()).negate()); - if (logger.isInfoEnabled()) - logger.info("calling for existing contract: addres={}" , Hex.toHexString(tx.getReceiveAddress())); + trackStateDB.update(senderAddress, senderState.getEncoded()); + trackStateDB.update(contractAddress, receiverState.getEncoded()); - // first of all debit the gas from the issuer - BigInteger gasDebit = tx.getTotalGasDebit(); - senderState.addToBalance(gasDebit.negate()); - if (senderState.getBalance().signum() == -1){ - // todo: the sender can't afford this contract do Out-Of-Gas - } - - if(stateLogger.isInfoEnabled()) - stateLogger.info("Before contract execution the sender address debit with gas total cost, \n sender={} \n contract={} \n gas_debit= {}", - Hex.toHexString( tx.getSender() ), Hex.toHexString(tx.getReceiveAddress()), gasDebit); - worldState.update(senderAddress, senderState.getEncoded()); - - // FETCH THE SAVED STORAGE - ContractDetails details = null; - byte[] detailsRLPData = detaildDB.get(tx.getReceiveAddress()); - if (detailsRLPData.length > 0) - details = new ContractDetails(detailsRLPData); - - ProgramInvoke programInvoke = - ProgramInvokeFactory.createProgramInvoke(tx, lastBlock, details); - - VM vm = new VM(); - Program program = new Program(programCode, programInvoke); - vm.play(program); - - ProgramResult result = program.getResult(); - applyProgramResult(result, gasDebit, senderState, receiverState, senderAddress, tx.getReceiveAddress()); + if (stateLogger.isInfoEnabled()) + stateLogger.info("Update value balance \n " + + "sender={}, receiver={}, value={}", + Hex.toHexString(senderAddress), + Hex.toHexString(contractAddress), + new BigInteger( tx.getValue())); } } + + // 3. FIND OUT WHAT IS THE TRANSACTION TYPE + if (tx.isContractCreation()) { + + byte[] initCode = tx.getData(); + + Block lastBlock = + MainData.instance.getBlockchain().getLastBlock(); + + ProgramInvoke programInvoke = + ProgramInvokeFactory.createProgramInvoke(tx, lastBlock, null, trackDetailDB, trackChainDb, trackStateDB); + + if (logger.isInfoEnabled()) + logger.info("running the init for contract: addres={}" , + Hex.toHexString(tx.getContractAddress())); + + + VM vm = new VM(); + Program program = new Program(initCode, programInvoke); + vm.play(program); + ProgramResult result = program.getResult(); + applyProgramResult(result, gasDebit, senderState, receiverState, senderAddress, tx.getContractAddress()); + + } else { + + if (receiverState.getCodeHash() != HashUtil.EMPTY_DATA_HASH){ + + byte[] programCode = chainDB.get(receiverState.getCodeHash()); + if (programCode != null && programCode.length != 0){ + + Block lastBlock = + MainData.instance.getBlockchain().getLastBlock(); + + if (logger.isInfoEnabled()) + logger.info("calling for existing contract: addres={}" , Hex.toHexString(tx.getReceiveAddress())); + + + // FETCH THE SAVED STORAGE + ContractDetails details = null; + byte[] detailsRLPData = detaildDB.get(tx.getReceiveAddress()); + if (detailsRLPData.length > 0) + details = new ContractDetails(detailsRLPData); + + ProgramInvoke programInvoke = + ProgramInvokeFactory.createProgramInvoke(tx, lastBlock, details, trackDetailDB, trackChainDb, trackStateDB); + + VM vm = new VM(); + Program program = new Program(programCode, programInvoke); + vm.play(program); + + ProgramResult result = program.getResult(); + applyProgramResult(result, gasDebit, senderState, receiverState, senderAddress, tx.getReceiveAddress()); + } + } + } + } catch (RuntimeException e) { + + trackDetailDB.rollbackTrack(); + trackChainDb.rollbackTrack(); + trackStateDB.rollbackTrack(); + return; } + + trackDetailDB.commitTrack(); + trackChainDb.commitTrack(); + trackStateDB.commitTrack(); + pendingTransactions.put(Hex.toHexString(tx.getHash()), tx); } @@ -201,9 +249,9 @@ public class WorldManager { if (result.getException() != null && result.getException() instanceof Program.OutOfGasException){ + logger.info("contract run halted by OutOfGas: contract={}", Hex.toHexString(contractAddress)); - // todo: find out what exactly should be reverted in that case - return; + throw result.getException(); } // Save the code created by init @@ -239,12 +287,6 @@ public class WorldManager { Hex.toHexString(bodyCode)); } - // Save the storage changes. - Map storage = result.getStorage(); - if (storage != null){ - ContractDetails contractDetails = new ContractDetails(storage); - detaildDB.put(contractAddress , contractDetails.getEncoded()); - } } public void applyTransactionList(List txList) { diff --git a/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java b/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java index 74ec089f..632af15c 100644 --- a/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java +++ b/ethereumj-core/src/main/java/org/ethereum/net/message/StaticMessages.java @@ -16,7 +16,7 @@ public class StaticMessages { public static final byte[] GET_TRANSACTIONS = Hex.decode("2240089100000002C116"); public static final byte[] DISCONNECT_08 = Hex.decode("2240089100000003C20108"); - public static final byte[] GENESIS_HASH = Hex.decode("77ef4fdaf389dca53236bcf7f72698e154eab2828f86fbc4fc6cd9225d285c89"); + public static final byte[] GENESIS_HASH = Hex.decode("56fff6ab5ef6f1ef8dafb7b4571b89a9ae1ab870e54197c59ea10ba6f2c7eb60"); public static final byte[] MAGIC_PACKET = Hex.decode("22400891"); static { @@ -26,7 +26,7 @@ public class StaticMessages { public static HelloMessage generateHelloMessage(){ byte[] peerIdBytes = HashUtil.randomPeerId(); - return new HelloMessage((byte) 0x13, (byte) 0x00, + return new HelloMessage((byte) 0x14, (byte) 0x00, "EthereumJ [v0.5.1] by RomanJ", (byte) 0b00000111, (short) 30303, peerIdBytes); } diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/TrackTrie.java b/ethereumj-core/src/main/java/org/ethereum/trie/TrackTrie.java new file mode 100644 index 00000000..d190444c --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/trie/TrackTrie.java @@ -0,0 +1,83 @@ +package org.ethereum.trie; + +import org.ethereum.db.ByteArrayWrapper; +import org.ethereum.db.Database; + +import java.util.HashMap; +import java.util.Map; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 11/06/2014 19:47 + */ + +public class TrackTrie implements TrieFacade { + + + private TrieFacade trie; + + private boolean trackingChanges; + private Map changes; + private Map deletes; + + public TrackTrie(TrieFacade trie) { + this.trie = trie ; + } + + + public void startTrack(){ + changes = new HashMap<>(); + deletes = new HashMap<>(); + trackingChanges = true; + } + + public void commitTrack(){ + for(ByteArrayWrapper key : changes.keySet()){ + trie.update(key.getData(), changes.get(key)); + } + changes = null; + trackingChanges = false; + } + + public void rollbackTrack(){ + changes = new HashMap<>(); + deletes = new HashMap<>(); + changes = null; + trackingChanges = false; + } + + + @Override + public void update(byte[] key, byte[] value) { + if (trackingChanges){ + changes.put( new ByteArrayWrapper(key) , value); + } else { + trie.update(key, value); + } + } + + @Override + public byte[] get(byte[] key) { + if(trackingChanges){ + ByteArrayWrapper wKey = new ByteArrayWrapper(key); + if (deletes.get(wKey) != null) return null; + if (changes.get(wKey) != null) return changes.get(wKey); + return trie.get(key); + } + return trie.get(key); + } + + @Override + public void delete(byte[] key) { + if (trackingChanges){ + ByteArrayWrapper wKey = new ByteArrayWrapper(key); + deletes.put(wKey, null); + } else { + trie.delete(key); + } + } +} + + diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java b/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java index 68a3355d..56911b2c 100644 --- a/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java +++ b/ethereumj-core/src/main/java/org/ethereum/trie/Trie.java @@ -7,8 +7,10 @@ import static org.ethereum.util.CompactEncoder.*; import java.util.Arrays; import org.ethereum.crypto.HashUtil; +import org.ethereum.db.Database; import org.ethereum.util.Value; import org.iq80.leveldb.DB; +import org.spongycastle.util.encoders.Hex; /** * The modified Merkle Patricia tree (trie) provides a persistent data structure @@ -31,7 +33,7 @@ import org.iq80.leveldb.DB; * @author: Nick Savers * Created on: 20/05/2014 10:44 */ -public class Trie { +public class Trie implements TrieFacade{ private static byte PAIR_SIZE = 2; private static byte LIST_SIZE = 17; @@ -124,6 +126,16 @@ public class Trie { } + /** + * Delete a key/value pair from the trie + * + * @param key + */ + public void delete(byte[] key) { + delete(Hex.encode(key)); + } + + /** * Delete a key/value pair from the trie * diff --git a/ethereumj-core/src/main/java/org/ethereum/trie/TrieFacade.java b/ethereumj-core/src/main/java/org/ethereum/trie/TrieFacade.java new file mode 100644 index 00000000..642493f3 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/trie/TrieFacade.java @@ -0,0 +1,17 @@ +package org.ethereum.trie; + +/** + * www.ethereumJ.com + * + * @author: Roman Mandeleil + * Created on: 11/06/2014 19:44 + */ + +public interface TrieFacade { + + public void update(byte[] key, byte[] value); + public byte[] get(byte[] key); + public void delete(byte[] key); + + +} diff --git a/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java b/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java index aba1236a..1aa32e1e 100644 --- a/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java +++ b/ethereumj-core/src/main/java/org/ethereum/util/ByteUtil.java @@ -154,4 +154,23 @@ public class ByteUtil { } return baos.toByteArray(); } + + public static byte[] stripLeadingZeroes(byte[] data){ + + if (data == null) return null; + + int firstNonZero = 0; + for (int i = 0; i < data.length; ++i) + if (data[i] != 0){ + firstNonZero = i; + break; + } + + if (firstNonZero == 0) return data; + + byte[] result = new byte[data.length - firstNonZero]; + System.arraycopy(data, firstNonZero, result, 0, data.length - firstNonZero); + + return result; + } } \ No newline at end of file diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java b/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java index 3c2317f3..f58602dd 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/DataWord.java @@ -1,5 +1,6 @@ package org.ethereum.vm; +import org.ethereum.util.ByteUtil; import org.spongycastle.util.Arrays; import org.spongycastle.util.encoders.Hex; @@ -55,6 +56,10 @@ public class DataWord { return data; } + public byte[] getNoLeadZeroesData() { + return ByteUtil.stripLeadingZeroes(data); + } + public BigInteger value(){ return new BigInteger(1, data); } @@ -83,9 +88,7 @@ public class DataWord { // when the number is explicit defined // as negative public boolean isNegative(){ - int result = data[0] & 0x80; - return result == 0x80; } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/Program.java b/ethereumj-core/src/main/java/org/ethereum/vm/Program.java index 278f9f13..0e4da1ba 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/Program.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/Program.java @@ -1,6 +1,9 @@ package org.ethereum.vm; +import org.ethereum.core.AccountState; import org.ethereum.core.ContractDetails; +import org.ethereum.db.TrackDatabase; +import org.ethereum.trie.TrackTrie; import org.ethereum.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,19 +36,20 @@ public class Program { ProgramInvoke invokeData; - Map addressChange; - - public Program(byte[] ops, ProgramInvoke invokeData) { - spendGas(GasCost.TRANSACTION); - spendGas(GasCost.TXDATA * invokeData.getDataSize().intValue()); + result.setStateDb(invokeData.getStateDb()); + result.setChainDb(invokeData.getChainDb()); + result.setDetailDB(invokeData.getDetaildDB()); if (ops == null) throw new RuntimeException("program can not run with ops: null"); this.invokeData = invokeData; this.ops = ops; + spendGas(GasCost.TRANSACTION); + spendGas(GasCost.TXDATA * invokeData.getDataSize().intValue()); + if (invokeData.getStorage() != null){ storage = invokeData.getStorage(); } @@ -64,6 +68,12 @@ public class Program { stack.push(stackWord); } + public void stackPushZero(){ + DataWord stackWord = new DataWord(0); + stack.push(stackWord); + } + + public void stackPush(DataWord stackWord){ stack.push(stackWord); } @@ -127,7 +137,7 @@ public class Program { throw new RuntimeException("attempted pull action for empty stack"); } return stack.pop(); - }; + } public int getMemSize(){ @@ -163,12 +173,14 @@ public class Program { int offset = offsetData.value().intValue(); int size = sizeData.value().intValue(); + allocateMemory(offset, new byte[sizeData.intValue()]); byte[] chunk = new byte[size]; - if (memory.limit() < offset + size) size = memory.limit() - offset; - - System.arraycopy(memory.array(), offset, chunk, 0, size); + if (memory != null){ + if (memory.limit() < offset + size) size = memory.limit() - offset; + System.arraycopy(memory.array(), offset, chunk, 0, size); + } return ByteBuffer.wrap(chunk); } @@ -203,13 +215,135 @@ public class Program { } } - public void sendToAddress(byte[] addr, DataWord bChange ){ - DataWord currentBChange = addressChange.get(addr); - if (currentBChange == null){ - addressChange.put(addr, bChange); - } else { - currentBChange.add(bChange); + /** + * That method implement internal calls + * and code invocations + * + * @param gas + * @param toAddressDW + * @param endowmentValue + * @param inDataOffs + * @param inDataSize + * @param outDataOffs + * @param outDataSize + */ + public void callToAddress(DataWord gas, DataWord toAddressDW, DataWord endowmentValue, + DataWord inDataOffs, DataWord inDataSize,DataWord outDataOffs, DataWord outDataSize){ + + ByteBuffer data = memoryChunk(inDataOffs, inDataSize); + + // FETCH THE SAVED STORAGE + ContractDetails details = null; + byte[] toAddress = toAddressDW.getNoLeadZeroesData(); + + byte[] detailsRLPData = invokeData.getDetaildDB().get(toAddress); + if (detailsRLPData != null && detailsRLPData.length > 0) + details = new ContractDetails(detailsRLPData); + + AccountState receiverState; + byte[] accountData = result.getStateDb().get(toAddress); + if (accountData == null){ + + logger.info("no saved address in db to call: address={}" ,Hex.toHexString(toAddress)); + return; + } else{ + + receiverState = new AccountState(accountData); + } + + // todo: endowment rollbacked move it from here + receiverState.addToBalance(endowmentValue.value()); + result.getStateDb().update(toAddress, receiverState.getEncoded()); + // todo: endowment rollbacked move it from here + + byte[] programCode = result.getChainDb().get(receiverState.getCodeHash()); + if (programCode != null && programCode.length != 0){ + + if (logger.isInfoEnabled()) + logger.info("calling for existing contract: address={}" , + Hex.toHexString(toAddress)); + + byte[] senderAddress = this.getOwnerAddress().getNoLeadZeroesData(); + byte[] senderStateB = this.result.getStateDb().get(senderAddress); + if (senderStateB == null){ + logger.info("This should not happen in any case, this inside contract run is is evidence for contract to exist: \n" + + "address={}", Hex.toHexString(senderAddress)); + return; + } + + AccountState senderState = new AccountState(senderStateB); + + // 2.1 PERFORM THE GAS VALUE TX + // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) + if (this.getGas().longValue() - gas.longValue() < 0 ){ + logger.info("No gas for the internal call, \n" + + "fromAddress={}, toAddress={}", + Hex.toHexString(senderAddress), Hex.toHexString(toAddress)); + + this.stackPushZero(); + return; + } + + + // 2.2 UPDATE THE NONCE + // (THIS STAGE IS NOT REVERTED BY ANY EXCEPTION) + senderState.incrementNonce(); + + TrackTrie stateDB = new TrackTrie( result.getStateDb() ); + TrackDatabase chainDB = new TrackDatabase( result.getChainDb() ); + TrackDatabase detailDB = new TrackDatabase( result.getDetailDB() ); + + detailDB.startTrack(); + chainDB.startTrack(); + stateDB.startTrack(); + + // todo: update the balance/value simple transfer + + Map storage = null; + if (details != null) + storage = details.getStorage(); + + ProgramInvoke programInvoke = + ProgramInvokeFactory.createProgramInvoke(this, toAddressDW, storage, endowmentValue, gas,receiverState.getBalance(), + data.array(), + detailDB, chainDB, stateDB); + + VM vm = new VM(); + Program program = new Program(programCode, programInvoke); + vm.play(program); + ProgramResult result = program.getResult(); + + if (result.getException() != null && + result.getException() instanceof Program.OutOfGasException){ + logger.info("contract run halted by OutOfGas: contract={}" , Hex.toHexString(toAddress)); + + detailDB.rollbackTrack(); + chainDB.rollbackTrack(); + stateDB.rollbackTrack(); + stackPushZero(); + return; + } + + // todo: apply results: result.gethReturn() + // todo: refund for remain gas + + detailDB.commitTrack(); + chainDB.commitTrack(); + stateDB.commitTrack(); + stackPush(new DataWord(1)); + + // the gas spent in any internal outcome + spendGas(result.getGasUsed()); + logger.info("The usage of the gas in external call updated", result.getGasUsed()); + + // update the storage , it could + // change by the call + byte[] contractDetailBytes = + result.getDetailDB().get(getOwnerAddress().getNoLeadZeroesData()); + if (contractDetailBytes != null){ + this.storage = new ContractDetails(contractDetailBytes).getStorage(); + } } } @@ -231,6 +365,11 @@ public class Program { DataWord keyWord = new DataWord(key); DataWord valWord = new DataWord(val); storage.put(keyWord, valWord); + + if (storage != null){ + ContractDetails contractDetails = new ContractDetails(storage); + result.getDetailDB().put(getOwnerAddress().getNoLeadZeroesData() , contractDetails.getEncoded()); + } } public DataWord getOwnerAddress(){ @@ -411,7 +550,7 @@ public class Program { if (listener != null){ listener.output(globalOutput.toString()); } - }; + } } public void addListener(ProgramListener listener){ diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvoke.java b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvoke.java index 5a08342c..0ee7faad 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvoke.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvoke.java @@ -1,5 +1,8 @@ package org.ethereum.vm; +import org.ethereum.db.TrackDatabase; +import org.ethereum.trie.TrackTrie; + import java.util.Map; /** @@ -31,4 +34,9 @@ public interface ProgramInvoke { public Map getStorage(); + public TrackDatabase getDetaildDB(); + public TrackDatabase getChainDb(); + public TrackTrie getStateDb(); + + } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeFactory.java b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeFactory.java index 91c6ac4c..d76841e3 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeFactory.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeFactory.java @@ -1,13 +1,17 @@ package org.ethereum.vm; -import org.abego.treelayout.internal.util.Contract; import org.ethereum.core.AccountState; import org.ethereum.core.Block; import org.ethereum.core.ContractDetails; import org.ethereum.core.Transaction; +import org.ethereum.db.TrackDatabase; import org.ethereum.manager.WorldManager; -import org.ethereum.util.ByteUtil; +import org.ethereum.trie.TrackTrie; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; +import java.math.BigInteger; import java.util.Map; /** @@ -19,9 +23,11 @@ import java.util.Map; public class ProgramInvokeFactory { + private static Logger logger = LoggerFactory.getLogger("VM"); // Invocation by the wire tx - public static ProgramInvoke createProgramInvoke(Transaction tx, Block lastBlock, ContractDetails details){ + public static ProgramInvoke createProgramInvoke(Transaction tx, Block lastBlock, ContractDetails details, + TrackDatabase detaildDB, TrackDatabase chainDb, TrackTrie stateDB){ // https://ethereum.etherpad.mozilla.org/26 @@ -38,7 +44,7 @@ public class ProgramInvokeFactory { byte[] caller = tx.getSender(); /*** BALANCE op ***/ - byte[] addressStateData = WorldManager.instance.worldState.get(address); + byte[] addressStateData = stateDB.get(address); byte[] balance = null; if (addressStateData.length == 0) @@ -86,10 +92,117 @@ public class ProgramInvokeFactory { if (details != null) storage = details.getStorage(); + detaildDB.startTrack(); + chainDb.startTrack(); + stateDB.startTrack(); + + if (logger.isInfoEnabled()){ + logger.info("Program invocation: \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), + Hex.toHexString(balance), + Hex.toHexString(gasPrice), + Hex.toHexString(gas), + Hex.toHexString(callValue), + 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, storage); + lastHash, coinbase, timestamp, number, difficulty, gaslimit, storage, + detaildDB, chainDb, stateDB); return programInvoke; } + + + /** + * This invocation created for contract call contract + */ + public static ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, + Map storageIn, + DataWord inValue, DataWord inGas, + BigInteger balanceInt, byte[] dataIn, + TrackDatabase detailDB, TrackDatabase chainDB, TrackTrie stateDB){ + + + 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(); + + Map storage = storageIn; + + if (logger.isInfoEnabled()){ + logger.info("Program invocation: \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.getData()), + Hex.toHexString(origin.getData()), + Hex.toHexString(caller.getData()), + Hex.toHexString(balance.getData()), + Hex.toHexString(gasPrice.getData()), + Hex.toHexString(gas.getData()), + Hex.toHexString(callValue.getData()), + Hex.toHexString(data), + Hex.toHexString(lastHash.getData()), + Hex.toHexString(coinbase.getData()), + Hex.toHexString(timestamp.getData()), + Hex.toHexString(number.getData()), + Hex.toHexString(difficulty.getData()), + Hex.toHexString(gasLimit.getData())); + } + + return new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue, + data, lastHash, coinbase, timestamp, number, difficulty, gasLimit, + storage, detailDB, chainDB, stateDB); + } + } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java index 394dd317..ab09ffbe 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramInvokeImpl.java @@ -1,5 +1,7 @@ package org.ethereum.vm; +import org.ethereum.db.TrackDatabase; +import org.ethereum.trie.TrackTrie; import org.ethereum.util.ByteUtil; import java.util.Map; @@ -10,7 +12,7 @@ import java.util.Map; * Created on: 03/06/2014 15:00 */ -public class ProgramInvokeImpl implements ProgramInvoke { +public class ProgramInvokeImpl implements ProgramInvoke { /*** TRANSACTION env ***/ DataWord address; @@ -33,10 +35,48 @@ public class ProgramInvokeImpl implements ProgramInvoke { Map storage; + TrackDatabase detaildDB; + TrackDatabase chainDb; + TrackTrie stateDb; + + public ProgramInvokeImpl(DataWord address, DataWord origin, DataWord caller, DataWord balance, + DataWord gasPrice, DataWord gas, DataWord callValue, byte[] msgData, + DataWord lastHash, DataWord coinbase, DataWord timestamp, DataWord number, DataWord difficulty, + DataWord gaslimit, Map storage, + TrackDatabase detaildDB, TrackDatabase chainDb, TrackTrie stateDB) { + + // Transaction env + this.address = address; + this.origin = origin; + this.caller = caller; + this.balance = balance; + this.gasPrice = gasPrice; + this.gas = gas; + this.callValue = callValue; + this.msgData = msgData; + + // last Block env + this.prevHash = lastHash; + this.coinbase = coinbase; + this.timestamp = timestamp; + this.number = number; + this.difficulty = difficulty; + this.gaslimit = gaslimit; + + this.storage = storage; + + this.detaildDB = detaildDB; + this.chainDb = chainDb; + this.stateDb = stateDB; + + } + + public ProgramInvokeImpl(byte[] address, byte[] origin, byte[] caller, byte[] balance, byte[] gasPrice, byte[] gas, byte[] callValue, byte[] msgData, byte[] lastHash, byte[] coinbase, long timestamp, long number, byte[] difficulty, - long gaslimit, Map storage) { + long gaslimit, Map storage, + TrackDatabase detaildDB, TrackDatabase chainDb, TrackTrie stateDB) { // Transaction env this.address = new DataWord(address); @@ -57,6 +97,10 @@ public class ProgramInvokeImpl implements ProgramInvoke { this.gaslimit = new DataWord(gaslimit); this.storage = storage; + + this.detaildDB = detaildDB; + this.chainDb = chainDb; + this.stateDb = stateDB; } /* ADDRESS op */ @@ -177,4 +221,17 @@ public class ProgramInvokeImpl implements ProgramInvoke { /* Storage */ public Map getStorage(){ return storage; } + + + public TrackDatabase getDetaildDB() { + return detaildDB; + } + + public TrackDatabase getChainDb() { + return chainDb; + } + + public TrackTrie getStateDb() { + return stateDb; + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java index 726a8ebb..06ed6ac1 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/ProgramResult.java @@ -1,7 +1,9 @@ package org.ethereum.vm; +import org.ethereum.db.TrackDatabase; +import org.ethereum.trie.TrackTrie; + import java.nio.ByteBuffer; -import java.util.HashMap; import java.util.Map; /** @@ -15,8 +17,10 @@ public class ProgramResult { private int gasUsed = 0; private ByteBuffer hReturn = null; private RuntimeException exception; - private Map storage; + TrackDatabase detailDB; + TrackDatabase chainDb; + TrackTrie stateDb; public void spendGas(int gas){ gasUsed += gas; @@ -44,11 +48,27 @@ public class ProgramResult { this.exception = exception; } - public Map getStorage() { - return storage; + public TrackDatabase getDetailDB() { + return detailDB; } - public void setStorage(Map storage) { - this.storage = storage; + public void setDetailDB(TrackDatabase detailDB) { + this.detailDB = detailDB; + } + + public TrackDatabase getChainDb() { + return chainDb; + } + + public void setChainDb(TrackDatabase chainDb) { + this.chainDb = chainDb; + } + + public TrackTrie getStateDb() { + return stateDb; + } + + public void setStateDb(TrackTrie stateDb) { + this.stateDb = stateDb; } } diff --git a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java index c56105ba..c7a2a292 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/VM.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/VM.java @@ -518,14 +518,18 @@ public class VM { program.step(); } break; case CALL:{ + DataWord gas = program.stackPop(); DataWord toAddress = program.stackPop(); DataWord value = program.stackPop(); - program.sendToAddress(toAddress.data, value); + DataWord inDataOffs = program.stackPop(); + DataWord inDataSize = program.stackPop(); - // todo: find out if we should or not execute - // todo: the contract for real + DataWord outDataOffs = program.stackPop(); + DataWord outDataSize = program.stackPop(); + + program.callToAddress(gas, toAddress, value, inDataOffs, inDataSize,outDataOffs, outDataSize); program.step(); } break; @@ -566,8 +570,6 @@ public class VM { this.step(program); } catch (RuntimeException e) { program.setRuntimeFailure(e); - } finally{ - program.getResult().setStorage(program.storage); } } } diff --git a/ethereumj-core/src/test/java/org/ethereum/core/BlockTest.java b/ethereumj-core/src/test/java/org/ethereum/core/BlockTest.java index b0863d90..d6c77ef4 100644 --- a/ethereumj-core/src/test/java/org/ethereum/core/BlockTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/core/BlockTest.java @@ -13,7 +13,7 @@ import static org.junit.Assert.*; public class BlockTest { // https://ethereum.etherpad.mozilla.org/12 - private String CPP_PoC5_GENESIS_HEX_RLP_ENCODED = "f8abf8a7a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a023b503734ff34ddb7bd5e478f1645680ec778ab3f90007cb1c854653693e5adc80834000008080830f4240808080a004994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829c0c0"; + private String CPP_PoC5_GENESIS_HEX_RLP_ENCODED = "f8abf8a7a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a011cc4aaa3b2f97cd6c858fcc0903b9b34b071e1798c91645f0e05e267028cb4a80834000008080830f4240808080a004994f67dc55b09e814ab7ffc8df3686b4afb2bb53e60eae97ef043fe03fb829c0c0"; private String CPP_PoC5_GENESIS_HEX_HASH = Hex.toHexString(StaticMessages.GENESIS_HASH); String block_1 = "f9072df8d3a077ef4fdaf389dca53236bcf7f72698e154eab2828f86fbc4fc6c" @@ -168,7 +168,7 @@ public class BlockTest { assertEquals(new BigInteger(1, Genesis.DIFFICULTY), difficulty); Block block1 = new Block(Hex.decode(block_1)); - diffBytes = block1.calcDifficulty(); + diffBytes = block1.calcDifficulty(); difficulty = new BigInteger(1, diffBytes); System.out.println("Block#1 difficulty = " + difficulty.toString()); assertEquals(new BigInteger(""), difficulty); diff --git a/ethereumj-core/src/test/java/org/ethereum/db/TrackDatabaseTest.java b/ethereumj-core/src/test/java/org/ethereum/db/TrackDatabaseTest.java index 951687af..ca4eebfb 100644 --- a/ethereumj-core/src/test/java/org/ethereum/db/TrackDatabaseTest.java +++ b/ethereumj-core/src/test/java/org/ethereum/db/TrackDatabaseTest.java @@ -17,7 +17,7 @@ public class TrackDatabaseTest { @Test public void test1(){ - Database db1 = new Database("temp"); + DatabaseImpl db1 = new DatabaseImpl("temp"); TrackDatabase trackDatabase1 = new TrackDatabase(db1); trackDatabase1.put(Hex.decode("abcdef"), Hex.decode("abcdef")); diff --git a/ethereumj-core/src/test/java/org/ethereum/vm/ProgramInvokeMockImpl.java b/ethereumj-core/src/test/java/org/ethereum/vm/ProgramInvokeMockImpl.java index d00cb780..636d09cf 100644 --- a/ethereumj-core/src/test/java/org/ethereum/vm/ProgramInvokeMockImpl.java +++ b/ethereumj-core/src/test/java/org/ethereum/vm/ProgramInvokeMockImpl.java @@ -2,6 +2,8 @@ package org.ethereum.vm; import org.ethereum.crypto.ECKey; import org.ethereum.crypto.HashUtil; +import org.ethereum.db.TrackDatabase; +import org.ethereum.trie.TrackTrie; import org.spongycastle.util.encoders.Hex; import java.util.Map; @@ -165,4 +167,19 @@ public class ProgramInvokeMockImpl implements ProgramInvoke { public Map getStorage() { return null; } + + @Override + public TrackDatabase getDetaildDB() { + return null; + } + + @Override + public TrackDatabase getChainDb() { + return null; + } + + @Override + public TrackTrie getStateDb() { + return null; + } }