CALL: first draft

+ recursive contract tested
+ tracking changes for trie introduced
+ genesis updated to protocol 20
This commit is contained in:
romanman 2014-06-12 16:18:01 +01:00
parent f917bf8e97
commit 8cad126c0c
24 changed files with 802 additions and 243 deletions

View File

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

View File

@ -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<DataWord, DataWord> storage) {
@ -97,7 +95,9 @@ public class ContractDetails {
Map<DataWord, DataWord> 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));
}

View File

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

View File

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

View File

@ -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);
}
}
}
public void put(byte[] key, byte[] value);
public byte[] get(byte[] key);
public void delete(byte[] key);
}

View File

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

View File

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

View File

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

View File

@ -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<String, Transaction> pendingTransactions =
Collections.synchronizedMap(new HashMap<String, Transaction>());
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<DataWord, DataWord> storage = result.getStorage();
if (storage != null){
ContractDetails contractDetails = new ContractDetails(storage);
detaildDB.put(contractAddress , contractDetails.getEncoded());
}
}
public void applyTransactionList(List<Transaction> txList) {

View File

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

View File

@ -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<ByteArrayWrapper, byte[]> changes;
private Map<ByteArrayWrapper, byte[]> 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);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<byte[], DataWord> 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<DataWord, DataWord> 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){

View File

@ -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<DataWord, DataWord> getStorage();
public TrackDatabase getDetaildDB();
public TrackDatabase getChainDb();
public TrackTrie getStateDb();
}

View File

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

View File

@ -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<DataWord, DataWord> 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<DataWord, DataWord> 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<DataWord, DataWord> storage) {
long gaslimit, Map<DataWord, DataWord> 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<DataWord, DataWord> getStorage(){ return storage; }
public TrackDatabase getDetaildDB() {
return detaildDB;
}
public TrackDatabase getChainDb() {
return chainDb;
}
public TrackTrie getStateDb() {
return stateDb;
}
}

View File

@ -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<DataWord, DataWord> 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<DataWord, DataWord> getStorage() {
return storage;
public TrackDatabase getDetailDB() {
return detailDB;
}
public void setStorage(Map<DataWord, DataWord> 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;
}
}

View File

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

View File

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

View File

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

View File

@ -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<DataWord, DataWord> getStorage() {
return null;
}
@Override
public TrackDatabase getDetaildDB() {
return null;
}
@Override
public TrackDatabase getChainDb() {
return null;
}
@Override
public TrackTrie getStateDb() {
return null;
}
}