Contract Call impl:

+ Contract storage local save
This commit is contained in:
romanman 2014-06-11 09:56:18 +01:00
parent 115d416e78
commit 11e9190957
15 changed files with 328 additions and 43 deletions

View File

@ -0,0 +1,106 @@
package org.ethereum.core;
import org.ethereum.util.RLP;
import org.ethereum.util.RLPElement;
import org.ethereum.util.RLPItem;
import org.ethereum.util.RLPList;
import org.ethereum.vm.DataWord;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* www.ethereumJ.com
*
* @author: Roman Mandeleil
* Created on: 09/06/2014 15:31
*/
public class ContractDetails {
private byte[] rlpEncoded;
List<DataWord> storageKeys;
List<DataWord> storageValues;
public ContractDetails(byte[] rlpEncoded) {
RLPList data = RLP.decode2(rlpEncoded);
RLPList rlpList = (RLPList)data.get(0);
RLPList keys = (RLPList)rlpList.get(0);
RLPList values = (RLPList)rlpList.get(1);
if (keys.size() > 0){
storageKeys = new ArrayList<>();
storageValues = new ArrayList<>();
}
for (int i = 0; i < keys.size(); ++i){
RLPItem rlpItem = (RLPItem)keys.get(i);
storageKeys.add(new DataWord(rlpItem.getRLPData()));
}
for (int i = 0; i < values.size(); ++i){
RLPItem rlpItem = (RLPItem)values.get(i);
storageValues.add(new DataWord(rlpItem.getRLPData()));
}
System.out.println();
}
public ContractDetails(Map<DataWord, DataWord> storage) {
storageKeys = new ArrayList<DataWord>();
storageValues = new ArrayList<DataWord>();
for(DataWord key : storage.keySet()){
DataWord value = storage.get(key);
storageKeys.add(key);
storageValues.add(value);
}
}
public byte[] getEncoded() {
if(rlpEncoded == null) {
byte[][] keys = new byte[storageKeys.size()][];
byte[][] values = new byte[storageValues.size()][];
int i = 0;
for (DataWord key : storageKeys){
keys[i] = RLP.encodeElement( key.getData());
++i;
}
i = 0;
for (DataWord value : storageValues){
values[i] = RLP.encodeElement( value.getData() );
++i;
}
byte[] rlpKeysList = RLP.encodeList(keys);
byte[] rlpValuesList = RLP.encodeList(values);
this.rlpEncoded = RLP.encodeList(rlpKeysList, rlpValuesList);
}
return rlpEncoded;
}
public Map<DataWord, DataWord> getStorage(){
Map<DataWord, DataWord> storage = new HashMap<>();
for (int i = 0; i < storageKeys.size(); ++i){
storage.put(storageKeys.get(i), storageValues.get(i));
}
return storage;
}
}

View File

@ -2,6 +2,7 @@ package org.ethereum.gui;
import org.ethereum.core.Account;
import org.ethereum.core.AccountState;
import org.ethereum.core.ContractDetails;
import org.ethereum.core.Transaction;
import org.ethereum.manager.MainData;
import org.ethereum.manager.WorldManager;
@ -215,10 +216,17 @@ class ContractCallDialog extends JDialog implements MessageAwareDialog{
return;
}
byte[] contractDetailsB =
WorldManager.instance.detaildDB.get(contractAddress);
ContractDetails contractDetails = null;
if (contractDetailsB.length > 0)
contractDetails = new ContractDetails(contractDetailsB);
Transaction tx = createTransaction();
if (tx == null) return;
ProgramPlayDialog.createAndShowGUI(programCode, tx, MainData.instance.getBlockchain().getLastBlock());
ProgramPlayDialog.createAndShowGUI(programCode, tx, MainData.instance.getBlockchain().getLastBlock(), contractDetails);
}
protected JRootPane createRootPane() {

View File

@ -108,7 +108,8 @@ class ContractSubmitDialog extends JDialog implements MessageAwareDialog {
}
contractAddrInput.setText(Hex.toHexString(tx.getContractAddress()));
ProgramPlayDialog.createAndShowGUI(tx.getData(), tx, MainData.instance.getBlockchain().getLastBlock());
ProgramPlayDialog.createAndShowGUI(tx.getData(), tx,
MainData.instance.getBlockchain().getLastBlock(), null);
}}
);

