diff --git a/silkworm/node/stagedsync/stages_test.cpp b/silkworm/node/stagedsync/stages_test.cpp index cc1819aa62..f3e78069f3 100644 --- a/silkworm/node/stagedsync/stages_test.cpp +++ b/silkworm/node/stagedsync/stages_test.cpp @@ -18,6 +18,7 @@ #include #include +#include #include #include #include @@ -30,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -52,6 +54,17 @@ static stagedsync::Execution make_execution_stage( }; } +static stagedsync::CallTraceIndex make_call_traces_stage( + stagedsync::SyncContext* sync_context, + const NodeSettings& node_settings) { + return stagedsync::CallTraceIndex{ + sync_context, + node_settings.batch_size, + node_settings.etl(), + node_settings.prune_mode.call_traces(), + }; +} + TEST_CASE("Sync Stages") { TemporaryDirectory temp_dir{}; NodeSettings node_settings{}; @@ -502,4 +515,117 @@ TEST_CASE("Sync Stages") { } } } + + SECTION("Execution and CallTraceIndex") { + using namespace magic_enum; + using StageResult = stagedsync::Stage::Result; + + // Prepare block 1 + const auto miner{0x5a0b54d5dc17e0aadc383d2db43b0a0d3e029c4c_address}; + const auto sender{0x9b9e32061f64f6c3f570a63b97a178d84e961db1_address}; + const auto receiver{miner}; + + Block block{}; + block.header.number = 1; + block.header.beneficiary = receiver; + block.header.gas_limit = 100'000; + block.header.gas_used = 21'000; + + static constexpr auto kEncoder = [](Bytes& to, const Receipt& r) { rlp::encode(to, r); }; + std::vector receipts{ + {TransactionType::kLegacy, true, block.header.gas_used, {}, {}}, + }; + block.header.receipts_root = trie::root_hash(receipts, kEncoder); + + block.transactions.resize(1); + block.transactions[0].gas_limit = block.header.gas_limit; + block.transactions[0].type = TransactionType::kLegacy; + + block.transactions[0].to = miner; + static_cast(block.transactions[0].set_v(27)); + block.transactions[0].r = + intx::from_string("0x48b55bfa915ac795c431978d8a6a992b628d557da5ff759b307d495a36649353"); + block.transactions[0].s = + intx::from_string("0x1fffd310ac743f371de3b9f7f9cb56c0b28ad43601b4ab949f53faa07bd2c804"); + block.transactions[0].value = 0; + + db::write_header(txn, block.header, /*with_header_numbers=*/true); + db::write_body(txn, block, block.header.hash(), block.header.number); + db::write_canonical_header_hash(txn, block.header.hash().bytes, block.header.number); + + // Stage Execution up to block 1 + REQUIRE_NOTHROW(db::stages::write_stage_progress(txn, db::stages::kSendersKey, 1)); + + stagedsync::SyncContext sync_context{}; + sync_context.target_height = 1; + stagedsync::Execution stage_execution = make_execution_stage(&sync_context, node_settings); + CHECK(stage_execution.forward(txn) == StageResult::kSuccess); + + // Post-condition: CallTraceSet table + { + auto call_traces_cursor{txn.ro_cursor(db::table::kCallTraceSet)}; + REQUIRE(call_traces_cursor->size() == 2); + const auto call_traces_record1{call_traces_cursor->to_next(/*throw_notfound=*/false)}; + REQUIRE(call_traces_record1.done); + const auto call_traces_record2{call_traces_cursor->to_next(/*throw_notfound=*/false)}; + REQUIRE(call_traces_record2.done); + auto check_call_trace_record = [&](const auto record, const auto& address, bool is_sender, bool is_receiver) { + const auto block_number = endian::load_big_u64(static_cast(record.key.data())); + CHECK(block_number == 1); + const ByteView value{static_cast(record.value.data()), record.value.length()}; + REQUIRE(value.size() == kAddressLength + 1); + CHECK(value.substr(0, kAddressLength) == address); + CHECK(bool(value[kAddressLength] & 1) == is_sender); + CHECK(bool(value[kAddressLength] & 2) == is_receiver); + }; + check_call_trace_record(call_traces_record1, receiver, /*.from=*/false, /*.to=*/true); + check_call_trace_record(call_traces_record2, sender, /*.from=*/true, /*.to=*/false); + } + + // Stage CallTraceIndex up to block 1 + stagedsync::CallTraceIndex stage_call_traces = make_call_traces_stage(&sync_context, node_settings); + REQUIRE(db::stages::read_stage_progress(txn, db::stages::kCallTracesKey) == 0); + const auto forward_result{stage_call_traces.forward(txn)}; + CHECK(enum_name(forward_result) == enum_name(StageResult::kSuccess)); + CHECK(db::stages::read_stage_progress(txn, db::stages::kCallTracesKey) == 1); + + // Post-condition: CallFromIndex table + { + auto call_from_cursor{txn.ro_cursor(db::table::kCallFromIndex)}; + REQUIRE(call_from_cursor->size() == 1); + const auto call_from_record{call_from_cursor->to_next(/*throw_notfound=*/false)}; + REQUIRE(call_from_record.done); + const auto address_data{db::from_slice(call_from_record.key)}; + REQUIRE(address_data.size() == kAddressLength + sizeof(uint64_t)); + CHECK(bytes_to_address(address_data.substr(0, kAddressLength)) == sender); + const auto bitmap_encoded{byte_view_to_string_view(db::from_slice(call_from_record.value))}; + const auto bitmap{db::bitmap::parse(bitmap_encoded)}; + CHECK(db::bitmap::seek(bitmap, 1)); + } + + // Post-condition: CallToIndex table + { + auto call_to_cursor{txn.ro_cursor(db::table::kCallToIndex)}; + REQUIRE(call_to_cursor->size() == 1); + const auto call_to_record{call_to_cursor->to_next(/*throw_notfound=*/false)}; + REQUIRE(call_to_record.done); + const auto address_data{db::from_slice(call_to_record.key)}; + REQUIRE(address_data.size() == kAddressLength + sizeof(uint64_t)); + CHECK(bytes_to_address(address_data.substr(0, kAddressLength)) == receiver); + const auto bitmap_encoded{byte_view_to_string_view(db::from_slice(call_to_record.value))}; + const auto bitmap{db::bitmap::parse(bitmap_encoded)}; + CHECK(db::bitmap::seek(bitmap, 1)); + } + + // Unwind the stage down to block 0 (i.e. block 0 *is* applied) + const BlockNum unwind_to{0}; + sync_context.unwind_point.emplace(unwind_to); + const auto unwind_result{stage_call_traces.unwind(txn)}; + CHECK(enum_name(unwind_result) == enum_name(StageResult::kSuccess)); + CHECK(db::stages::read_stage_progress(txn, db::stages::kCallTracesKey) == unwind_to); + auto call_from_cursor{txn.ro_cursor(db::table::kCallFromIndex)}; + CHECK(call_from_cursor->empty()); + auto call_to_cursor{txn.ro_cursor(db::table::kCallToIndex)}; + CHECK(call_to_cursor->empty()); + } }