Skip to content

Commit

Permalink
Add tracing output for t8n
Browse files Browse the repository at this point in the history
  • Loading branch information
rodiazet committed Apr 14, 2023
1 parent 31fd534 commit 74e9d0d
Show file tree
Hide file tree
Showing 8 changed files with 182 additions and 7 deletions.
60 changes: 60 additions & 0 deletions lib/evmone/tracing.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ class HistogramTracer : public Tracer

class InstructionTracer : public Tracer
{
protected:
struct Context
{
const uint8_t* const code; ///< Reference to the code being executed.
Expand Down Expand Up @@ -149,6 +150,60 @@ class InstructionTracer : public Tracer
m_out << std::dec; // Set number formatting to dec, JSON does not support other forms.
}
};


class StandardTracer : public InstructionTracer
{
void on_execution_start(
evmc_revision /*rev*/, const evmc_message& msg, bytes_view code) noexcept override
{
m_contexts.emplace(code.data(), msg.gas);
}

void on_instruction_start(uint32_t pc, const intx::uint256* stack_top, int stack_height,
int64_t gas, const ExecutionState& state) noexcept override
{
const auto& ctx = m_contexts.top();

const auto opcode = ctx.code[pc];
m_out << "{";
m_out << R"("pc":)" << std::dec << pc;
m_out << R"(,"op":)" << std::dec << int{opcode};
m_out << R"(,"opName":")" << get_name(opcode) << '"';
m_out << R"(,"gas":"0x)" << std::hex << gas << '"';
m_out << R"("gasCost":"0x)" << std::hex << instr::gas_costs[state.rev][opcode] << '"';
output_stack(stack_top, stack_height);
m_out << R"("depth":)" << std::dec << state.msg->depth;
m_out << R"("refund":)" << std::dec << state.gas_refund;

// Full memory can be dumped as evmc::hex({state.memory.data(), state.memory.size()}),
// but this should not be done by default. Adding --tracing=+memory option would be nice.
m_out << R"(,"memSize":)" << std::dec << state.memory.size();

m_out << "}\n";
}

void on_execution_end(const evmc_result& result) noexcept override
{
const auto& ctx = m_contexts.top();

m_out << "{";
m_out << R"("error":)";
if (result.status_code == EVMC_SUCCESS)
m_out << R"("")";
else
m_out << '"' << result.status_code << '"';
m_out << R"(,"gasUsed":"0x)" << std::hex << (ctx.start_gas - result.gas_left) << '"';
m_out << R"(,"output":")" << evmc::hex({result.output_data, result.output_size}) << '"';
m_out << "}\n";

m_contexts.pop();
}

public:
explicit StandardTracer(std::ostream& out) noexcept : InstructionTracer(out) {}
};

} // namespace

std::unique_ptr<Tracer> create_histogram_tracer(std::ostream& out)
Expand All @@ -160,4 +215,9 @@ std::unique_ptr<Tracer> create_instruction_tracer(std::ostream& out)
{
return std::make_unique<InstructionTracer>(out);
}

std::unique_ptr<Tracer> create_standard_tracer(std::ostream& out)
{
return std::make_unique<StandardTracer>(out);
}
} // namespace evmone
2 changes: 2 additions & 0 deletions lib/evmone/tracing.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,6 @@ EVMC_EXPORT std::unique_ptr<Tracer> create_histogram_tracer(std::ostream& out);

EVMC_EXPORT std::unique_ptr<Tracer> create_instruction_tracer(std::ostream& out);

EVMC_EXPORT std::unique_ptr<Tracer> create_standard_tracer(std::ostream& out);

} // namespace evmone
22 changes: 21 additions & 1 deletion lib/evmone/vm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "baseline.hpp"
#include <evmone/evmone.h>
#include <cassert>
#include <filesystem>
#include <iostream>

namespace evmone
Expand Down Expand Up @@ -56,6 +57,14 @@ evmc_set_option_result set_option(evmc_vm* c_vm, char const* c_name, char const*
vm.add_tracer(create_instruction_tracer(std::cerr));
return EVMC_SET_OPTION_SUCCESS;
}
else if (name == "stdtrace")
{
if (value == "no")
vm.remove_tracers(); // TODO: It removes all. Consider adding "no" value to `trace`?
else
vm.add_standard_tracer(value);
return EVMC_SET_OPTION_SUCCESS;
}
else if (name == "histogram")
{
vm.add_tracer(create_histogram_tracer(std::cerr));
Expand All @@ -67,7 +76,7 @@ evmc_set_option_result set_option(evmc_vm* c_vm, char const* c_name, char const*
} // namespace


inline constexpr VM::VM() noexcept
inline VM::VM() noexcept
: evmc_vm{
EVMC_ABI_VERSION,
"evmone",
Expand All @@ -79,6 +88,17 @@ inline constexpr VM::VM() noexcept
}
{}

void VM::add_standard_tracer(std::string_view output_name) noexcept
{
if (output_name == "stderr")
add_tracer(create_standard_tracer(std::cerr));
else // File output
{
add_tracer(create_standard_tracer(
m_tracing_outputs.emplace_back(std::filesystem::path(output_name))));
}
}

} // namespace evmone

extern "C" {
Expand Down
13 changes: 12 additions & 1 deletion lib/evmone/vm.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

#include "tracing.hpp"
#include <evmc/evmc.h>
#include <fstream>
#include <vector>

#if defined(_MSC_VER) && !defined(__clang__)
#define EVMONE_CGOTO_SUPPORTED 0
Expand All @@ -22,9 +24,10 @@ class VM : public evmc_vm

private:
std::unique_ptr<Tracer> m_first_tracer;
std::vector<std::ofstream> m_tracing_outputs;

public:
inline constexpr VM() noexcept;
inline VM() noexcept;

void add_tracer(std::unique_ptr<Tracer> tracer) noexcept
{
Expand All @@ -35,6 +38,14 @@ class VM : public evmc_vm
*end = std::move(tracer);
}

void add_standard_tracer(std::string_view output_name) noexcept;

void remove_tracers() noexcept
{
m_first_tracer = nullptr;
m_tracing_outputs.clear();
}

[[nodiscard]] Tracer* get_tracer() const noexcept { return m_first_tracer.get(); }
};
} // namespace evmone
1 change: 1 addition & 0 deletions test/t8n/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ find_package(nlohmann_json CONFIG REQUIRED)
add_executable(evmone-t8n)
target_link_libraries(evmone-t8n PRIVATE evmone::statetestutils nlohmann_json::nlohmann_json)
target_link_libraries(evmone-t8n PRIVATE evmc::evmc evmone)
target_include_directories(evmone-t8n PRIVATE ${evmone_private_include_dir})
target_sources(evmone-t8n PRIVATE t8n.cpp)

# Provide the project version to selected source files.
Expand Down
25 changes: 20 additions & 5 deletions test/t8n/t8n.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ int main(int argc, const char* argv[])
fs::path output_body_file;
std::optional<uint64_t> block_reward;
uint64_t chain_id = 0;
bool tracing_enabled = false;

try
{
Expand Down Expand Up @@ -62,6 +63,8 @@ int main(int argc, const char* argv[])
chain_id = intx::from_string<uint64_t>(argv[i]);
else if (arg == "--output.body" && ++i < argc)
output_body_file = argv[i];
else if (arg == "--trace")
tracing_enabled = true;
}

state::BlockInfo block;
Expand Down Expand Up @@ -106,9 +109,8 @@ int main(int argc, const char* argv[])
auto tx = test::from_json<state::Transaction>(j_txs[i]);
tx.chain_id = chain_id;

auto res = state::transition(state, block, tx, rev, vm);

const auto computed_tx_hash = keccak256(rlp::encode(tx));
const auto computed_tx_hash_str = hex0x(computed_tx_hash);

if (j_txs[i].contains("hash"))
{
Expand All @@ -117,15 +119,28 @@ int main(int argc, const char* argv[])

if (loaded_tx_hash_opt != computed_tx_hash)
throw std::logic_error("transaction hash mismatched: computed " +
hex0x(computed_tx_hash) + ", expected " +
computed_tx_hash_str + ", expected " +
hex0x(loaded_tx_hash_opt.value()));
}

if (tracing_enabled)
{
// Remove old tracers
vm.set_option("stdtrace", "no");
auto output_filename =
fs::path(output_dir / ("trace-" + std::to_string(i) + "-" +
computed_tx_hash_str + ".jsonl"));
// Add 'to file' tracer. Casting required by windows build.
vm.set_option("stdtrace", (const char*)(output_filename.c_str()));
}

auto res = state::transition(state, block, tx, rev, vm);

