From a23617afda0af71064a65f643476081ac3c5ab64 Mon Sep 17 00:00:00 2001 From: eugene-shevchenko Date: Thu, 9 Apr 2015 22:42:52 +0300 Subject: [PATCH] VM trace JSON serialization bugfix. --- .../ethereum/core/TransactionExecutor.java | 16 ++-- .../main/java/org/ethereum/vm/Program.java | 31 +------- .../main/java/org/ethereum/vmtrace/Op.java | 39 +++------- .../org/ethereum/vmtrace/ProgramTrace.java | 25 +------ .../org/ethereum/vmtrace/Serializers.java | 73 +++++++++++++++++++ 5 files changed, 101 insertions(+), 83 deletions(-) create mode 100644 ethereumj-core/src/main/java/org/ethereum/vmtrace/Serializers.java diff --git a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java index d4c50e6f..392fa54b 100644 --- a/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java +++ b/ethereumj-core/src/main/java/org/ethereum/core/TransactionExecutor.java @@ -240,14 +240,16 @@ public class TransactionExecutor { this.receipt = receipt; return; } finally { - String traceAsJson = "{}"; - if (program != null) { - ProgramTrace trace = program.getProgramTrace(); - trace.setResult(result.getHReturn()); - trace.setError(result.getException()); - traceAsJson = trace.getJsonString(); + if (CONFIG.vmTrace()) { + String traceAsJson = "{}"; + if (program != null) { + ProgramTrace trace = program.getProgramTrace(); + trace.setResult(result.getHReturn()); + trace.setError(result.getException()); + traceAsJson = trace.asJsonString(); + } + listener.onVMTraceCreated(txHash, traceAsJson); } - listener.onVMTraceCreated(txHash, traceAsJson); } trackTx.commit(); } else { 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 c954de82..8f13897d 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vm/Program.java +++ b/ethereumj-core/src/main/java/org/ethereum/vm/Program.java @@ -8,35 +8,21 @@ import org.ethereum.vm.MessageCall.MsgType; import org.ethereum.vm.PrecompiledContracts.PrecompiledContract; import org.ethereum.vmtrace.Op; import org.ethereum.vmtrace.ProgramTrace; - -import org.codehaus.jackson.JsonNode; -import org.codehaus.jackson.map.ObjectMapper; -import org.codehaus.jackson.map.SerializationConfig; - 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.HashSet; -import java.util.List; -import java.util.Set; -import java.util.Stack; +import java.util.*; import static org.ethereum.config.SystemProperties.CONFIG; import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; +import static org.springframework.util.StringUtils.isEmpty; /** * @author Roman Mandeleil @@ -840,7 +826,7 @@ public class Program { public void saveProgramTraceToFile(String fileName) { - if (!CONFIG.vmTrace()) return; + if (!CONFIG.vmTrace() || isEmpty(CONFIG.vmTraceDir())) return; String dir = CONFIG.databaseDir() + "/" + CONFIG.vmTraceDir() + "/"; @@ -849,21 +835,12 @@ public class Program { 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); - + bw.write(programTrace.asJsonString()); } catch (IOException e) { logger.error(e.getMessage(), e); } finally { diff --git a/ethereumj-core/src/main/java/org/ethereum/vmtrace/Op.java b/ethereumj-core/src/main/java/org/ethereum/vmtrace/Op.java index 94725740..cd2840fa 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vmtrace/Op.java +++ b/ethereumj-core/src/main/java/org/ethereum/vmtrace/Op.java @@ -1,23 +1,13 @@ package org.ethereum.vmtrace; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; 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.*; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Stack; +import static org.ethereum.vmtrace.Serializers.*; /** * Data to for one program step to save. @@ -35,16 +25,18 @@ import java.util.Stack; * @author Roman Mandeleil * @since 28.10.2014 */ - public class Op { + @JsonSerialize(using = OpCodeSerializer.class) private byte op; private int deep; private int pc; + @JsonSerialize(using = DataWordSerializer.class) private DataWord gas; - private Map storage; + @JsonSerialize(using = ByteArraySerializer.class) private byte[] memory; private List stack; + private Map storage; public void setOp(byte op) { this.op = op; @@ -89,19 +81,10 @@ public class Op { } public String toString() { - - Map jsonData = new LinkedHashMap<>(); - - jsonData.put("op", OpCode.code(op).name()); - jsonData.put("deep", Long.toString(deep)); - 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); + return asJsonString(); } - + private String asJsonString() { + return serializeFieldsOnly(this, false); + } } diff --git a/ethereumj-core/src/main/java/org/ethereum/vmtrace/ProgramTrace.java b/ethereumj-core/src/main/java/org/ethereum/vmtrace/ProgramTrace.java index 2ee9eb98..d530ffad 100644 --- a/ethereumj-core/src/main/java/org/ethereum/vmtrace/ProgramTrace.java +++ b/ethereumj-core/src/main/java/org/ethereum/vmtrace/ProgramTrace.java @@ -1,22 +1,14 @@ package org.ethereum.vmtrace; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import org.json.simple.JSONArray; -import org.json.simple.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.spongycastle.util.encoders.Hex; import java.nio.ByteBuffer; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import static java.lang.String.format; +import static org.ethereum.vmtrace.Serializers.serializeFieldsOnly; /** * @author Roman Mandeleil @@ -24,8 +16,6 @@ import static java.lang.String.format; */ public class ProgramTrace { - private static final Logger LOGGER = LoggerFactory.getLogger("vmtrace"); - @JsonIgnore private byte[] txHash; private List ops = new ArrayList<>(); @@ -41,7 +31,7 @@ public class ProgramTrace { } public void setError(Exception error) { - this.error = (error == null) ? "" : format("%s: %s", error.getClass(), error.getMessage());; + this.error = (error == null) ? "" : format("%s: %s", error.getClass(), error.getMessage()); } public void addOp(Op op) { @@ -56,14 +46,7 @@ public class ProgramTrace { this.ops.addAll(programTrace.ops); } - public String getJsonString() { - try { - return new ObjectMapper() - .enable(SerializationFeature.INDENT_OUTPUT) - .writeValueAsString(this); - } catch (JsonProcessingException e) { - LOGGER.error("JSON serialization error: ", e); - return "{}"; - } + public String asJsonString() { + return serializeFieldsOnly(this, true); } } diff --git a/ethereumj-core/src/main/java/org/ethereum/vmtrace/Serializers.java b/ethereumj-core/src/main/java/org/ethereum/vmtrace/Serializers.java new file mode 100644 index 00000000..b7c06d74 --- /dev/null +++ b/ethereumj-core/src/main/java/org/ethereum/vmtrace/Serializers.java @@ -0,0 +1,73 @@ +package org.ethereum.vmtrace; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.introspect.VisibilityChecker; +import org.ethereum.vm.DataWord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.spongycastle.util.encoders.Hex; + +import java.io.IOException; + +final class Serializers { + + private static final Logger LOGGER = LoggerFactory.getLogger("vmtrace"); + + public static class DataWordSerializer extends JsonSerializer { + + @Override + public void serialize(DataWord gas, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeString(gas.value().toString()); + } + } + + public static class ByteArraySerializer extends JsonSerializer { + + @Override + public void serialize(byte[] memory, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeString(Hex.toHexString(memory)); + } + } + + public static class OpCodeSerializer extends JsonSerializer { + + @Override + public void serialize(Byte op, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException { + jgen.writeString(org.ethereum.vm.OpCode.code(op).name()); + } + } + + + public static String serializeFieldsOnly(Object value, boolean pretty) { + try { + ObjectMapper mapper = createMapper(pretty); + mapper.setVisibilityChecker(fieldsOnlyVisibilityChecker(mapper)); + + return mapper.writeValueAsString(value); + } catch (Exception e) { + LOGGER.error("JSON serialization error: ", e); + return "{}"; + } + } + + private static VisibilityChecker fieldsOnlyVisibilityChecker(ObjectMapper mapper) { + return mapper.getSerializationConfig().getDefaultVisibilityChecker() + .withFieldVisibility(JsonAutoDetect.Visibility.ANY) + .withGetterVisibility(JsonAutoDetect.Visibility.NONE) + .withIsGetterVisibility(JsonAutoDetect.Visibility.NONE); + } + + public static ObjectMapper createMapper(boolean pretty) { + ObjectMapper mapper = new ObjectMapper(); + if (pretty) { + mapper.enable(SerializationFeature.INDENT_OUTPUT); + } + return mapper; + } +}