Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rpcdaemon: generate transaction receipts on-the-fly #2375

Merged
merged 13 commits into from
Oct 2, 2024
4 changes: 2 additions & 2 deletions silkworm/rpc/commands/erigon_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ Task<void> ErigonRpcApi::handle_erigon_get_block_receipts_by_block_hash(const nl
co_await tx->close(); // RAII not (yet) available with coroutines
co_return;
}
auto receipts{co_await core::get_receipts(*tx, *block_with_hash)};
auto receipts{co_await core::get_receipts(*tx, *block_with_hash, *chain_storage, workers_)};
SILK_TRACE << "#receipts: " << receipts.size();

const auto block{block_with_hash->block};
Expand Down Expand Up @@ -414,7 +414,7 @@ Task<void> ErigonRpcApi::handle_erigon_get_logs_by_hash(const nlohmann::json& re
co_await tx->close(); // RAII not (yet) available with coroutines
co_return;
}
const auto receipts{co_await core::get_receipts(*tx, *block_with_hash)};
const auto receipts{co_await core::get_receipts(*tx, *block_with_hash, *chain_storage, workers_)};
SILK_DEBUG << "receipts.size(): " << receipts.size();
std::vector<Logs> logs{};
logs.reserve(receipts.size());
Expand Down
7 changes: 5 additions & 2 deletions silkworm/rpc/commands/erigon_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include <silkworm/db/kv/api/state_cache.hpp>
#include <silkworm/infra/concurrency/private_service.hpp>
#include <silkworm/infra/concurrency/shared_service.hpp>
#include <silkworm/rpc/common/worker_pool.hpp>
#include <silkworm/rpc/ethbackend/backend.hpp>
#include <silkworm/rpc/ethdb/database.hpp>
#include <silkworm/rpc/json/types.hpp>
Expand All @@ -37,10 +38,11 @@ namespace silkworm::rpc::commands {

class ErigonRpcApi {
public:
explicit ErigonRpcApi(boost::asio::io_context& io_context)
explicit ErigonRpcApi(boost::asio::io_context& io_context, WorkerPool& workers)
: block_cache_{must_use_shared_service<BlockCache>(io_context)},
database_{must_use_private_service<ethdb::Database>(io_context)},
backend_{must_use_private_service<ethbackend::BackEnd>(io_context)} {}
backend_{must_use_private_service<ethbackend::BackEnd>(io_context)},
workers_{workers} {}
virtual ~ErigonRpcApi() = default;

ErigonRpcApi(const ErigonRpcApi&) = delete;
Expand All @@ -66,6 +68,7 @@ class ErigonRpcApi {
BlockCache* block_cache_;
ethdb::Database* database_;
ethbackend::BackEnd* backend_;
WorkerPool& workers_;

friend class silkworm::rpc::json_rpc::RequestHandler;
};
Expand Down
4 changes: 2 additions & 2 deletions silkworm/rpc/commands/erigon_api_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ namespace silkworm::rpc::commands {
//! Utility class to expose handle hooks publicly just for tests
class ErigonRpcApiForTest : public ErigonRpcApi {
public:
explicit ErigonRpcApiForTest(boost::asio::io_context& io_context) : ErigonRpcApi{io_context} {}
explicit ErigonRpcApiForTest(boost::asio::io_context& io_context, WorkerPool& workers) : ErigonRpcApi{io_context, workers} {}

// MSVC doesn't support using access declarations properly, so explicitly forward these public accessors
Task<void> erigon_get_block_by_timestamp(const nlohmann::json& request, std::string& reply) {
Expand All @@ -52,7 +52,7 @@ class ErigonRpcApiForTest : public ErigonRpcApi {
}
};

using ErigonRpcApiTest = test_util::JsonApiTestBase<ErigonRpcApiForTest>;
using ErigonRpcApiTest = test_util::JsonApiWithWorkersTestBase<ErigonRpcApiForTest>;

#ifndef SILKWORM_SANITIZE
TEST_CASE_METHOD(ErigonRpcApiTest, "ErigonRpcApi::handle_erigon_get_block_by_timestamp", "[rpc][erigon_api]") {
Expand Down
6 changes: 3 additions & 3 deletions silkworm/rpc/commands/eth_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -831,7 +831,7 @@ Task<void> EthereumRpcApi::handle_eth_get_transaction_receipt(const nlohmann::js
co_await tx->close(); // RAII not (yet) available with coroutines
co_return;
}
auto receipts = co_await core::get_receipts(*tx, *block_with_hash);
auto receipts = co_await core::get_receipts(*tx, *block_with_hash, *chain_storage, workers_);
const auto& transactions = block_with_hash->block.transactions;
if (receipts.size() != transactions.size()) {
throw std::invalid_argument{"Unexpected size for receipts in handle_eth_get_transaction_receipt"};
Expand Down Expand Up @@ -2038,8 +2038,8 @@ Task<void> EthereumRpcApi::handle_fee_history(const nlohmann::json& request, nlo
rpc::fee_history::BlockProvider block_provider = [this, &chain_storage](BlockNum block_number) {
return core::read_block_by_number(*block_cache_, *chain_storage, block_number);
};
rpc::fee_history::ReceiptsProvider receipts_provider = [&tx](const BlockWithHash& block_with_hash) {
return core::get_receipts(*tx, block_with_hash);
rpc::fee_history::ReceiptsProvider receipts_provider = [&tx, &chain_storage, this](const BlockWithHash& block_with_hash) {
return core::get_receipts(*tx, block_with_hash, *chain_storage, this->workers_);
};

rpc::fee_history::LatestBlockProvider latest_block_provider = [&tx]() {
Expand Down
8 changes: 4 additions & 4 deletions silkworm/rpc/commands/ots_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ Task<void> OtsRpcApi::handle_ots_get_block_details(const nlohmann::json& request
const BlockDetails block_details{block_size, block_with_hash->hash, block_with_hash->block.header, *total_difficulty,
block_with_hash->block.transactions.size(), block_with_hash->block.ommers,
block_with_hash->block.withdrawals};
const auto receipts = co_await core::get_receipts(*tx, *block_with_hash);
const auto receipts = co_await core::get_receipts(*tx, *block_with_hash, *chain_storage, workers_);
const auto chain_config = co_await chain_storage->read_chain_config();
const IssuanceDetails issuance = get_issuance(chain_config, *block_with_hash);
const intx::uint256 total_fees = get_block_fees(*block_with_hash, receipts);
Expand Down Expand Up @@ -165,7 +165,7 @@ Task<void> OtsRpcApi::handle_ots_get_block_details_by_hash(const nlohmann::json&
const BlockDetails block_details{block_size, block_with_hash->hash, block_with_hash->block.header, *total_difficulty,
block_with_hash->block.transactions.size(), block_with_hash->block.ommers,
block_with_hash->block.withdrawals};
const auto receipts = co_await core::get_receipts(*tx, *block_with_hash);
const auto receipts = co_await core::get_receipts(*tx, *block_with_hash, *chain_storage, workers_);
const auto chain_config = co_await chain_storage->read_chain_config();
const IssuanceDetails issuance = get_issuance(chain_config, *block_with_hash);
const intx::uint256 total_fees = get_block_fees(*block_with_hash, receipts);
Expand Down Expand Up @@ -214,7 +214,7 @@ Task<void> OtsRpcApi::handle_ots_get_block_transactions(const nlohmann::json& re
const auto total_difficulty{co_await chain_storage->read_total_difficulty(block_with_hash->hash, block_number)};
ensure(total_difficulty.has_value(), [&]() { return "no total difficulty for block: " + std::to_string(block_number); });
const Block extended_block{block_with_hash, *total_difficulty, false};
auto receipts = co_await core::get_receipts(*tx, *block_with_hash);
auto receipts = co_await core::get_receipts(*tx, *block_with_hash, *chain_storage, workers_);
auto block_size = extended_block.get_block_size();
auto transaction_count = block_with_hash->block.transactions.size();

Expand Down Expand Up @@ -857,7 +857,7 @@ Task<void> OtsRpcApi::trace_block(db::kv::api::Transaction& tx, BlockNum block_n

const auto total_difficulty{co_await chain_storage->read_total_difficulty(block_with_hash->hash, block_number)};
ensure(total_difficulty.has_value(), [&]() { return "no total difficulty for block: " + std::to_string(block_number); });
const auto receipts = co_await core::get_receipts(tx, *block_with_hash);
const auto receipts = co_await core::get_receipts(tx, *block_with_hash, *chain_storage, workers_);
const Block extended_block{block_with_hash, *total_difficulty, false};

trace::TraceCallExecutor executor{*block_cache_, *chain_storage, workers_, tx};
Expand Down
2 changes: 1 addition & 1 deletion silkworm/rpc/commands/parity_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Task<void> ParityRpcApi::handle_parity_get_block_receipts(const nlohmann::json&
const auto block_number = co_await core::get_block_number(bnoh, *tx);
const auto block_with_hash = co_await core::read_block_by_number(*block_cache_, *chain_storage, block_number.first);
if (block_with_hash) {
auto receipts{co_await core::get_receipts(*tx, *block_with_hash)};
auto receipts{co_await core::get_receipts(*tx, *block_with_hash, *chain_storage, workers_)};
SILK_TRACE << "#receipts: " << receipts.size();

const auto block{block_with_hash->block};
Expand Down
7 changes: 5 additions & 2 deletions silkworm/rpc/commands/parity_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <silkworm/core/common/block_cache.hpp>
#include <silkworm/infra/concurrency/private_service.hpp>
#include <silkworm/infra/concurrency/shared_service.hpp>
#include <silkworm/rpc/common/worker_pool.hpp>
#include <silkworm/rpc/ethbackend/backend.hpp>
#include <silkworm/rpc/ethdb/database.hpp>
#include <silkworm/rpc/json/types.hpp>
Expand All @@ -36,10 +37,11 @@ namespace silkworm::rpc::commands {

class ParityRpcApi {
public:
explicit ParityRpcApi(boost::asio::io_context& io_context)
explicit ParityRpcApi(boost::asio::io_context& io_context, WorkerPool& workers)
: block_cache_{must_use_shared_service<BlockCache>(io_context)},
database_{must_use_private_service<ethdb::Database>(io_context)},
backend_{must_use_private_service<ethbackend::BackEnd>(io_context)} {}
backend_{must_use_private_service<ethbackend::BackEnd>(io_context)},
workers_{workers} {}
virtual ~ParityRpcApi() = default;

ParityRpcApi(const ParityRpcApi&) = delete;
Expand All @@ -54,6 +56,7 @@ class ParityRpcApi {
BlockCache* block_cache_;
ethdb::Database* database_;
ethbackend::BackEnd* backend_;
WorkerPool& workers_;

friend class silkworm::rpc::json_rpc::RequestHandler;
};
Expand Down
4 changes: 2 additions & 2 deletions silkworm/rpc/commands/rpc_api.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ class RpcApi : protected EthereumRpcApi,
AdminRpcApi{io_context},
Web3RpcApi{io_context},
DebugRpcApi{io_context, workers},
ParityRpcApi{io_context},
ErigonRpcApi{io_context},
ParityRpcApi{io_context, workers},
ErigonRpcApi{io_context, workers},
TraceRpcApi{io_context, workers},
EngineRpcApi(io_context, std::move(build_info)),
TxPoolRpcApi(io_context),
Expand Down
104 changes: 104 additions & 0 deletions silkworm/rpc/core/evm_executor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,110 @@ ExecutionResult EVMExecutor::call(
return exec_result;
}

ExecutionResult EVMExecutor::call_with_receipt(
const silkworm::Block& block,
const silkworm::Transaction& txn,
Receipt& receipt,
const Tracers& tracers,
bool refund,
bool gas_bailout) {
SILK_DEBUG << "EVMExecutor::call: blockNumber: " << block.header.number << " gas_limit: " << txn.gas_limit << " refund: " << refund
<< " gas_bailout: " << gas_bailout << " transaction: " << rpc::Transaction{txn};

auto& svc = use_service<AnalysisCacheService>(workers_);
EVM evm{block, ibs_state_, config_, gas_bailout};
evm.analysis_cache = svc.get_analysis_cache();
evm.state_pool = svc.get_object_pool();
evm.beneficiary = rule_set_->get_beneficiary(block.header);
evm.transfer = rule_set_->transfer_func();

for (auto& tracer : tracers) {
evm.add_tracer(*tracer);
}

if (!txn.sender()) {
return {std::nullopt, txn.gas_limit, Bytes{}, "malformed transaction: cannot recover sender"};
}
ibs_state_.access_account(*txn.sender());

const evmc_revision rev{evm.revision()};
const intx::uint256 base_fee_per_gas{evm.block().header.base_fee_per_gas.value_or(0)};
const intx::uint128 g0{protocol::intrinsic_gas(txn, rev)};
SILKWORM_ASSERT(g0 <= UINT64_MAX); // true due to the precondition (transaction must be valid)

if (const auto pre_check_result = pre_check(evm, txn, base_fee_per_gas, g0)) {
Bytes data{};
return {std::nullopt, txn.gas_limit, data, pre_check_result->pre_check_error, pre_check_result->pre_check_error_code};
}

if (const auto result = evm.deduct_entry_fees(txn); result.status != EVMC_SUCCESS) {
return {std::nullopt, txn.gas_limit, {}, result.error_message, PreCheckErrorCode::kInsufficientFunds};
}
if (txn.to.has_value()) {
ibs_state_.access_account(*txn.to);
// EVM itself increments the nonce for contract creation
ibs_state_.set_nonce(*txn.sender(), ibs_state_.get_nonce(*txn.sender()) + 1);
}
for (const AccessListEntry& ae : txn.access_list) {
ibs_state_.access_account(ae.account);
for (const evmc::bytes32& key : ae.storage_keys) {
ibs_state_.access_storage(ae.account, key);
}
}

CallResult result;
try {
SILK_DEBUG << "EVMExecutor::call execute on EVM txn: " << &txn << " g0: " << static_cast<uint64_t>(g0) << " start";
result = evm.execute(txn, txn.gas_limit - static_cast<uint64_t>(g0));
SILK_DEBUG << "EVMExecutor::call execute on EVM txn: " << &txn << " gas_left: " << result.gas_left << " end";
} catch (const std::exception& e) {
SILK_ERROR << "exception: evm_execute: " << e.what() << "\n";
std::string error_msg = "evm.execute: ";
error_msg.append(e.what());
return {std::nullopt, txn.gas_limit, /* data */ {}, error_msg};
} catch (...) {
SILK_ERROR << "exception: evm_execute: unexpected exception\n";
return {std::nullopt, txn.gas_limit, /* data */ {}, "evm.execute: unknown exception"};
}

uint64_t gas_left{result.gas_left};
uint64_t gas_used{txn.gas_limit - result.gas_left};

if (refund && !gas_bailout) {
gas_used = txn.gas_limit - refund_gas(evm, txn, result.gas_left, result.gas_refund);
gas_left = txn.gas_limit - gas_used;
}

// Reward the fee recipient
const intx::uint256 priority_fee_per_gas{txn.max_fee_per_gas >= base_fee_per_gas ? txn.priority_fee_per_gas(base_fee_per_gas)
: txn.max_priority_fee_per_gas};
SILK_DEBUG << "EVMExecutor::call evm.beneficiary: " << evm.beneficiary << " balance: " << priority_fee_per_gas * gas_used;
ibs_state_.add_to_balance(evm.beneficiary, priority_fee_per_gas * gas_used);

for (auto& tracer : evm.tracers()) {
tracer.get().on_reward_granted(result, ibs_state_);
}
ibs_state_.finalize_transaction(rev);

receipt.success = result.status == EVMC_SUCCESS;
receipt.bloom = logs_bloom(ibs_state_.logs());
receipt.gas_used = gas_used;
receipt.type = static_cast<uint8_t>(txn.type);
for (size_t j{0}; j < ibs_state_.logs().size(); j++) {
Log rpc_log;
rpc_log.address = ibs_state_.logs()[j].address;
rpc_log.data = ibs_state_.logs()[j].data;
rpc_log.topics = ibs_state_.logs()[j].topics;
receipt.logs.push_back(rpc_log);
}

ExecutionResult exec_result{result.status, gas_left, result.data};

SILK_DEBUG << "EVMExecutor::call call_result: " << exec_result.error_message() << " #data: " << exec_result.data.size() << " end";

return exec_result;
}

Task<ExecutionResult> EVMExecutor::call(
const silkworm::ChainConfig& config,
const ChainStorage& chain_storage,
Expand Down
10 changes: 10 additions & 0 deletions silkworm/rpc/core/evm_executor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,12 @@
#include <silkworm/core/protocol/rule_set.hpp>
#include <silkworm/core/state/state.hpp>
#include <silkworm/core/types/block.hpp>
#include <silkworm/core/types/receipt.hpp>
#include <silkworm/core/types/transaction.hpp>
#include <silkworm/db/chain/chain_storage.hpp>
#include <silkworm/db/state/state_reader.hpp>
#include <silkworm/rpc/common/worker_pool.hpp>
#include <silkworm/rpc/types/receipt.hpp>

namespace silkworm::rpc {

Expand Down Expand Up @@ -125,6 +127,14 @@ class EVMExecutor {
bool refund = true,
bool gas_bailout = false);

ExecutionResult call_with_receipt(
const silkworm::Block& block,
const silkworm::Transaction& txn,
Receipt& receipt,
const Tracers& tracers = {},
bool refund = true,
bool gas_bailout = false);

void reset();

void call_first_n(const silkworm::Block& block, uint64_t n, const Tracers& tracers = {}, bool refund = true, bool gas_bailout = false);
Expand Down
Loading
Loading