introducing structural tracing for VM
This commit is contained in:
parent
e0aa458aa8
commit
9d9c84f862
|
@ -153,6 +153,12 @@
|
|||
<version>1.1.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
<version>1.9.13</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
|
|
|
@ -41,6 +41,8 @@ public class SystemProperties {
|
|||
private static int DEFAULT_MAX_BLOCKS_QUEUED = 300;
|
||||
private static String DEFAULT_PROJECT_VERSION = "";
|
||||
private static String DEFAULT_HELLO_PHRASE = "Dev";
|
||||
private static Boolean DEFAULT_VM_TRACE = false;
|
||||
private static String DEFAULT_VM_TRACE_DIR = "dmp";
|
||||
|
||||
private static List<String> DEFAULT_PROTOCOL_LIST = Arrays.asList("eth", "shh");
|
||||
|
||||
|
@ -221,6 +223,16 @@ public class SystemProperties {
|
|||
return Arrays.asList(capabilitiesList.split(","));
|
||||
}
|
||||
|
||||
public boolean vmTrace(){
|
||||
if (prop.isEmpty()) return DEFAULT_VM_TRACE;
|
||||
return Boolean.parseBoolean(prop.getProperty("vm.structured.trace"));
|
||||
}
|
||||
|
||||
public String vmTraceDir() {
|
||||
if (prop.isEmpty()) return DEFAULT_VM_TRACE_DIR;
|
||||
return prop.getProperty("vm.structured.dir");
|
||||
}
|
||||
|
||||
public void print() {
|
||||
Enumeration<?> e = prop.propertyNames();
|
||||
while (e.hasMoreElements()) {
|
||||
|
|
|
@ -369,6 +369,8 @@ public class BlockchainImpl implements Blockchain {
|
|||
|
||||
if (CONFIG.playVM())
|
||||
vm.play(program);
|
||||
|
||||
program.saveProgramTraceToFile(Hex.toHexString(tx.getHash()));
|
||||
ProgramResult result = program.getResult();
|
||||
applyProgramResult(result, gasDebit, gasPrice, trackRepository,
|
||||
senderAddress, receiverAddress, coinbase, isContractCreation);
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.ethereum.db.RepositoryImpl;
|
|||
import org.ethereum.facade.Repository;
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.vm.*;
|
||||
import org.ethereum.vmtrace.ProgramTrace;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
@ -23,6 +24,7 @@ import java.util.*;
|
|||
public class TestRunner {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger("JSONTest");
|
||||
private ProgramTrace trace = null;
|
||||
|
||||
public List<String> runTestSuite(TestSuite testSuite) {
|
||||
|
||||
|
@ -97,14 +99,11 @@ public class TestRunner {
|
|||
/* 4. run VM */
|
||||
VM vm = new VM();
|
||||
Program program = new Program(exec.getCode(), programInvoke);
|
||||
|
||||
try {
|
||||
System.out.println("-------- EXEC --------");
|
||||
while(!program.isStopped())
|
||||
vm.step(program);
|
||||
} catch (RuntimeException e) {
|
||||
program.setRuntimeFailure(e);
|
||||
}
|
||||
vm.play(program);
|
||||
|
||||
program.saveProgramTraceToFile(testCase.getName());
|
||||
|
||||
this.trace = program.getProgramTrace();
|
||||
|
||||
System.out.println("--------- POST --------");
|
||||
/* 5. Assert Post values */
|
||||
|
@ -184,14 +183,15 @@ public class TestRunner {
|
|||
|
||||
Map<DataWord, DataWord> testStorage = contractDetails.getStorage();
|
||||
DataWord actualValue = testStorage.get(new DataWord(storageKey.getData()));
|
||||
|
||||
if (!Arrays.equals(expectedStValue, actualValue.getNoLeadZeroesData())) {
|
||||
|
||||
if (actualValue == null ||
|
||||
!Arrays.equals(expectedStValue, actualValue.getNoLeadZeroesData())) {
|
||||
|
||||
String output =
|
||||
String.format("Storage value different: key [ %s ], expectedValue: [ %s ], actualValue: [ %s ]",
|
||||
Hex.toHexString(storageKey.getData()),
|
||||
Hex.toHexString(expectedStValue),
|
||||
Hex.toHexString(actualValue.getNoLeadZeroesData()));
|
||||
actualValue == null ? "" : Hex.toHexString(actualValue.getNoLeadZeroesData()));
|
||||
logger.info(output);
|
||||
results.add(output);
|
||||
}
|
||||
|
@ -315,4 +315,8 @@ public class TestRunner {
|
|||
repository.close();
|
||||
}
|
||||
}
|
||||
|
||||
public ProgramTrace getTrace() {
|
||||
return trace;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,28 @@
|
|||
package org.ethereum.vm;
|
||||
|
||||
import org.codehaus.jackson.JsonNode;
|
||||
import org.codehaus.jackson.map.ObjectMapper;
|
||||
import org.codehaus.jackson.map.SerializationConfig;
|
||||
import org.ethereum.crypto.HashUtil;
|
||||
import org.ethereum.db.ContractDetails;
|
||||
import org.ethereum.facade.Repository;
|
||||
import org.ethereum.util.ByteUtil;
|
||||
import org.ethereum.vm.MessageCall.MsgType;
|
||||
import org.ethereum.vmtrace.Op;
|
||||
import org.ethereum.vmtrace.ProgramTrace;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
import java.util.*;
|
||||
|
||||
import static org.ethereum.config.SystemProperties.CONFIG;
|
||||
|
||||
/**
|
||||
* www.ethereumJ.com
|
||||
|
@ -35,6 +42,7 @@ public class Program {
|
|||
DataWord programAddress;
|
||||
|
||||
ProgramResult result = new ProgramResult();
|
||||
ProgramTrace programTrace = new ProgramTrace();
|
||||
|
||||
byte[] ops;
|
||||
int pc = 0;
|
||||
|
@ -414,6 +422,7 @@ public class Program {
|
|||
Program program = new Program(programCode, programInvoke);
|
||||
vm.play(program);
|
||||
result = program.getResult();
|
||||
this.getProgramTrace().merge(program.getProgramTrace());
|
||||
this.result.addDeleteAccounts(result.getDeleteAccounts());
|
||||
}
|
||||
|
||||
|
@ -704,8 +713,67 @@ public class Program {
|
|||
listener.output(globalOutput.toString());
|
||||
}
|
||||
}
|
||||
|
||||
public static String stringify(byte[] code, int index, String result) {
|
||||
|
||||
public void saveOpTrace(){
|
||||
|
||||
Op op = new Op();
|
||||
op.setPc(pc);
|
||||
|
||||
op.setOp(ops[pc]);
|
||||
op.saveGas(getGas());
|
||||
|
||||
ContractDetails contractDetails = this.result.getRepository().
|
||||
getContractDetails(this.programAddress.getLast20Bytes());
|
||||
op.saveStorageMap(contractDetails.getStorage());
|
||||
op.saveMemory(memory);
|
||||
op.saveStack(stack);
|
||||
|
||||
programTrace.addOp(op);
|
||||
}
|
||||
|
||||
public void saveProgramTraceToFile(String fileName){
|
||||
|
||||
if (!CONFIG.vmTrace()) return;
|
||||
|
||||
String dir = CONFIG.vmTraceDir() + "/";
|
||||
|
||||
File dumpFile = new File(System.getProperty("user.dir") + "/" + dir + fileName + ".json");
|
||||
FileWriter fw = null;
|
||||
BufferedWriter bw = null;
|
||||
|
||||
try {
|
||||
|
||||
dumpFile.getParentFile().mkdirs();
|
||||
dumpFile.createNewFile();
|
||||
|
||||
fw = new FileWriter(dumpFile.getAbsoluteFile());
|
||||
bw = new BufferedWriter(fw);
|
||||
|
||||
ObjectMapper objectMapper = new ObjectMapper();
|
||||
objectMapper.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
|
||||
|
||||
String originalJson = programTrace.getJsonString();
|
||||
JsonNode tree = objectMapper .readTree(originalJson);
|
||||
String formattedJson = objectMapper.writeValueAsString(tree);
|
||||
bw.write(formattedJson);
|
||||
|
||||
} catch (IOException e) {
|
||||
logger.error(e.getMessage(), e);
|
||||
} finally {
|
||||
try {
|
||||
if (bw != null) bw.close();
|
||||
if (fw != null) fw.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public ProgramTrace getProgramTrace() {
|
||||
return programTrace;
|
||||
}
|
||||
|
||||
public static String stringify(byte[] code, int index, String result) {
|
||||
if(code == null || code.length == 0)
|
||||
return result;
|
||||
|
||||
|
@ -721,7 +789,7 @@ public class Program {
|
|||
|
||||
int nPush = op.val() - OpCode.PUSH1.val() + 1;
|
||||
byte[] data = Arrays.copyOfRange(code, index+1, index + nPush + 1);
|
||||
result += new BigInteger(data).toString() + ' ';
|
||||
result += new BigInteger(1, data).toString() + ' ';
|
||||
|
||||
continuedCode = Arrays.copyOfRange(code, index + nPush + 1, code.length);
|
||||
break;
|
||||
|
|
|
@ -75,6 +75,9 @@ public class VM {
|
|||
public void step(Program program) {
|
||||
|
||||
program.fullTrace();
|
||||
|
||||
if (CONFIG.vmTrace())
|
||||
program.saveOpTrace();
|
||||
|
||||
try {
|
||||
OpCode op = OpCode.code(program.getCurrentOp());
|
||||
|
@ -917,6 +920,8 @@ public class VM {
|
|||
logger.warn("VM halted", e.getMessage());
|
||||
program.stop();
|
||||
throw e;
|
||||
} finally {
|
||||
program.fullTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -933,6 +938,7 @@ public class VM {
|
|||
|
||||
while(!program.isStopped())
|
||||
this.step(program);
|
||||
|
||||
} catch (RuntimeException e) {
|
||||
program.setRuntimeFailure(e);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
package org.ethereum.vmtrace;
|
||||
|
||||
import org.ethereum.vm.DataWord;
|
||||
import org.ethereum.vm.OpCode;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
import org.spongycastle.util.encoders.Hex;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Data to for one program step to save.
|
||||
*
|
||||
* {
|
||||
* 'op': 'CODECOPY'
|
||||
* 'storage': {},
|
||||
* 'gas': '99376',
|
||||
* 'pc': '9',
|
||||
* 'memory': '',
|
||||
* 'stack': ['15', '15', '14', '0'],
|
||||
* }
|
||||
*
|
||||
* www.etherj.com
|
||||
*
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 28/10/2014 23:39
|
||||
*/
|
||||
|
||||
public class Op {
|
||||
|
||||
byte op;
|
||||
int pc;
|
||||
DataWord gas;
|
||||
Map<String, String> storage;
|
||||
byte[] memory;
|
||||
List<String> stack;
|
||||
|
||||
public void setOp(byte op) {
|
||||
this.op = op;
|
||||
}
|
||||
|
||||
public void setPc(int pc) {
|
||||
this.pc = pc;
|
||||
}
|
||||
|
||||
public void saveGas(DataWord gas) {
|
||||
this.gas = gas;
|
||||
}
|
||||
|
||||
public void saveStorageMap(Map<DataWord, DataWord> storage) {
|
||||
|
||||
this.storage = new HashMap<>();
|
||||
List<DataWord> keys = new ArrayList<>(storage.keySet());
|
||||
Collections.sort(keys);
|
||||
for (DataWord key : keys) {
|
||||
DataWord value = storage.get(key);
|
||||
this.storage.put(Hex.toHexString(key.getNoLeadZeroesData()),
|
||||
Hex.toHexString(value.getNoLeadZeroesData()));
|
||||
}
|
||||
}
|
||||
|
||||
public void saveMemory(ByteBuffer memory){
|
||||
if (memory != null)
|
||||
this.memory = Arrays.copyOf(memory.array(), memory.array().length);
|
||||
}
|
||||
|
||||
public void saveStack(Stack<DataWord> stack) {
|
||||
|
||||
this.stack = new ArrayList<>();
|
||||
|
||||
for(DataWord element : stack){
|
||||
this.stack.add(0, Hex.toHexString(element.getNoLeadZeroesData()));
|
||||
}
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
|
||||
Map<Object, Object> jsonData = new LinkedHashMap<>();
|
||||
|
||||
jsonData.put("op", OpCode.code(op).name());
|
||||
jsonData.put("pc", Long.toString(pc));
|
||||
jsonData.put("gas", gas.value().toString());
|
||||
jsonData.put("stack", stack);
|
||||
jsonData.put("memory", memory == null ? "" : Hex.toHexString(memory));
|
||||
jsonData.put("storage", new JSONObject(storage) );
|
||||
|
||||
return JSONValue.toJSONString(jsonData);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
package org.ethereum.vmtrace;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* www.etherj.com
|
||||
*
|
||||
* @author: Roman Mandeleil
|
||||
* Created on: 28/10/2014 23:47
|
||||
*/
|
||||
|
||||
public class ProgramTrace {
|
||||
|
||||
byte[] txHash;
|
||||
List<Op> ops = new ArrayList<>();
|
||||
|
||||
public void setTxHash(byte[] txHash) {
|
||||
this.txHash = txHash;
|
||||
}
|
||||
|
||||
public void addOp(Op op){
|
||||
ops.add(op);
|
||||
}
|
||||
|
||||
/**
|
||||
* used for merging sub calls execution
|
||||
*
|
||||
* @param programTrace
|
||||
*/
|
||||
public void merge(ProgramTrace programTrace){
|
||||
|
||||
this.ops.addAll( programTrace.ops );
|
||||
}
|
||||
|
||||
public String getJsonString(){
|
||||
return JSONArray.toJSONString(ops);
|
||||
}
|
||||
}
|
|
@ -100,6 +100,17 @@ dump.style = pretty
|
|||
# clean the dump dir each start
|
||||
dump.clean.on.restart = true
|
||||
|
||||
# structured trace
|
||||
# is the trace being
|
||||
# collected in the
|
||||
# form of objects and
|
||||
# exposed to the user
|
||||
# in json or any other
|
||||
# convenient form.
|
||||
vm.structured.trace = true
|
||||
vm.structured.dir = vmtrace
|
||||
|
||||
|
||||
# make changes to tracing options
|
||||
# starting from certain block
|
||||
# -1 don't make any tracing changes
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
<dependency>
|
||||
<groupId>org.ethereum</groupId>
|
||||
<artifactId>ethereumj</artifactId>
|
||||
<version>0.7.2.20141029.1501</version>
|
||||
<version>0.7.2.20141030.1409</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue