From f17f4df0422e0b1028f21d8ab2ac0f6380325bbc Mon Sep 17 00:00:00 2001 From: rodiazet Date: Tue, 18 Apr 2023 11:08:36 +0200 Subject: [PATCH] Add tracing output for t8n --- lib/evmone/tracing.cpp | 61 +++++++++++++++++++++++++++++++++ lib/evmone/tracing.hpp | 2 ++ lib/evmone/vm.cpp | 22 +++++++++++- lib/evmone/vm.hpp | 13 ++++++- test/t8n/CMakeLists.txt | 1 + test/t8n/t8n.cpp | 25 +++++++++++--- test/unittests/evmone_test.cpp | 26 ++++++++++++++ test/unittests/tracing_test.cpp | 40 +++++++++++++++++++++ 8 files changed, 183 insertions(+), 7 deletions(-) diff --git a/lib/evmone/tracing.cpp b/lib/evmone/tracing.cpp index 8ef933aef7..f911130cd0 100644 --- a/lib/evmone/tracing.cpp +++ b/lib/evmone/tracing.cpp @@ -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. @@ -149,6 +150,61 @@ class InstructionTracer : public Tracer m_out << std::dec; // Set number formatting to dec, JSON does not support other forms. } }; + +/// Standard tracer implemented to satisfy retesteth requirements +/// Documented here: https://github.com/ethereum/tests/issues/249 +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 create_histogram_tracer(std::ostream& out) @@ -160,4 +216,9 @@ std::unique_ptr create_instruction_tracer(std::ostream& out) { return std::make_unique(out); } + +std::unique_ptr create_standard_tracer(std::ostream& out) +{ + return std::make_unique(out); +} } // namespace evmone diff --git a/lib/evmone/tracing.hpp b/lib/evmone/tracing.hpp index 3d923dcc49..7043ac973a 100644 --- a/lib/evmone/tracing.hpp +++ b/lib/evmone/tracing.hpp @@ -65,4 +65,6 @@ EVMC_EXPORT std::unique_ptr create_histogram_tracer(std::ostream& out); EVMC_EXPORT std::unique_ptr create_instruction_tracer(std::ostream& out); +EVMC_EXPORT std::unique_ptr create_standard_tracer(std::ostream& out); + } // namespace evmone diff --git a/lib/evmone/vm.cpp b/lib/evmone/vm.cpp index 108a278e5d..e400b05048 100644 --- a/lib/evmone/vm.cpp +++ b/lib/evmone/vm.cpp @@ -10,6 +10,7 @@ #include "baseline.hpp" #include #include +#include #include namespace evmone @@ -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)); @@ -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", @@ -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" { diff --git a/lib/evmone/vm.hpp b/lib/evmone/vm.hpp index 98c8d39105..926d0f1af8 100644 --- a/lib/evmone/vm.hpp +++ b/lib/evmone/vm.hpp @@ -5,6 +5,8 @@ #include "tracing.hpp" #include +#include +#include #if defined(_MSC_VER) && !defined(__clang__) #define EVMONE_CGOTO_SUPPORTED 0 @@ -22,9 +24,10 @@ class VM : public evmc_vm private: std::unique_ptr m_first_tracer; + std::vector m_tracing_outputs; public: - inline constexpr VM() noexcept; + inline VM() noexcept; void add_tracer(std::unique_ptr tracer) noexcept { @@ -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 diff --git a/test/t8n/CMakeLists.txt b/test/t8n/CMakeLists.txt index 206463e7e6..ea7943406c 100644 --- a/test/t8n/CMakeLists.txt +++ b/test/t8n/CMakeLists.txt @@ -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. diff --git a/test/t8n/t8n.cpp b/test/t8n/t8n.cpp index 5974fcd997..0325685e09 100644 --- a/test/t8n/t8n.cpp +++ b/test/t8n/t8n.cpp @@ -30,6 +30,7 @@ int main(int argc, const char* argv[]) fs::path output_body_file; std::optional block_reward; uint64_t chain_id = 0; + bool tracing_enabled = false; try { @@ -62,6 +63,8 @@ int main(int argc, const char* argv[]) chain_id = intx::from_string(argv[i]); else if (arg == "--output.body" && ++i < argc) output_body_file = argv[i]; + else if (arg == "--trace") + tracing_enabled = true; } state::BlockInfo block; @@ -106,9 +109,8 @@ int main(int argc, const char* argv[]) auto tx = test::from_json(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")) { @@ -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(res)) { const auto ec = std::get(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); @@ -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(receipt.gas_used)); cumulative_gas_used += receipt.gas_used; j_receipt["cumulativeGasUsed"] = hex0x(cumulative_gas_used); diff --git a/test/unittests/evmone_test.cpp b/test/unittests/evmone_test.cpp index a80dfd39e0..6f0da49289 100644 --- a/test/unittests/evmone_test.cpp +++ b/test/unittests/evmone_test.cpp @@ -6,6 +6,7 @@ #include #include #include +#include TEST(evmone, info) { @@ -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(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)); +} diff --git a/test/unittests/tracing_test.cpp b/test/unittests/tracing_test.cpp index 0d5054a719..327a08a449 100644 --- a/test/unittests/tracing_test.cpp +++ b/test/unittests/tracing_test.cpp @@ -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(*this, "A")); + vm.add_tracer(std::make_unique(*this, "B")); + vm.add_tracer(std::make_unique(*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(*this, "D")); + + EXPECT_EQ(trace(dup1(0)), "D0:PUSH1 D2:DUP1 "); +} + TEST_F(tracing, histogram) { vm.add_tracer(evmone::create_histogram_tracer(trace_stream)); @@ -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":""} +)"); +}