if (holds_alternative<std::error_code>(res))
{
const auto ec = std::get<std::error_code>(res);
json::json j_rejected_tx;
j_rejected_tx["hash"] = hex0x(computed_tx_hash);
j_rejected_tx["hash"] = computed_tx_hash_str;
j_rejected_tx["index"] = i;
j_rejected_tx["error"] = ec.message();
j_result["rejected"].push_back(j_rejected_tx);
Expand All @@ -139,7 +154,7 @@ int main(int argc, const char* argv[])
txs_logs.insert(txs_logs.end(), tx_logs.begin(), tx_logs.end());
auto& j_receipt = j_result["receipts"][j_result["receipts"].size()];

j_receipt["transactionHash"] = hex0x(computed_tx_hash);
j_receipt["transactionHash"] = computed_tx_hash_str;
j_receipt["gasUsed"] = hex0x(static_cast<uint64_t>(receipt.gas_used));
cumulative_gas_used += receipt.gas_used;
j_receipt["cumulativeGasUsed"] = hex0x(cumulative_gas_used);
Expand Down
26 changes: 26 additions & 0 deletions test/unittests/evmone_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <evmone/evmone.h>
#include <evmone/vm.hpp>
#include <gtest/gtest.h>
#include <filesystem>

TEST(evmone, info)
{
Expand Down Expand Up @@ -53,3 +54,28 @@ TEST(evmone, set_option_cgoto)
EXPECT_EQ(vm.set_option("cgoto", "no"), EVMC_SET_OPTION_INVALID_NAME);
#endif
}

TEST(evmone, set_option_stdtracer)
{
evmc::VM vm{evmc_create_evmone()};

EXPECT_EQ(vm.set_option("stdtrace", "stderr"), EVMC_SET_OPTION_SUCCESS);
const auto& evm = *static_cast<evmone::VM*>(vm.get_raw_pointer());
ASSERT_NE(evm.get_tracer(), nullptr);

EXPECT_EQ(vm.set_option("stdtrace", "no"), EVMC_SET_OPTION_SUCCESS);
ASSERT_EQ(evm.get_tracer(), nullptr);

// TODO: Find better why to create tmp file name.
auto trace_test_filename = "set_option_stdtracer_test_trace_file.json";
ASSERT_FALSE(std::filesystem::exists(trace_test_filename));
EXPECT_EQ(vm.set_option("stdtrace", trace_test_filename), EVMC_SET_OPTION_SUCCESS);
ASSERT_NE(evm.get_tracer(), nullptr);

EXPECT_EQ(vm.set_option("stdtrace", "no"), EVMC_SET_OPTION_SUCCESS);
ASSERT_EQ(evm.get_tracer(), nullptr);

ASSERT_TRUE(std::filesystem::exists(trace_test_filename));
std::filesystem::remove(trace_test_filename);
ASSERT_FALSE(std::filesystem::exists(trace_test_filename));
}
40 changes: 40 additions & 0 deletions test/unittests/tracing_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,20 @@ TEST_F(tracing, three_tracers)
EXPECT_EQ(trace(dup1(0)), "A0:PUSH1 B0:PUSH1 C0:PUSH1 A2:DUP1 B2:DUP1 C2:DUP1 ");
}

TEST_F(tracing, remove_tracer)
{
vm.add_tracer(std::make_unique<OpcodeTracer>(*this, "A"));
vm.add_tracer(std::make_unique<OpcodeTracer>(*this, "B"));
vm.add_tracer(std::make_unique<OpcodeTracer>(*this, "C"));

EXPECT_EQ(trace(dup1(0)), "A0:PUSH1 B0:PUSH1 C0:PUSH1 A2:DUP1 B2:DUP1 C2:DUP1 ");

vm.remove_tracers();
vm.add_tracer(std::make_unique<OpcodeTracer>(*this, "D"));

EXPECT_EQ(trace(dup1(0)), "D0:PUSH1 D2:DUP1 ");
}

TEST_F(tracing, histogram)
{
vm.add_tracer(evmone::create_histogram_tracer(trace_stream));
Expand Down Expand Up @@ -308,3 +322,29 @@ TEST_F(tracing, trace_eof)
{"error":null,"gas":0xf4237,"gasUsed":0x9,"output":""}
)");
}

TEST_F(tracing, standard_tracer)
{
vm.add_tracer(evmone::create_standard_tracer(trace_stream));

trace_stream << '\n';
EXPECT_EQ(trace(add(2, 3)), R"(
{"pc":0,"op":96,"opName":"PUSH1","gas":"0xf4240""gasCost":"0x3","stack":[]"depth":0"refund":0,"memSize":0}
{"pc":2,"op":96,"opName":"PUSH1","gas":"0xf423d""gasCost":"0x3","stack":["0x3"]"depth":0"refund":0,"memSize":0}
{"pc":4,"op":1,"opName":"ADD","gas":"0xf423a""gasCost":"0x3","stack":["0x3","0x2"]"depth":0"refund":0,"memSize":0}
{"error":"","gasUsed":"0x9","output":""}
)");
}

TEST_F(tracing, standard_tracer_trace_undefined_instruction)
{
vm.add_tracer(evmone::create_standard_tracer(trace_stream));

const auto code = bytecode{} + OP_JUMPDEST + "EF";
trace_stream << '\n';
EXPECT_EQ(trace(code), R"(
{"pc":0,"op":91,"opName":"JUMPDEST","gas":"0xf4240""gasCost":"0x1","stack":[]"depth":0"refund":0,"memSize":0}
{"pc":1,"op":239,"opName":"0xef","gas":"0xf423f""gasCost":"0xffff","stack":[]"depth":0"refund":0,"memSize":0}
{"error":"undefined instruction","gasUsed":"0xf4240","output":""}
)");
}

0 comments on commit 74e9d0d

Please sign in to comment.