Skip to content

Commit

Permalink
rpcdaemon: debug_traceCallMany API (#1265)
Browse files Browse the repository at this point in the history
  • Loading branch information
Sixtysixter authored Jun 25, 2023
1 parent 47ed5ee commit 2e0c13b
Show file tree
Hide file tree
Showing 13 changed files with 7,544 additions and 29 deletions.
2 changes: 1 addition & 1 deletion docs/JSON-RPC-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ The following table shows the current [JSON RPC API](https://eth.wiki/json-rpc/A
| debug_traceBlockByNumber | Yes | uses JSON streaming |
| debug_traceTransaction | Yes | uses JSON streaming |
| debug_traceCall | Yes | uses JSON streaming |
| debug_traceCallMany | - | not yet implemented (see Erigon PR #4567) |
| debug_traceCallMany | Yes | uses JSON streaming |
| | | |
| trace_call | Yes | |
| trace_callMany | Yes | |
Expand Down
70 changes: 66 additions & 4 deletions silkworm/silkrpc/commands/debug_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ awaitable<void> DebugRpcApi::handle_debug_trace_transaction(const nlohmann::json

try {
ethdb::TransactionDatabase tx_database{*tx};
debug::DebugExecutor executor{tx_database, *tx, *block_cache_, workers_, config};
debug::DebugExecutor executor{tx_database, *block_cache_, workers_, config};
co_await executor.trace_transaction(stream, transaction_hash);
} catch (const std::exception& e) {
SILK_ERROR << "exception: " << e.what() << " processing request: " << request.dump();
Expand Down Expand Up @@ -336,7 +336,7 @@ awaitable<void> DebugRpcApi::handle_debug_trace_call(const nlohmann::json& reque
const core::rawdb::DatabaseReader& db_reader =
is_latest_block ? static_cast<core::rawdb::DatabaseReader&>(cached_database) : static_cast<core::rawdb::DatabaseReader&>(tx_database);

debug::DebugExecutor executor{db_reader, *tx, *block_cache_, workers_, config};
debug::DebugExecutor executor{db_reader, *block_cache_, workers_, config};
co_await executor.trace_call(stream, block_number_or_hash, call);
} catch (const std::exception& e) {
SILK_ERROR << "exception: " << e.what() << " processing request: " << request.dump();
Expand All @@ -356,6 +356,68 @@ awaitable<void> DebugRpcApi::handle_debug_trace_call(const nlohmann::json& reque
co_return;
}

// https://github.com/ethereum/retesteth/wiki/RPC-Methods#debug_tracecallmany
awaitable<void> DebugRpcApi::handle_debug_trace_call_many(const nlohmann::json& request, json::Stream& stream) {
if (!request.contains("params")) {
auto error_msg = "missing value for required arguments";
SILK_ERROR << error_msg << request.dump();
const auto reply = make_json_error(request["id"], 100, error_msg);
stream.write_json(reply);

co_return;
}

const auto& params = request["params"];
if (params.size() < 2) {
auto error_msg = "invalid debug_traceCallMany params: " + params.dump();
SILK_ERROR << error_msg;
const auto reply = make_json_error(request["id"], 100, error_msg);
stream.write_json(reply);

co_return;
}
const auto bundles = params[0].get<Bundles>();

if (bundles.empty()) {
const auto error_msg = "invalid debug_traceCallMany bundle list: " + params.dump();
SILK_ERROR << error_msg;
const auto reply = make_json_error(request["id"], 100, error_msg);
stream.write_json(reply);

co_return;
}

const auto simulation_context = params[1].get<SimulationContext>();

debug::DebugConfig config;
if (params.size() > 2) {
config = params[2].get<debug::DebugConfig>();
}

SILK_DEBUG << "bundles: " << bundles << " simulation_context: " << simulation_context << " config: {" << config << "}";

stream.open_object();
stream.write_field("id", request["id"]);
stream.write_field("jsonrpc", "2.0");

auto tx = co_await database_->begin();

try {
ethdb::TransactionDatabase tx_database{*tx};
debug::DebugExecutor executor{tx_database, *block_cache_, workers_, config};
co_await executor.trace_call_many(stream, bundles, simulation_context);
} catch (...) {
SILK_ERROR << "unexpected exception processing request: " << request.dump();
const Error error{100, "unexpected exception"};
stream.write_field("error", error);
}

stream.close_object();

co_await tx->close(); // RAII not (yet) available with coroutines
co_return;
}

// https://github.com/ethereum/retesteth/wiki/RPC-Methods#debug_traceblockbynumber
awaitable<void> DebugRpcApi::handle_debug_trace_block_by_number(const nlohmann::json& request, json::Stream& stream) {
const auto& params = request["params"];
Expand Down Expand Up @@ -384,7 +446,7 @@ awaitable<void> DebugRpcApi::handle_debug_trace_block_by_number(const nlohmann::
try {
ethdb::TransactionDatabase tx_database{*tx};

debug::DebugExecutor executor{tx_database, *tx, *block_cache_, workers_, config};
debug::DebugExecutor executor{tx_database, *block_cache_, workers_, config};
co_await executor.trace_block(stream, block_number);
} catch (const std::invalid_argument& e) {
SILK_ERROR << "exception: " << e.what() << " processing request: " << request.dump();
Expand Down Expand Up @@ -436,7 +498,7 @@ awaitable<void> DebugRpcApi::handle_debug_trace_block_by_hash(const nlohmann::js
try {
ethdb::TransactionDatabase tx_database{*tx};

debug::DebugExecutor executor{tx_database, *tx, *block_cache_, workers_, config};
debug::DebugExecutor executor{tx_database, *block_cache_, workers_, config};
co_await executor.trace_block(stream, block_hash);
} catch (const std::invalid_argument& e) {
SILK_ERROR << "exception: " << e.what() << " processing request: " << request.dump();
Expand Down
1 change: 1 addition & 0 deletions silkworm/silkrpc/commands/debug_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class DebugRpcApi {

awaitable<void> handle_debug_trace_transaction(const nlohmann::json& request, json::Stream& stream);
awaitable<void> handle_debug_trace_call(const nlohmann::json& request, json::Stream& stream);
awaitable<void> handle_debug_trace_call_many(const nlohmann::json& request, json::Stream& stream);
awaitable<void> handle_debug_trace_block_by_number(const nlohmann::json& request, json::Stream& stream);
awaitable<void> handle_debug_trace_block_by_hash(const nlohmann::json& request, json::Stream& stream);

Expand Down
1 change: 1 addition & 0 deletions silkworm/silkrpc/commands/rpc_api_table.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ void RpcApiTable::add_debug_handlers() {
method_handlers_[http::method::k_debug_storageRangeAt] = &commands::RpcApi::handle_debug_storage_range_at;

stream_handlers_[http::method::k_debug_traceCall] = &commands::RpcApi::handle_debug_trace_call;
stream_handlers_[http::method::k_debug_traceCallMany] = &commands::RpcApi::handle_debug_trace_call_many;
stream_handlers_[http::method::k_debug_traceTransaction] = &commands::RpcApi::handle_debug_trace_transaction;
stream_handlers_[http::method::k_debug_traceBlockByNumber] = &commands::RpcApi::handle_debug_trace_block_by_number;
stream_handlers_[http::method::k_debug_traceBlockByHash] = &commands::RpcApi::handle_debug_trace_block_by_hash;
Expand Down
112 changes: 106 additions & 6 deletions silkworm/silkrpc/core/evm_debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,7 @@ boost::asio::awaitable<void> DebugExecutor::trace_block(json::Stream& stream, co
}

boost::asio::awaitable<void> DebugExecutor::trace_call(json::Stream& stream, const BlockNumberOrHash& bnoh, const Call& call) {
ethdb::TransactionDatabase tx_database{transaction_};

const auto block_with_hash = co_await rpc::core::read_block_by_number_or_hash(block_cache_, tx_database, bnoh);
const auto block_with_hash = co_await rpc::core::read_block_by_number_or_hash(block_cache_, database_reader_, bnoh);
rpc::Transaction transaction{call.to_transaction()};

const auto& block = block_with_hash->block;
Expand All @@ -302,9 +300,7 @@ boost::asio::awaitable<void> DebugExecutor::trace_call(json::Stream& stream, con
}

boost::asio::awaitable<void> DebugExecutor::trace_transaction(json::Stream& stream, const evmc::bytes32& tx_hash) {
ethdb::TransactionDatabase tx_database{transaction_};

const auto tx_with_block = co_await rpc::core::read_transaction_by_hash(block_cache_, tx_database, tx_hash);
const auto tx_with_block = co_await rpc::core::read_transaction_by_hash(block_cache_, database_reader_, tx_hash);

if (!tx_with_block) {
std::ostringstream oss;
Expand All @@ -325,6 +321,21 @@ boost::asio::awaitable<void> DebugExecutor::trace_transaction(json::Stream& stre
co_return;
}

boost::asio::awaitable<void> DebugExecutor::trace_call_many(json::Stream& stream, const Bundles& bundles, const SimulationContext& context) {
const auto block_with_hash = co_await rpc::core::read_block_by_number_or_hash(block_cache_, database_reader_, context.block_number);
auto transaction_index = context.transaction_index;
if (transaction_index == -1) {
transaction_index = static_cast<std::int32_t>(block_with_hash->block.transactions.size());
}

stream.write_field("result");
stream.open_array();
co_await execute(stream, *block_with_hash, bundles, transaction_index);
stream.close_array();

co_return;
}

awaitable<void> DebugExecutor::execute(json::Stream& stream, const silkworm::Block& block) {
auto block_number = block.header.number;
const auto& transactions = block.transactions;
Expand Down Expand Up @@ -426,4 +437,93 @@ awaitable<void> DebugExecutor::execute(json::Stream& stream, uint64_t block_numb
co_return;
}

awaitable<void> DebugExecutor::execute(json::Stream& stream,
const silkworm::BlockWithHash& block_with_hash,
const Bundles& bundles,
int32_t transaction_index) {
const auto& block = block_with_hash.block;
const auto& block_transactions = block.transactions;

SILK_INFO << "DebugExecutor::execute: "
<< " block number: " << block.header.number
<< " txns in block: " << block_transactions.size()
<< " bundles: [" << bundles << "]"
<< " transaction_index: " << std::dec << transaction_index
<< " config: " << config_;

const auto chain_id = co_await core::rawdb::read_chain_id(database_reader_);
const auto chain_config_ptr = lookup_chain_config(chain_id);

auto current_executor = co_await boost::asio::this_coro::executor;
state::RemoteState remote_state{current_executor, database_reader_, block.header.number};

EVMExecutor executor{*chain_config_ptr, workers_, remote_state};

for (auto idx{0}; idx < transaction_index; idx++) {
silkworm::Transaction txn{block_transactions[std::size_t(idx)]};

if (!txn.from) {
txn.recover_sender();
}

co_await executor.call(block, txn);
}
executor.reset();

for (const auto& bundle : bundles) {
const auto& block_override = bundle.block_override;

rpc::Block blockContext{{block_with_hash.block}};
if (block_override.block_number) {
blockContext.block.header.number = block_override.block_number.value();
}
if (block_override.coin_base) {
blockContext.block.header.beneficiary = block_override.coin_base.value();
}
if (block_override.timestamp) {
blockContext.block.header.timestamp = block_override.timestamp.value();
}
if (block_override.difficulty) {
blockContext.block.header.difficulty = block_override.difficulty.value();
}
if (block_override.gas_limit) {
blockContext.block.header.gas_limit = block_override.gas_limit.value();
}
if (block_override.base_fee) {
blockContext.block.header.base_fee_per_gas = block_override.base_fee;
}

stream.open_array();

for (const auto& call : bundle.transactions) {
silkworm::Transaction txn{call.to_transaction()};

stream.open_object();
stream.write_field("structLogs");
stream.open_array();

auto debug_tracer = std::make_shared<debug::DebugTracer>(stream, config_);
Tracers tracers{debug_tracer};

const auto execution_result = co_await executor.call(blockContext.block, txn, tracers, /* refund */ false, /* gasBailout */ false);

debug_tracer->flush_logs();
stream.close_array();

if (execution_result.pre_check_error) {
SILK_DEBUG << "debug failed: " << execution_result.pre_check_error.value();

stream.write_field("failed", true);
} else {
stream.write_field("failed", execution_result.error_code != evmc_status_code::EVMC_SUCCESS);
stream.write_field("gas", txn.gas_limit - execution_result.gas_left);
stream.write_field("returnValue", silkworm::to_hex(execution_result.data));
}
stream.close_object();
}

stream.close_array();
}
}

} // namespace silkworm::rpc::debug
11 changes: 8 additions & 3 deletions silkworm/silkrpc/core/evm_debug.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
#include <silkworm/silkrpc/common/block_cache.hpp>
#include <silkworm/silkrpc/core/rawdb/accessors.hpp>
#include <silkworm/silkrpc/ethdb/transaction.hpp>
#include <silkworm/silkrpc/ethdb/transaction_database.hpp>
#include <silkworm/silkrpc/json/stream.hpp>
#include <silkworm/silkrpc/types/block.hpp>
#include <silkworm/silkrpc/types/call.hpp>
Expand Down Expand Up @@ -100,11 +101,10 @@ class DebugExecutor {
public:
explicit DebugExecutor(
const core::rawdb::DatabaseReader& database_reader,
ethdb::Transaction& transaction,
BlockCache& block_cache,
boost::asio::thread_pool& workers,
DebugConfig config = {})
: database_reader_(database_reader), transaction_(transaction), block_cache_(block_cache), workers_{workers}, config_{config} {}
: database_reader_(database_reader), block_cache_(block_cache), workers_{workers}, config_{config} {}
virtual ~DebugExecutor() = default;

DebugExecutor(const DebugExecutor&) = delete;
Expand All @@ -114,6 +114,7 @@ class DebugExecutor {
boost::asio::awaitable<void> trace_block(json::Stream& stream, const evmc::bytes32& block_hash);
boost::asio::awaitable<void> trace_call(json::Stream& stream, const BlockNumberOrHash& bnoh, const Call& call);
boost::asio::awaitable<void> trace_transaction(json::Stream& stream, const evmc::bytes32& tx_hash);
boost::asio::awaitable<void> trace_call_many(json::Stream& stream, const Bundles& bundles, const SimulationContext& context);

boost::asio::awaitable<void> execute(json::Stream& stream, const silkworm::Block& block, const Call& call);

Expand All @@ -122,8 +123,12 @@ class DebugExecutor {
boost::asio::awaitable<void> execute(json::Stream& stream, std::uint64_t block_number,
const silkworm::Block& block, const Transaction& transaction, int32_t = -1);

boost::asio::awaitable<void> execute(json::Stream& stream,
const silkworm::BlockWithHash& block_with_hash,
const Bundles& bundles,
int32_t transaction_index);

const core::rawdb::DatabaseReader& database_reader_;
ethdb::Transaction& transaction_;
BlockCache& block_cache_;
boost::asio::thread_pool& workers_;
DebugConfig config_;
Expand Down
Loading

0 comments on commit 2e0c13b

Please sign in to comment.