View File

@ -1,6 +1,7 @@
package org.ethereum.gui;
import org.ethereum.core.Block;
import org.ethereum.core.ContractDetails;
import org.ethereum.core.Transaction;
import org.ethereum.vm.Program;
import org.ethereum.vm.ProgramInvokeFactory;
@ -32,29 +33,19 @@ public class ProgramPlayDialog extends JPanel implements ActionListener,
private Transaction tx;
public ProgramPlayDialog(byte[] code, Transaction tx, Block lastBlock) {
public ProgramPlayDialog(byte[] code, Transaction tx, Block lastBlock, ContractDetails contractDetails) {
this.tx = tx;
outputList = new ArrayList<String>();
VM vm = new VM();
// Program program = new Program(Hex.decode("630000000060445960CC60DD611234600054615566602054630000000060445960CC60DD611234600054615566602054630000000060445960CC60DD611234600054615566602054"));
// Program program = new Program(Hex.decode("60016023576000605f556014600054601e60205463abcddcba6040545b51602001600a5254516040016014525451606001601e5254516080016028525460a052546016604860003960166000f26000603f556103e75660005460005360200235602054"), null);
// String code = "60016000546006601160003960066000f261778e600054";
// String code = "620f424073cd2a3d9f938e13cd947ec05abc7fe734df8dd826576086602660003960866000f26001602036040e0f630000002159600060200235600054600053565b525b54602052f263000000765833602054602053566040546000602002356060546001602002356080546080536040530a0f0f630000006c59608053604053036020535760805360605356016060535760015b525b54602052f263000000765860005b525b54602052f2";
// byte[] codeBytes =
// Hex.decode(code);
Program program = new Program(code ,
ProgramInvokeFactory.createProgramInvoke(tx, lastBlock));
ProgramInvokeFactory.createProgramInvoke(tx, lastBlock, contractDetails));
program.addListener(this);
program.fullTrace();
while(!program.isStopped())
vm.step(program);
vm.play(program);
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
@ -70,6 +61,9 @@ public class ProgramPlayDialog extends JPanel implements ActionListener,
stepSlider.setMajorTickSpacing(1);
if (outputList.size() > 40)
stepSlider.setMajorTickSpacing(3);
if (outputList.size() > 100)
stepSlider.setMajorTickSpacing(20);
stepSlider.setMinorTickSpacing(1);
stepSlider.setPaintTicks(true);
@ -129,9 +123,9 @@ public class ProgramPlayDialog extends JPanel implements ActionListener,
* this method should be invoked from the
* event-dispatching thread.
*/
public static void createAndShowGUI(byte[] runCode, Transaction tx, Block lastBlock) {
public static void createAndShowGUI(byte[] runCode, Transaction tx, Block lastBlock, ContractDetails details) {
ProgramPlayDialog ppd = new ProgramPlayDialog(runCode, tx, lastBlock);
ProgramPlayDialog ppd = new ProgramPlayDialog(runCode, tx, lastBlock, details);
//Create and set up the window.
JFrame frame = new JFrame("Program Draft Play");
@ -142,7 +136,6 @@ public class ProgramPlayDialog extends JPanel implements ActionListener,
Image img = kit.createImage(url);
frame.setIconImage(img);
frame.setPreferredSize(new Dimension(580, 500));
frame.setLocation(400, 200);

View File

@ -45,11 +45,12 @@ public class MainData {
// Initialize Wallet
byte[] cowAddr = HashUtil.sha3("cow".getBytes());
ECKey key = ECKey.fromPrivate(cowAddr);
wallet.importKey(cowAddr);
AccountState state = wallet.getAccountState(key.getAddress());
state.addToBalance(BigInteger.valueOf(2).pow(200));
wallet.importKey(HashUtil.sha3("cat".getBytes()));
// wallet.importKey(HashUtil.sha3("cat".getBytes()));
String secret = CONFIG.coinbaseSecret();
byte[] cbAddr = HashUtil.sha3(secret.getBytes());

View File

@ -2,6 +2,7 @@ package org.ethereum.manager;
import org.ethereum.core.AccountState;
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;
@ -36,8 +37,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 chainDB = new Database("blockchain");
public Database stateDB = new Database("state");
public Database detaildDB = new Database("details");
public Trie worldState = new Trie(stateDB.getDb());
@ -109,7 +111,7 @@ public class WorldManager {
MainData.instance.getBlockchain().getLastBlock();
ProgramInvoke programInvoke =
ProgramInvokeFactory.createProgramInvoke(tx, lastBlock);
ProgramInvokeFactory.createProgramInvoke(tx, lastBlock, null);
if (logger.isInfoEnabled())
logger.info("running the init for contract: addres={}" ,
@ -128,22 +130,30 @@ public class WorldManager {
Hex.toHexString( tx.getSender() ), Hex.toHexString(tx.getContractAddress()), gasDebit);
worldState.update(senderAddress, senderState.getEncoded());
VM vm = new VM();
Program program = new Program(initCode, programInvoke);
vm.play(program);
ProgramResult result = program.getResult();
// TODO: (!!!!!) ALL THE CHECKS FOR THE PROGRAM RESULT
// TODO: (!!!!!) consider introduce one method for applying results
if (result.getException() != null &&
result.getException() instanceof Program.OutOfGasException){
// The out of gas means nothing applied
}
// Save the code created by init
byte[] bodyCode = null;
if (result.gethReturn() != null){
bodyCode = result.gethReturn().array();
}
// TODO: what if the body code is null , still submit ?
// TODO: (!!!!!) ALL THE CHECKS FOR THE PROGRAM RESULT
BigInteger gasPrice = BigInteger.valueOf( MainData.instance.getBlockchain().getGasPrice());
BigInteger gasPrice =
BigInteger.valueOf( MainData.instance.getBlockchain().getGasPrice());
BigInteger refund =
gasDebit.subtract(BigInteger.valueOf( result.getGasUsed()).multiply(gasPrice));
@ -168,6 +178,16 @@ 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(tx.getContractAddress() , contractDetails.getEncoded());
}
} else {
if (receiverState.getCodeHash() != HashUtil.EMPTY_DATA_HASH){
@ -178,12 +198,18 @@ public class WorldManager {
Block lastBlock =
MainData.instance.getBlockchain().getLastBlock();
ProgramInvoke programInvoke =
ProgramInvokeFactory.createProgramInvoke(tx, lastBlock);
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);
VM vm = new VM();
Program program = new Program(programCode, programInvoke);
vm.play(program);
@ -197,6 +223,7 @@ public class WorldManager {
}
}
pendingTransactions.put(Hex.toHexString(tx.getHash()), tx);
}

View File

@ -1,5 +1,6 @@
package org.ethereum.vm;
import org.ethereum.core.ContractDetails;
import org.ethereum.util.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -44,6 +45,10 @@ public class Program {
this.invokeData = invokeData;
this.ops = ops;
if (invokeData.getStorage() != null){
storage = invokeData.getStorage();
}
}
public byte getCurrentOp(){
@ -415,4 +420,9 @@ public class Program {
public interface ProgramListener{
public void output(String out);
}
public class OutOfGasException extends RuntimeException{
}
}

View File

@ -1,5 +1,7 @@
package org.ethereum.vm;
import java.util.Map;
/**
* www.ethereumJ.com
* @author: Roman Mandeleil
@ -27,4 +29,6 @@ public interface ProgramInvoke {
public DataWord getDifficulty();
public DataWord getGaslimit();
public Map<DataWord, DataWord> getStorage();
}

View File

@ -1,11 +1,15 @@
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.manager.WorldManager;
import org.ethereum.util.ByteUtil;
import java.util.Map;
/**
* www.ethereumJ.com
*
@ -15,15 +19,9 @@ import org.ethereum.util.ByteUtil;
public class ProgramInvokeFactory {
// Invocation by the other program
public static ProgramInvoke createProgramInvoke(Program program){
return null;
}
// Invocation by the wire tx
public static ProgramInvoke createProgramInvoke(Transaction tx, Block lastBlock){
public static ProgramInvoke createProgramInvoke(Transaction tx, Block lastBlock, ContractDetails details){
// https://ethereum.etherpad.mozilla.org/26
@ -83,10 +81,14 @@ public class ProgramInvokeFactory {
/*** GASLIMIT op ***/
long gaslimit = lastBlock.getGasLimit();
/*** Map of storage values ***/
Map<DataWord, DataWord> storage = null;
if (details != null)
storage = details.getStorage();
ProgramInvoke programInvoke =
new ProgramInvokeImpl(address, origin, caller, balance, gasPrice, gas, callValue, data,
lastHash, coinbase, timestamp, number, difficulty, gaslimit);
lastHash, coinbase, timestamp, number, difficulty, gaslimit, storage);
return programInvoke;
}

View File

@ -2,6 +2,8 @@ package org.ethereum.vm;
import org.ethereum.util.ByteUtil;
import java.util.Map;
/**
* www.ethereumJ.com
* @author: Roman Mandeleil
@ -29,12 +31,12 @@ public class ProgramInvokeImpl implements ProgramInvoke {
DataWord difficulty;
DataWord gaslimit;
Map<DataWord, DataWord> storage;
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) {
long gaslimit, Map<DataWord, DataWord> storage) {
// Transaction env
this.address = new DataWord(address);
@ -54,6 +56,7 @@ public class ProgramInvokeImpl implements ProgramInvoke {
this.difficulty = new DataWord(difficulty);
this.gaslimit = new DataWord(gaslimit);
this.storage = storage;
}
/* ADDRESS op */
@ -171,4 +174,7 @@ public class ProgramInvokeImpl implements ProgramInvoke {
public DataWord getGaslimit() {
return gaslimit;
}
/* Storage */
public Map<DataWord, DataWord> getStorage(){ return storage; }
}

View File

@ -1,6 +1,8 @@
package org.ethereum.vm;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Map;
/**
* www.ethereumJ.com
@ -13,6 +15,8 @@ public class ProgramResult {
private int gasUsed = 0;
private ByteBuffer hReturn = null;
private RuntimeException exception;
private Map<DataWord, DataWord> storage;
public void spendGas(int gas){
gasUsed += gas;
@ -40,5 +44,11 @@ public class ProgramResult {
this.exception = exception;
}
public Map<DataWord, DataWord> getStorage() {
return storage;
}
public void setStorage(Map<DataWord, DataWord> storage) {
this.storage = storage;
}
}

View File

@ -567,8 +567,7 @@ public class VM {
} catch (RuntimeException e) {
program.setRuntimeFailure(e);
} finally{
// todo: Here wrap the storage into result;
program.getResult().setStorage(program.storage);
}
}
}

View File

@ -0,0 +1,90 @@
package org.ethereum.core;
import org.ethereum.vm.DataWord;
import org.junit.Assert;
import org.junit.Test;
import org.spongycastle.util.encoders.Hex;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* www.ethereumJ.com
*
* @author: Roman Mandeleil
* Created on: 09/06/2014 15:41
*/
public class ContractDetailsTest {
@Test /* encode 2 keys/values */
public void test1(){
String expected = "f888f842a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000001f842a00000000000000000000000000000000000000000000000000000000000009dd4a0000000000000000000000000000000000000000000000000000000000000765f";
DataWord key1 = new DataWord(1);
DataWord value1 = new DataWord(30303);
DataWord key2 = new DataWord(2);
DataWord value2 = new DataWord(40404);
HashMap<DataWord, DataWord> storage = new HashMap<>();
storage.put(key1, value1);
storage.put(key2, value2);
ContractDetails contractDetails = new ContractDetails(storage);
String encoded = Hex.toHexString(contractDetails.getEncoded());
Assert.assertEquals(expected, encoded);
}
@Test /* encode 3 keys/values */
public void test2(){
String expected = "f8caf863a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000003f863a00000000000000000000000000000000000000000000000000000000000009dd4a0000000000000000000000000000000000000000000000000000000000000765fa0000000000000000000000000000000000000000000000000000000000000ffff";
DataWord key1 = new DataWord(1);
DataWord value1 = new DataWord(30303);
DataWord key2 = new DataWord(2);
DataWord value2 = new DataWord(40404);
DataWord key3 = new DataWord(3);
DataWord value3 = new DataWord(0xFFFF);
HashMap<DataWord, DataWord> storage = new HashMap<>();
storage.put(key1, value1);
storage.put(key2, value2);
storage.put(key3, value3);
ContractDetails contractDetails = new ContractDetails(storage);
String encoded = Hex.toHexString(contractDetails.getEncoded());
Assert.assertEquals(expected, encoded);
}
@Test /* decode 3 keys/values */
public void test3(){
String rlpData = "f8caf863a00000000000000000000000000000000000000000000000000000000000000002a00000000000000000000000000000000000000000000000000000000000000001a00000000000000000000000000000000000000000000000000000000000000003f863a00000000000000000000000000000000000000000000000000000000000009dd4a0000000000000000000000000000000000000000000000000000000000000765fa0000000000000000000000000000000000000000000000000000000000000ffff";
ContractDetails contractDetails = new ContractDetails(Hex.decode(rlpData));
String expKey3String = "0000000000000000000000000000000000000000000000000000000000000003";
String expVal3String = "000000000000000000000000000000000000000000000000000000000000ffff";
DataWord key3 = contractDetails.storageKeys.get(2);
DataWord value3 = contractDetails.storageValues.get(2);
String key3String = Hex.toHexString(key3.getData());
String value3String = Hex.toHexString(value3.getData());
Assert.assertEquals(expKey3String, key3String);
Assert.assertEquals(expVal3String, value3String);
}
}

View File

@ -45,4 +45,25 @@ public class MachineCompileTest {
Assert.assertEquals(expected, result);
}
@Test // contract for if jump
public void test3(){
String code = "a=2\n" +
"if a>0:\n" +
" b = 3\n" +
"else: \n" +
" c = 4";
// String expected = "610100600e6000396101006000f260026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005460026000546002600054600260005469";
String asm = SerpentCompiler.compile(code);
byte[] machineCode = SerpentCompiler.compileAssemblyToMachine(asm);
byte[] vmReadyCode = SerpentCompiler.encodeMachineCodeForVMRun(machineCode, null);
System.out.println(asm);
System.out.println(GUIUtils.getHexStyledText(vmReadyCode));
String result = Hex.toHexString(vmReadyCode);
// Assert.assertEquals(expected, result);
}
}

View File

@ -4,6 +4,8 @@ import org.ethereum.crypto.ECKey;
import org.ethereum.crypto.HashUtil;
import org.spongycastle.util.encoders.Hex;
import java.util.Map;
/**
* www.ethereumJ.com
* @author: Roman Mandeleil
@ -158,4 +160,9 @@ public class ProgramInvokeMockImpl implements ProgramInvoke {
long gasLimit = 968269;
return new DataWord(gasLimit);
}
@Override
public Map<DataWord, DataWord> getStorage() {
return null;
}
}