introducing structural tracing for VM

This commit is contained in:
romanman 2014-10-31 10:18:40 -05:00
parent e0aa458aa8
commit 9d9c84f862
10 changed files with 267 additions and 20 deletions

View File

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

View File

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

View File

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

View File

@ -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);
vm.play(program);
try {
System.out.println("-------- EXEC --------");
while(!program.isStopped())
vm.step(program);
} catch (RuntimeException e) {
program.setRuntimeFailure(e);
}
program.saveProgramTraceToFile(testCase.getName());
this.trace = program.getProgramTrace();
System.out.println("--------- POST --------");
/* 5. Assert Post values */
@ -185,13 +184,14 @@ 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;
}
}

View File

@ -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());
}
@ -705,6 +714,65 @@ public class Program {
}
}
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;

View File

@ -76,6 +76,9 @@ public class VM {
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);
}

View File

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

View File

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

View File

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

View File

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