Skip to content

Commit

Permalink
rpcdaemon: add ots_getInternalOperations API (#1226)
Browse files Browse the repository at this point in the history
  • Loading branch information
riuzzoda authored Jun 28, 2023
1 parent b2d9bd4 commit 8ec57da
Show file tree
Hide file tree
Showing 12 changed files with 309 additions and 20 deletions.
2 changes: 1 addition & 1 deletion docs/JSON-RPC-API.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ The following table shows the current [JSON RPC API](https://eth.wiki/json-rpc/A
| parity_listStorageKeys | Yes | |
| | | |
| ots_getApiLevel | Yes | |
| ots_getInternalOperations | - | not yet implemented |
| ots_getInternalOperations | Yes | |
| ots_searchTransactionsBefore | - | not yet implemented |
| ots_searchTransactionsAfter | - | not yet implemented |
| ots_getBlockDetails | Yes | |
Expand Down
3 changes: 2 additions & 1 deletion silkworm/core/execution/evm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,12 +142,13 @@ evmc::Result EVM::create(const evmc_message& message) noexcept {
state_.add_to_balance(contract_addr, value);

const evmc_message deploy_message{
.kind = EVMC_CALL,
.kind = message.depth > 0 ? message.kind : EVMC_CALL,
.depth = message.depth,
.gas = message.gas,
.recipient = contract_addr,
.sender = message.sender,
.value = message.value,
.create2_salt = message.create2_salt,
};

auto evm_res{execute(deploy_message, ByteView{message.input_data, message.input_size}, /*code_hash=*/nullptr)};
Expand Down
79 changes: 63 additions & 16 deletions silkworm/core/execution/evm_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -370,12 +370,12 @@ class TestTracer : public EvmTracer {
public:
explicit TestTracer(std::optional<evmc::address> contract_address = std::nullopt,
std::optional<evmc::bytes32> key = std::nullopt)
: contract_address_(contract_address), key_(key), rev_{}, msg_{} {}
: contract_address_(contract_address), key_(key), rev_{} {}

void on_execution_start(evmc_revision rev, const evmc_message& msg, evmone::bytes_view bytecode) noexcept override {
execution_start_called_ = true;
rev_ = rev;
msg_ = msg;
msg_stack_.push_back(msg);
bytecode_ = Bytes{bytecode};
}
void on_instruction_start(uint32_t pc, const intx::uint256* /*stack_top*/, int /*stack_height*/,
Expand Down Expand Up @@ -414,7 +414,7 @@ class TestTracer : public EvmTracer {
[[nodiscard]] bool creation_completed_called() const { return creation_completed_called_; }
[[nodiscard]] const Bytes& bytecode() const { return bytecode_; }
[[nodiscard]] const evmc_revision& rev() const { return rev_; }
[[nodiscard]] const evmc_message& msg() const { return msg_; }
[[nodiscard]] const std::vector<evmc_message>& msg_stack() const { return msg_stack_; }
[[nodiscard]] const std::vector<uint32_t>& pc_stack() const { return pc_stack_; }
[[nodiscard]] const std::map<uint32_t, std::size_t>& memory_size_stack() const { return memory_size_stack_; }
[[nodiscard]] const std::map<uint32_t, evmc::bytes32>& storage_stack() const { return storage_stack_; }
Expand All @@ -427,7 +427,7 @@ class TestTracer : public EvmTracer {
std::optional<evmc::address> contract_address_;
std::optional<evmc::bytes32> key_;
evmc_revision rev_;
evmc_message msg_;
std::vector<evmc_message> msg_stack_;
Bytes bytecode_;
std::vector<uint32_t> pc_stack_;
std::map<uint32_t, std::size_t> memory_size_stack_;
Expand Down Expand Up @@ -485,10 +485,10 @@ TEST_CASE("Tracing smart contract with storage") {

CHECK((tracer1.execution_start_called() && tracer1.execution_end_called() && tracer1.creation_completed_called()));
CHECK(tracer1.rev() == evmc_revision::EVMC_ISTANBUL);
CHECK(tracer1.msg().kind == evmc_call_kind::EVMC_CALL);
CHECK(tracer1.msg().flags == 0);
CHECK(tracer1.msg().depth == 0);
CHECK(tracer1.msg().gas == 0);
CHECK(tracer1.msg_stack().at(0).kind == evmc_call_kind::EVMC_CALL);
CHECK(tracer1.msg_stack().at(0).flags == 0);
CHECK(tracer1.msg_stack().at(0).depth == 0);
CHECK(tracer1.msg_stack().at(0).gas == 0);
CHECK(tracer1.bytecode() == code);
CHECK(tracer1.pc_stack() == std::vector<uint32_t>{0});
CHECK(tracer1.memory_size_stack() == std::map<uint32_t, std::size_t>{{0, 0}});
Expand All @@ -508,10 +508,10 @@ TEST_CASE("Tracing smart contract with storage") {

CHECK((tracer2.execution_start_called() && tracer2.execution_end_called()));
CHECK(tracer2.rev() == evmc_revision::EVMC_ISTANBUL);
CHECK(tracer2.msg().kind == evmc_call_kind::EVMC_CALL);
CHECK(tracer2.msg().flags == 0);
CHECK(tracer2.msg().depth == 0);
CHECK(tracer2.msg().gas == 50'000);
CHECK(tracer2.msg_stack().at(0).kind == evmc_call_kind::EVMC_CALL);
CHECK(tracer2.msg_stack().at(0).flags == 0);
CHECK(tracer2.msg_stack().at(0).depth == 0);
CHECK(tracer2.msg_stack().at(0).gas == 50'000);
CHECK(tracer2.bytecode() == code);
CHECK(tracer2.pc_stack() == std::vector<uint32_t>{0, 2, 4, 5, 8, 10, 11, 13, 14, 16, 18, 19, 21});
CHECK(tracer2.memory_size_stack() == std::map<uint32_t, std::size_t>{{0, 0},
Expand Down Expand Up @@ -551,10 +551,10 @@ TEST_CASE("Tracing smart contract with storage") {

CHECK((tracer3.execution_start_called() && tracer3.execution_end_called()));
CHECK(tracer3.rev() == evmc_revision::EVMC_ISTANBUL);
CHECK(tracer3.msg().kind == evmc_call_kind::EVMC_CALL);
CHECK(tracer3.msg().flags == 0);
CHECK(tracer3.msg().depth == 0);
CHECK(tracer3.msg().gas == 50'000);
CHECK(tracer3.msg_stack().at(0).kind == evmc_call_kind::EVMC_CALL);
CHECK(tracer3.msg_stack().at(0).flags == 0);
CHECK(tracer3.msg_stack().at(0).depth == 0);
CHECK(tracer3.msg_stack().at(0).gas == 50'000);
CHECK(tracer3.storage_stack() == std::map<uint32_t, evmc::bytes32>{
{0, to_bytes32(*from_hex("2a"))},
{2, to_bytes32(*from_hex("2a"))},
Expand All @@ -568,6 +568,53 @@ TEST_CASE("Tracing smart contract with storage") {
CHECK(tracer3.result().data.empty());
}

TEST_CASE("Tracing creation smart contract with CREATE2") {
Block block{};
block.header.number = 10'336'006;
evmc::address caller{0x0a6bb546b9208cfab9e8fa2b9b2c042b18df7030_address};

Bytes code{*from_hex(
"6080604052348015600f57600080fd5b506000801b604051601e906043565b81"
"90604051809103906000f5905080158015603d573d6000803e3d6000fd5b5050"
"604f565b605c8061009c83390190565b603f8061005d6000396000f3fe608060"
"4052600080fdfea2646970667358221220ffaf2d6fdd061c3273248388b99d0e"
"48f13466b078ba552718eb14d618127f5f64736f6c6343000813003360806040"
"52348015600f57600080fd5b50603f80601d6000396000f3fe60806040526000"
"80fdfea2646970667358221220ea2cccbd9b69291ff50e3244e6b74392bb58de"
"7268abedc75e862628e939d32e64736f6c63430008130033")};
// pragma solidity 0.8.19;
//
// contract Factory {
// constructor() {
// new TestContract{salt: 0}();
// }
// }
// contract TestContract {
// constructor() {}
// }

InMemoryState db;
IntraBlockState state{db};
EVM evm{block, state, kMainnetConfig};

Transaction txn{};
txn.from = caller;
txn.data = code;

TestTracer tracer;
evm.add_tracer(tracer);
CHECK(evm.tracers().size() == 1);

uint64_t gas = {100'000};
CallResult res{evm.execute(txn, gas)};

CHECK(tracer.msg_stack().at(0).depth == 0);
CHECK(tracer.msg_stack().at(1).depth == 1);

CHECK(tracer.msg_stack().at(0).kind == evmc_call_kind::EVMC_CALL);
CHECK(tracer.msg_stack().at(1).kind == evmc_call_kind::EVMC_CREATE2);
}

TEST_CASE("Tracing smart contract w/o code") {
Block block{};
block.header.number = 10'336'006;
Expand Down
48 changes: 47 additions & 1 deletion silkworm/silkrpc/commands/ots_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -547,7 +547,7 @@ boost::asio::awaitable<void> OtsRpcApi::handle_ots_trace_transaction(const nlohm
boost::asio::awaitable<void> OtsRpcApi::handle_ots_get_transaction_error(const nlohmann::json& request, nlohmann::json& reply) {
const auto& params = request["params"];
if (params.size() != 1) {
const auto error_msg = "invalid ots_get_transaction_error params: " + params.dump();
const auto error_msg = "invalid ots_getTransactionError params: " + params.dump();
SILK_ERROR << error_msg << "\n";
reply = make_json_error(request["id"], 100, error_msg);
co_return;
Expand Down Expand Up @@ -590,6 +590,52 @@ boost::asio::awaitable<void> OtsRpcApi::handle_ots_get_transaction_error(const n
co_return;
}

boost::asio::awaitable<void> OtsRpcApi::handle_ots_get_internal_operations(const nlohmann::json& request, nlohmann::json& reply) {
const auto& params = request["params"];
if (params.size() != 1) {
const auto error_msg = "invalid ots_getInternalOperations params: " + params.dump();
SILK_ERROR << error_msg << "\n";
reply = make_json_error(request["id"], 100, error_msg);
co_return;
}

const auto transaction_hash = params[0].get<evmc::bytes32>();

SILK_DEBUG << "transaction_hash: " << transaction_hash;

auto tx = co_await database_->begin();

try {
ethdb::TransactionDatabase tx_database{*tx};
trace::TraceCallExecutor executor{*block_cache_, tx_database, workers_, *tx};

const auto transaction_with_block = co_await core::read_transaction_by_hash(*block_cache_, tx_database, transaction_hash);

if (!transaction_with_block.has_value()) {
reply = make_json_content(request["id"], nlohmann::detail::value_t::null);
co_await tx->close();
co_return;
}

const auto result = co_await executor.trace_operations(transaction_with_block.value());

reply = make_json_content(request["id"], result);

} catch (const std::invalid_argument& iv) {
SILK_WARN << "invalid_argument: " << iv.what() << " processing request: " << request.dump();
reply = make_json_content(request["id"], nlohmann::detail::value_t::null);
} catch (const std::exception& e) {
SILK_ERROR << "exception: " << e.what() << " processing request: " << request.dump();
reply = make_json_error(request["id"], 100, e.what());
} catch (...) {
SILK_ERROR << "unexpected exception processing request: " << request.dump();
reply = make_json_error(request["id"], 100, "unexpected exception");
}

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

IssuanceDetails OtsRpcApi::get_issuance(const ChainConfig& chain_config, const silkworm::BlockWithHash& block) {
auto config = silkworm::ChainConfig::from_json(chain_config.config).value();

Expand Down
1 change: 1 addition & 0 deletions silkworm/silkrpc/commands/ots_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class OtsRpcApi {
boost::asio::awaitable<void> handle_ots_get_contract_creator(const nlohmann::json& request, nlohmann::json& reply);
boost::asio::awaitable<void> handle_ots_trace_transaction(const nlohmann::json& request, nlohmann::json& reply);
boost::asio::awaitable<void> handle_ots_get_transaction_error(const nlohmann::json& request, nlohmann::json& reply);
boost::asio::awaitable<void> handle_ots_get_internal_operations(const nlohmann::json& request, nlohmann::json& reply);

boost::asio::io_context& io_context_;
boost::asio::thread_pool& workers_;
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 @@ -235,6 +235,7 @@ void RpcApiTable::add_ots_handlers() {
method_handlers_[http::method::k_ots_getContractCreator] = &commands::RpcApi::handle_ots_get_contract_creator;
method_handlers_[http::method::k_ots_traceTransaction] = &commands::RpcApi::handle_ots_trace_transaction;
method_handlers_[http::method::k_ots_getTransactionError] = &commands::RpcApi::handle_ots_get_transaction_error;
method_handlers_[http::method::k_ots_getInternalOperations] = &commands::RpcApi::handle_ots_get_internal_operations;
}

} // namespace silkworm::rpc::commands
74 changes: 73 additions & 1 deletion silkworm/silkrpc/core/evm_trace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,17 @@ void to_json(nlohmann::json& json, const TraceEntry& trace_entry) {
json["input"] = trace_entry.input;
}

void to_json(nlohmann::json& json, const InternalOperation& trace_operation) {
json["type"] = trace_operation.type;
json["from"] = trace_operation.from;
json["to"] = trace_operation.to;
if (trace_operation.value.empty()) {
json["value"] = nullptr;
} else {
json["value"] = trace_operation.value;
}
}

int get_stack_count(std::uint8_t op_code) {
int count;
switch (op_code) {
Expand Down Expand Up @@ -1498,6 +1509,26 @@ boost::asio::awaitable<std::string> TraceCallExecutor::trace_transaction_error(c
co_return ret_result;
}

boost::asio::awaitable<TraceOperationsResult> TraceCallExecutor::trace_operations(const TransactionWithBlock& transaction_with_block) {
auto block_number = transaction_with_block.block_with_hash.block.header.number;

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;
auto state = tx_.create_state(current_executor, database_reader_, block_number - 1);
silkworm::IntraBlockState initial_ibs{*state};

auto curr_state = tx_.create_state(current_executor, database_reader_, block_number - 1);
EVMExecutor executor{*chain_config_ptr, workers_, curr_state};
auto tracer = std::make_shared<trace::OperationTracer>(initial_ibs);
Tracers tracers{tracer};

auto execution_result = executor.call(transaction_with_block.block_with_hash.block, transaction_with_block.transaction, tracers, /*refund=*/true, /*gas_bailout=*/true);

co_return tracer->result();
}

awaitable<void> TraceCallExecutor::trace_filter(const TraceFilter& trace_filter, json::Stream* stream) {
SILK_INFO << "TraceCallExecutor::trace_filter: filter " << trace_filter;

Expand Down Expand Up @@ -1657,7 +1688,15 @@ void EntryTracer::on_execution_start(evmc_revision, const evmc_message& msg, evm
}

if (create) {
result_.push_back(TraceEntry{"CALL", msg.depth, sender, recipient, str_value, str_input});
if (msg.depth > 0) {
if (msg.kind == evmc_call_kind::EVMC_CREATE) {
result_.push_back(TraceEntry{"CREATE", msg.depth, sender, recipient, str_value, str_input});
} else if (msg.kind == evmc_call_kind::EVMC_CREATE2) {
result_.push_back(TraceEntry{"CREATE2", msg.depth, sender, recipient, str_value, str_input});
}
} else {
result_.push_back(TraceEntry{"CALL", msg.depth, sender, recipient, str_value, str_input});
}
} else {
bool in_static_mode = (msg.flags & evmc_flags::EVMC_STATIC) != 0;
switch (msg.kind) {
Expand Down Expand Up @@ -1688,4 +1727,37 @@ void EntryTracer::on_execution_start(evmc_revision, const evmc_message& msg, evm
<< ", msg.input_data: " << to_hex(ByteView{msg.input_data, msg.input_size});
}

void OperationTracer::on_execution_start(evmc_revision, const evmc_message& msg, evmone::bytes_view code) noexcept {
auto sender = evmc::address{msg.sender};
auto recipient = evmc::address{msg.recipient};
auto code_address = evmc::address{msg.code_address};

auto depth = msg.depth;
auto kind = msg.kind;

bool create = (!initial_ibs_.exists(recipient) && recipient != code_address);
auto str_value = "0x" + intx::hex(intx::be::load<intx::uint256>(msg.value));

if (create && msg.depth > 0) {
if (msg.kind == evmc_call_kind::EVMC_CREATE) {
result_.push_back(InternalOperation{OperationType::OP_CREATE, sender, recipient, str_value});
} else if (msg.kind == evmc_call_kind::EVMC_CREATE2) {
result_.push_back(InternalOperation{OperationType::OP_CREATE2, sender, recipient, str_value});
} else if (msg.kind == evmc_call_kind::EVMC_CALL && intx::be::load<intx::uint256>(msg.value) > 0) {
result_.push_back(InternalOperation{OperationType::OP_TRANSFER, sender, recipient, str_value});
}
}

SILK_DEBUG << "OperationTracer::on_execution_start: gas: " << std::dec << msg.gas
<< " create: " << create
<< ", msg.depth: " << depth
<< ", msg.kind: " << kind
<< ", sender: " << sender
<< ", recipient: " << recipient << " (created: " << create << ")"
<< ", code_address: " << code_address
<< ", msg.value: " << intx::hex(intx::be::load<intx::uint256>(msg.value))
<< ", code: " << silkworm::to_hex(code)
<< ", msg.input_data: " << to_hex(ByteView{msg.input_data, msg.input_size});
}

} // namespace silkworm::rpc::trace
Loading

0 comments on commit 8ec57da

Please sign in to comment.