From a297b075498715e2a5658520031d184674c245bd Mon Sep 17 00:00:00 2001 From: lupin012 <58134934+lupin012@users.noreply.github.com> Date: Tue, 11 Mar 2025 18:00:35 +0100 Subject: [PATCH 1/3] align estimategas impl to erigon --- silkworm/core/execution/evm.cpp | 2 +- silkworm/core/execution/evm.hpp | 1 + silkworm/core/execution/evm_test.cpp | 2 +- silkworm/core/execution/processor.cpp | 6 +- silkworm/rpc/commands/eth_api.cpp | 42 +++-- silkworm/rpc/core/estimate_gas_oracle.cpp | 68 ++++---- silkworm/rpc/core/estimate_gas_oracle.hpp | 2 +- .../rpc/core/estimate_gas_oracle_test.cpp | 165 ++++++++---------- silkworm/rpc/core/evm_executor.cpp | 18 +- silkworm/rpc/core/evm_executor.hpp | 2 + 10 files changed, 158 insertions(+), 150 deletions(-) diff --git a/silkworm/core/execution/evm.cpp b/silkworm/core/execution/evm.cpp index 1f312d8f39..ad2d2a8734 100644 --- a/silkworm/core/execution/evm.cpp +++ b/silkworm/core/execution/evm.cpp @@ -98,7 +98,7 @@ CallResult EVM::execute(const Transaction& txn, uint64_t gas) noexcept { const auto gas_left = static_cast<uint64_t>(res.gas_left); const auto gas_refund = static_cast<uint64_t>(res.gas_refund); - return {ValidationResult::kOk, res.status_code, gas_left, gas_refund, {res.output_data, res.output_size}}; + return {ValidationResult::kOk, res.status_code, gas_left, gas_refund, std::nullopt, {res.output_data, res.output_size}}; } evmc::Result EVM::create(const evmc_message& message) noexcept { diff --git a/silkworm/core/execution/evm.hpp b/silkworm/core/execution/evm.hpp index 2f3583de41..54a96182ea 100644 --- a/silkworm/core/execution/evm.hpp +++ b/silkworm/core/execution/evm.hpp @@ -44,6 +44,7 @@ struct CallResult { evmc_status_code status{EVMC_SUCCESS}; uint64_t gas_left{0}; uint64_t gas_refund{0}; + std::optional<uint64_t> gas_used{0}; Bytes data; std::string error_message; }; diff --git a/silkworm/core/execution/evm_test.cpp b/silkworm/core/execution/evm_test.cpp index 1a170c7aab..11abcb588f 100644 --- a/silkworm/core/execution/evm_test.cpp +++ b/silkworm/core/execution/evm_test.cpp @@ -569,7 +569,7 @@ class TestTracer : public EvmTracer { execution_end_called_ = true; const auto gas_left = static_cast<uint64_t>(res.gas_left); const auto gas_refund = static_cast<uint64_t>(res.gas_refund); - result_ = {ValidationResult::kOk, res.status_code, gas_left, gas_refund, {res.output_data, res.output_size}}; + result_ = {ValidationResult::kOk, res.status_code, gas_left, gas_refund, std::nullopt, {res.output_data, res.output_size}}; if (contract_address_ && !pc_stack_.empty()) { const auto pc = pc_stack_.back(); storage_stack_[pc] = diff --git a/silkworm/core/execution/processor.cpp b/silkworm/core/execution/processor.cpp index 053bc488b7..234bafd419 100644 --- a/silkworm/core/execution/processor.cpp +++ b/silkworm/core/execution/processor.cpp @@ -303,8 +303,10 @@ CallResult ExecutionProcessor::call(const Transaction& txn, const std::vector<st uint64_t gas_left{result.gas_left}; uint64_t gas_used{txn.gas_limit - result.gas_left}; + uint64_t gas_refund = 0; if (refund && !evm().bailout) { - gas_used = txn.gas_limit - refund_gas(txn, effective_gas_price, result.gas_left, result.gas_refund); + gas_refund = refund_gas(txn, effective_gas_price, result.gas_left, result.gas_refund) - result.gas_left; + gas_used = txn.gas_limit - (result.gas_left + gas_refund); gas_left = txn.gas_limit - gas_used; } @@ -321,7 +323,7 @@ CallResult ExecutionProcessor::call(const Transaction& txn, const std::vector<st evm_.remove_tracers(); - return {ValidationResult::kOk, result.status, gas_left, gas_used, result.data, result.error_message}; + return {ValidationResult::kOk, result.status, gas_left, gas_refund, gas_used, result.data, result.error_message}; } void ExecutionProcessor::reset() { diff --git a/silkworm/rpc/commands/eth_api.cpp b/silkworm/rpc/commands/eth_api.cpp index ad27098acd..1be948e4a4 100644 --- a/silkworm/rpc/commands/eth_api.cpp +++ b/silkworm/rpc/commands/eth_api.cpp @@ -886,11 +886,12 @@ Task<void> EthereumRpcApi::handle_eth_estimate_gas(const nlohmann::json& request const auto chain_storage{tx->create_storage()}; rpc::BlockReader block_reader{*chain_storage, *tx}; - std::optional<BlockNum> block_num_for_gas_limit; + std::string block_id; if (params.size() >= 2) { - const auto block_id = params[1].get<std::string>(); + block_id = params[1].get<std::string>(); SILK_DEBUG << "block_id: " << block_id; - block_num_for_gas_limit = co_await block_reader.get_block_num(block_id); + } else { + block_id = kLatestBlockId; } AccountsOverrides accounts_overrides; @@ -902,19 +903,18 @@ Task<void> EthereumRpcApi::handle_eth_estimate_gas(const nlohmann::json& request try { tx->set_state_cache_enabled(/*cache_enabled=*/true); // always at latest block - const BlockNumOrHash block_num_or_hash{kLatestBlockId}; - const auto chain_config = co_await chain_storage->read_chain_config(); - const auto latest_block_num = co_await block_reader.get_block_num(kLatestBlockId); - SILK_DEBUG << "chain_id: " << chain_config.chain_id << ", latest_block_num: " << latest_block_num; + const auto req_block_num = co_await block_reader.get_block_num(block_id); + SILK_DEBUG << "chain_id: " << chain_config.chain_id << ", block_num: " << req_block_num; - const auto latest_block_with_hash = co_await core::read_block_by_number(*block_cache_, *chain_storage, latest_block_num); - if (!latest_block_with_hash) { - reply = make_json_error(request, 100, "block not found"); + const auto block_with_hash = co_await core::read_block_by_number(*block_cache_, *chain_storage, req_block_num); + if (!block_with_hash) { + std::string error_msg = "could not find the block " + std::to_string(req_block_num) + " in cache or db"; + reply = make_json_error(request, -32000, error_msg); co_await tx->close(); // RAII not (yet) available with coroutines co_return; } - const auto latest_block = latest_block_with_hash->block; + const auto block = block_with_hash->block; rpc::BlockHeaderProvider block_header_provider = [&chain_storage](BlockNum block_num) { return chain_storage->read_canonical_header(block_num); @@ -926,7 +926,7 @@ Task<void> EthereumRpcApi::handle_eth_estimate_gas(const nlohmann::json& request }; rpc::EstimateGasOracle estimate_gas_oracle{block_header_provider, account_reader, chain_config, workers_, *tx, *chain_storage, accounts_overrides}; - const auto estimated_gas = co_await estimate_gas_oracle.estimate_gas(call, latest_block, std::nullopt /*latest block */, block_num_for_gas_limit); + const auto estimated_gas = co_await estimate_gas_oracle.estimate_gas(call, block, std::nullopt /*latest block */); reply = make_json_content(request, to_quantity(estimated_gas)); } catch (const std::invalid_argument&) { @@ -1138,8 +1138,19 @@ Task<void> EthereumRpcApi::handle_eth_get_storage_at(const nlohmann::json& reque co_await tx->close(); // RAII not (yet) available with coroutines } +//! The current supported version of the Otterscan API +static constexpr int kCurrentApiLevel{8}; + +bool first_time = true; +silkworm::ChainConfig global_chain_config; + // https://eth.wiki/json-rpc/API#eth_call Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::string& reply) { +#ifdef notdef + make_glaze_json_null_content(request, reply); + co_return; +#endif + if (!request.contains("params")) { auto error_msg = "missing value for required argument 0"; SILK_ERROR << error_msg << request.dump(); @@ -1171,7 +1182,10 @@ Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::s try { const auto chain_storage{tx->create_storage()}; rpc::BlockReader block_reader{*chain_storage, *tx}; - const auto chain_config = co_await chain_storage->read_chain_config(); + if (first_time) { + global_chain_config = co_await chain_storage->read_chain_config(); + first_time = false; + } const auto [block_num, is_latest_block] = co_await block_reader.get_block_num(block_id, /*latest_required=*/true); tx->set_state_cache_enabled(/*cache_enabled=*/is_latest_block); @@ -1191,7 +1205,7 @@ Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::s } const auto execution_result = co_await EVMExecutor::call( - chain_config, *chain_storage, workers_, block_with_hash->block, txn, txn_id, [&state_factory](auto& io_executor, std::optional<TxnId> curr_txn_id, auto& storage) { + global_chain_config, *chain_storage, workers_, block_with_hash->block, txn, txn_id, [&state_factory](auto& io_executor, std::optional<TxnId> curr_txn_id, auto& storage) { return state_factory.create_state(io_executor, storage, curr_txn_id); }, /* tracers */ {}, /* refund */ true, /* gas_bailout */ false, accounts_overrides); diff --git a/silkworm/rpc/core/estimate_gas_oracle.cpp b/silkworm/rpc/core/estimate_gas_oracle.cpp index 9b03378e53..73ceafeebd 100644 --- a/silkworm/rpc/core/estimate_gas_oracle.cpp +++ b/silkworm/rpc/core/estimate_gas_oracle.cpp @@ -26,19 +26,19 @@ namespace silkworm::rpc { -Task<intx::uint256> EstimateGasOracle::estimate_gas(const Call& call, const silkworm::Block& block, std::optional<TxnId> txn_id, std::optional<BlockNum> block_num_for_gas_limit) { +Task<intx::uint256> EstimateGasOracle::estimate_gas(const Call& call, const silkworm::Block& block, std::optional<TxnId> txn_id) { SILK_DEBUG << "EstimateGasOracle::estimate_gas called"; const auto block_num = block.header.number; uint64_t hi = 0; - uint64_t lo = kTxGas - 1; + uint64_t lo; if (call.gas.value_or(0) >= kTxGas) { SILK_DEBUG << "Set gas limit using call args: " << call.gas.value_or(0); hi = call.gas.value(); } else { - const auto header = co_await block_header_provider_(block_num_for_gas_limit.value_or(block_num)); + const auto header = co_await block_header_provider_(block_num); if (!header) { co_return 0; } @@ -46,6 +46,11 @@ Task<intx::uint256> EstimateGasOracle::estimate_gas(const Call& call, const silk SILK_DEBUG << "Set gas limit using block: " << header->gas_limit; } + if (hi > kGasCap) { + SILK_WARN << "caller gas above allowance, capping: requested " << hi << ", cap " << kGasCap; + hi = kGasCap; + } + std::optional<intx::uint256> gas_price = call.gas_price; if (gas_price && gas_price != 0) { evmc::address from = call.from.value_or(evmc::address{0}); @@ -71,14 +76,6 @@ Task<intx::uint256> EstimateGasOracle::estimate_gas(const Call& call, const silk } } - if (hi > kGasCap) { - SILK_WARN << "caller gas above allowance, capping: requested " << hi << ", cap " << kGasCap; - hi = kGasCap; - } - auto cap = hi; - - SILK_DEBUG << "hi: " << hi << ", lo: " << lo << ", cap: " << cap; - auto this_executor = co_await boost::asio::this_coro::executor; execution::StateFactory state_factory{transaction_}; @@ -88,33 +85,40 @@ Task<intx::uint256> EstimateGasOracle::estimate_gas(const Call& call, const silk ExecutionResult result{evmc_status_code::EVMC_SUCCESS}; silkworm::Transaction transaction{call.to_transaction()}; + + auto state_overrides = std::make_shared<state::OverrideState>(*state, accounts_overrides_); + EVMExecutor executor{block, config_, workers_, state_overrides}; + transaction.gas_limit = hi; + result = try_execution(executor, transaction); + if (!result.success()) { + return result; + } + + // Assuming a contract can freely run all the instructions, we have + // the true amount of gas it wants to consume to execute fully. + // We want to ensure that the gas used doesn't fall below this + auto true_gas = result.gas_used.value_or(0); + uint64_t refund = result.gas_refund.value_or(0); + lo = std::min(true_gas + refund - 1, kTxGas - 1); + + SILK_DEBUG << "hi: " << hi << ", lo: " << lo; + while (lo + 1 < hi) { - auto state_overrides = std::make_shared<state::OverrideState>(*state, accounts_overrides_); - EVMExecutor executor{block, config_, workers_, state_overrides}; + state_overrides = std::make_shared<state::OverrideState>(*state, accounts_overrides_); + EVMExecutor curr_executor{block, config_, workers_, state_overrides}; auto mid = (hi + lo) / 2; transaction.gas_limit = mid; - result = try_execution(executor, transaction); - if (result.success()) { - hi = mid; - } else { - if (result.pre_check_error_code && result.pre_check_error_code != PreCheckErrorCode::kIntrinsicGasTooLow) { - result.status_code = evmc_status_code::EVMC_SUCCESS; - return result; - } + result = try_execution(curr_executor, transaction); + if (result.pre_check_error_code) { + break; + } + if (!result.success() || result.gas_used.value_or(0) < true_gas) { lo = mid; + } else { + hi = mid; } } - - if (hi == cap) { - auto state_overrides = std::make_shared<state::OverrideState>(*state, accounts_overrides_); - EVMExecutor executor{block, config_, workers_, state_overrides}; - transaction.gas_limit = hi; - result = try_execution(executor, transaction); - SILK_DEBUG << "HI == cap tested again with " << (result.status_code == evmc_status_code::EVMC_SUCCESS ? "succeed" : "failed"); - } else if (!result.pre_check_error_code || result.pre_check_error_code == PreCheckErrorCode::kIntrinsicGasTooLow) { - result.pre_check_error = std::nullopt; - result.status_code = evmc_status_code::EVMC_SUCCESS; - } + result.status_code = evmc_status_code::EVMC_SUCCESS; SILK_DEBUG << "EstimateGasOracle::estimate_gas returns " << hi; diff --git a/silkworm/rpc/core/estimate_gas_oracle.hpp b/silkworm/rpc/core/estimate_gas_oracle.hpp index 43eb7eeda4..23c5d70483 100644 --- a/silkworm/rpc/core/estimate_gas_oracle.hpp +++ b/silkworm/rpc/core/estimate_gas_oracle.hpp @@ -92,7 +92,7 @@ class EstimateGasOracle { EstimateGasOracle(const EstimateGasOracle&) = delete; EstimateGasOracle& operator=(const EstimateGasOracle&) = delete; - Task<intx::uint256> estimate_gas(const Call& call, const silkworm::Block& latest_block, std::optional<TxnId> txn_id, std::optional<BlockNum> block_num_for_gas_limit = {}); + Task<intx::uint256> estimate_gas(const Call& call, const silkworm::Block& latest_block, std::optional<TxnId> txn_id); protected: virtual ExecutionResult try_execution(EVMExecutor& executor, const silkworm::Transaction& transaction); diff --git a/silkworm/rpc/core/estimate_gas_oracle_test.cpp b/silkworm/rpc/core/estimate_gas_oracle_test.cpp index 072b149e06..d7e2a5874b 100644 --- a/silkworm/rpc/core/estimate_gas_oracle_test.cpp +++ b/silkworm/rpc/core/estimate_gas_oracle_test.cpp @@ -102,12 +102,12 @@ TEST_CASE("estimate gas") { AccountsOverrides accounts_overrides; MockEstimateGasOracle estimate_gas_oracle{block_header_provider, account_reader, config, workers, *tx, storage, accounts_overrides}; - SECTION("Call empty, always fails but success in last step") { + SECTION("Call empty, always fails but success in first step") { ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS}; ExecutionResult expect_result_fail{.status_code = evmc_status_code::EVMC_OUT_OF_GAS}; EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) .Times(16) - .WillOnce(Return(expect_result_fail)) + .WillOnce(Return(expect_result_ok)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) @@ -126,47 +126,41 @@ TEST_CASE("estimate gas") { auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); const intx::uint256& estimate_gas = result.get(); - CHECK(estimate_gas == kTxGas * 2); + // CHECK(estimate_gas == kTxGas * 2); + CHECK(estimate_gas == 0xa40f); + // 0xa40f == 42000 (0xa410) } SECTION("Call empty, always succeeds") { ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS}; - EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(14).WillRepeatedly(Return(expect_result_ok)); + EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(15).WillRepeatedly(Return(expect_result_ok)); auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); const intx::uint256& estimate_gas = result.get(); CHECK(estimate_gas == kTxGas); } - SECTION("Call empty, alternatively fails and succeeds") { + SECTION("Call empty, fails first call") { ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS}; ExecutionResult expect_result_fail{.status_code = evmc_status_code::EVMC_OUT_OF_GAS}; - EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) - .Times(14) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_ok)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_ok)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_ok)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_ok)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_ok)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_ok)) - .WillOnce(Return(expect_result_fail)) - .WillRepeatedly(Return(expect_result_ok)); - auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); - const intx::uint256& estimate_gas = result.get(); - - CHECK(estimate_gas == 0x88b6); + try { + EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(1).WillOnce(Return(expect_result_fail)); + auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); + result.get(); + CHECK(false); + } catch (const silkworm::rpc::EstimateGasException&) { + CHECK(true); + } catch (const std::exception&) { + CHECK(false); + } catch (...) { + CHECK(false); + } } SECTION("Call empty, alternatively succeeds and fails") { ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS}; ExecutionResult expect_result_fail{.status_code = evmc_status_code::EVMC_OUT_OF_GAS}; EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) - .Times(14) + .Times(16) .WillOnce(Return(expect_result_ok)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_ok)) @@ -184,7 +178,7 @@ TEST_CASE("estimate gas") { auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); const intx::uint256& estimate_gas = result.get(); - CHECK(estimate_gas == 0x6d5e); + CHECK(estimate_gas == 0x88b8); } SECTION("Call empty, alternatively succeeds and fails with intrinsic") { @@ -193,35 +187,30 @@ TEST_CASE("estimate gas") { .pre_check_error = "intrinsic ", .pre_check_error_code = PreCheckErrorCode::kIntrinsicGasTooLow}; ExecutionResult expect_result_fail{.status_code = evmc_status_code::EVMC_OUT_OF_GAS}; - EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) - .Times(14) - .WillOnce(Return(expect_result_ok)) - .WillOnce(Return(expect_result_fail_pre_check)) - .WillOnce(Return(expect_result_ok)) - .WillOnce(Return(expect_result_fail_pre_check)) - .WillOnce(Return(expect_result_ok)) - .WillOnce(Return(expect_result_fail_pre_check)) - .WillOnce(Return(expect_result_ok)) - .WillOnce(Return(expect_result_fail_pre_check)) - .WillOnce(Return(expect_result_ok)) - .WillOnce(Return(expect_result_fail_pre_check)) - .WillOnce(Return(expect_result_ok)) - .WillOnce(Return(expect_result_fail_pre_check)) - .WillOnce(Return(expect_result_ok)) - .WillRepeatedly(Return(expect_result_fail)); - auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); - const intx::uint256& estimate_gas = result.get(); - - CHECK(estimate_gas == 0x6d5e); + try { + EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) + .Times(2) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_fail_pre_check)); + auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); + result.get(); + CHECK(false); + } catch (const silkworm::rpc::EstimateGasException&) { + CHECK(true); + } catch (const std::exception&) { + CHECK(false); + } catch (...) { + CHECK(false); + } } - SECTION("Call with gas, always fails but succes last step") { + SECTION("Call with gas, always fails but succes first and last step") { call.gas = kTxGas * 4; ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS}; ExecutionResult expect_result_fail{.status_code = evmc_status_code::EVMC_OUT_OF_GAS}; EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) .Times(17) - .WillOnce(Return(expect_result_fail)) + .WillOnce(Return(expect_result_ok)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) @@ -241,14 +230,14 @@ TEST_CASE("estimate gas") { auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); const intx::uint256& estimate_gas = result.get(); - CHECK(estimate_gas == kTxGas * 4); + CHECK(estimate_gas == 0x1481f); } SECTION("Call with gas, always succeeds") { call.gas = kTxGas * 4; ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS}; EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) - .Times(15) + .Times(16) .WillRepeatedly(Return(expect_result_ok)); auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); const intx::uint256& estimate_gas = result.get(); @@ -263,27 +252,26 @@ TEST_CASE("estimate gas") { call.gas_price = intx::uint256{10'000}; EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) - .Times(16) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) + .Times(15) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) - .WillRepeatedly(Return(expect_result_ok)); + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)); auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); const intx::uint256& estimate_gas = result.get(); - CHECK(estimate_gas == kTxGas * 2); + CHECK(estimate_gas == 0x546f); } SECTION("Call with gas_price, gas capped") { @@ -294,23 +282,23 @@ TEST_CASE("estimate gas") { EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) .Times(13) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) .WillRepeatedly(Return(expect_result_ok)); auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); const intx::uint256& estimate_gas = result.get(); - CHECK(estimate_gas == 0x61a8); + CHECK(estimate_gas == 0x5847); } SECTION("Call with gas_price and value, gas not capped") { @@ -322,8 +310,8 @@ TEST_CASE("estimate gas") { EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) .Times(16) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) @@ -341,7 +329,7 @@ TEST_CASE("estimate gas") { auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); const intx::uint256& estimate_gas = result.get(); - CHECK(estimate_gas == kTxGas * 2); + CHECK(estimate_gas == 0x7b0a); } SECTION("Call with gas_price and value, gas capped") { @@ -353,8 +341,8 @@ TEST_CASE("estimate gas") { EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) .Times(13) - .WillOnce(Return(expect_result_fail)) - .WillOnce(Return(expect_result_fail)) + .WillOnce(Return(expect_result_ok)) + .WillOnce(Return(expect_result_ok)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) .WillOnce(Return(expect_result_fail)) @@ -369,13 +357,13 @@ TEST_CASE("estimate gas") { auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); const intx::uint256& estimate_gas = result.get(); - CHECK(estimate_gas == 0x61a8); + CHECK(estimate_gas == 0x59d6); } SECTION("Call gas above allowance, always succeeds, gas capped") { ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS}; call.gas = kGasCap * 2; - EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(25).WillRepeatedly(Return(expect_result_ok)); + EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(26).WillRepeatedly(Return(expect_result_ok)); auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); const intx::uint256& estimate_gas = result.get(); @@ -386,7 +374,7 @@ TEST_CASE("estimate gas") { ExecutionResult expect_result_ok{.status_code = evmc_status_code::EVMC_SUCCESS}; call.gas = kTxGas / 2; - EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(14).WillRepeatedly(Return(expect_result_ok)); + EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(15).WillRepeatedly(Return(expect_result_ok)); auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); const intx::uint256& estimate_gas = result.get(); @@ -398,7 +386,7 @@ TEST_CASE("estimate gas") { call.value = intx::uint256{2'000'000'000}; try { - EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(16).WillRepeatedly(Return(expect_result_fail)); + EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)).Times(1).WillRepeatedly(Return(expect_result_fail)); auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); result.get(); CHECK(false); @@ -422,9 +410,8 @@ TEST_CASE("estimate gas") { try { EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) - .Times(2) - .WillOnce(Return(expect_result_fail)) - .WillRepeatedly(Return(expect_result_fail_pre_check)); + .Times(1) + .WillOnce(Return(expect_result_fail)); auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); result.get(); CHECK(false); @@ -449,9 +436,8 @@ TEST_CASE("estimate gas") { try { EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) - .Times(2) - .WillOnce(Return(expect_result_fail)) - .WillRepeatedly(Return(expect_result_fail_pre_check)); + .Times(1) + .WillOnce(Return(expect_result_fail)); auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); result.get(); CHECK(false); @@ -475,9 +461,8 @@ TEST_CASE("estimate gas") { try { EXPECT_CALL(estimate_gas_oracle, try_execution(_, _)) - .Times(2) - .WillOnce(Return(expect_result_fail)) - .WillRepeatedly(Return(expect_result_fail_pre_check)); + .Times(1) + .WillOnce(Return(expect_result_fail)); auto result = boost::asio::co_spawn(pool, estimate_gas_oracle.estimate_gas(call, block, 244087591818874), boost::asio::use_future); result.get(); CHECK(false); diff --git a/silkworm/rpc/core/evm_executor.cpp b/silkworm/rpc/core/evm_executor.cpp index 08b60d96d2..4e54bf67b2 100644 --- a/silkworm/rpc/core/evm_executor.cpp +++ b/silkworm/rpc/core/evm_executor.cpp @@ -176,25 +176,25 @@ ExecutionResult EVMExecutor::convert_validation_result(const ValidationResult& r case ValidationResult::kMaxPriorityFeeGreaterThanMax: { std::string error = "tip higher than fee cap: address " + from + ", tip: " + intx::to_string(txn.max_priority_fee_per_gas) + " gasFeeCap: " + intx::to_string(txn.max_fee_per_gas); - return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kTipHigherThanFeeCap}; + return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kTipHigherThanFeeCap}; } case ValidationResult::kMaxFeeLessThanBase: { std::string error = "fee cap less than block base fee: address " + from + ", gasFeeCap: " + intx::to_string(txn.max_fee_per_gas) + " baseFee: " + intx::to_string(*block.header.base_fee_per_gas); - return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kFeeCapLessThanBlockFeePerGas}; + return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kFeeCapLessThanBlockFeePerGas}; } case ValidationResult::kIntrinsicGas: { const intx::uint128 g0{protocol::intrinsic_gas(txn, evm.revision())}; std::string error = "intrinsic gas too low: have " + std::to_string(txn.gas_limit) + ", want " + intx::to_string(g0); - return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kIntrinsicGasTooLow}; + return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kIntrinsicGasTooLow}; } case ValidationResult::kWrongBlockGas: { std::string error = "internal failure: Cancun is active but ExcessBlobGas is nil"; - return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kInternalError}; + return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kInternalError}; } case ValidationResult::kUnsupportedTransactionType: { std::string error = "eip-1559 transactions require london"; - return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kIsNotLondon}; + return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kIsNotLondon}; } case ValidationResult::kInsufficientFunds: { auto owned_funds = execution_processor_.intra_block_state().get_balance(*txn.sender()); @@ -208,11 +208,11 @@ ExecutionResult EVMExecutor::convert_validation_result(const ValidationResult& r maximum_cost = txn.maximum_gas_cost(); } std::string error = "insufficient funds for gas * price + value: address " + from + " have " + intx::to_string(owned_funds) + " want " + intx::to_string(maximum_cost + txn.value); - return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kInsufficientFunds}; + return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kInsufficientFunds}; } default: { std::string error = "internal failure"; - return {std::nullopt, txn.gas_limit, {}, error, PreCheckErrorCode::kInternalError}; + return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, {}, error, PreCheckErrorCode::kInternalError}; } } } @@ -232,7 +232,7 @@ ExecutionResult EVMExecutor::call( evm.bailout = bailout; if (!txn.sender()) { - return {std::nullopt, txn.gas_limit, Bytes{}, "malformed transaction: cannot recover sender"}; + return {std::nullopt, txn.gas_limit, std::nullopt, std::nullopt, Bytes{}, "malformed transaction: cannot recover sender"}; } const auto result = execution_processor_.call(txn, tracers, refund); @@ -240,7 +240,7 @@ ExecutionResult EVMExecutor::call( return convert_validation_result(result.validation_result, txn); } - ExecutionResult exec_result{result.status, result.gas_left, result.data}; + ExecutionResult exec_result{result.status, result.gas_left, result.gas_refund, result.gas_used.value_or(0), result.data}; SILK_DEBUG << "EVMExecutor::call call_result: " << exec_result.error_message() << " #data: " << exec_result.data.size() << " end"; diff --git a/silkworm/rpc/core/evm_executor.hpp b/silkworm/rpc/core/evm_executor.hpp index 28fd61a5c2..4c345cd997 100644 --- a/silkworm/rpc/core/evm_executor.hpp +++ b/silkworm/rpc/core/evm_executor.hpp @@ -52,6 +52,8 @@ enum class PreCheckErrorCode { struct ExecutionResult { std::optional<evmc_status_code> status_code; uint64_t gas_left{0}; + std::optional<uint64_t> gas_refund; + std::optional<uint64_t> gas_used; Bytes data; std::optional<std::string> pre_check_error{std::nullopt}; std::optional<PreCheckErrorCode> pre_check_error_code{std::nullopt}; From 5ff5a0d1624037503a6b104beb86c724a65504c1 Mon Sep 17 00:00:00 2001 From: lupin012 <58134934+lupin012@users.noreply.github.com> Date: Tue, 11 Mar 2025 18:24:12 +0100 Subject: [PATCH 2/3] remove test code --- silkworm/rpc/commands/eth_api.cpp | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/silkworm/rpc/commands/eth_api.cpp b/silkworm/rpc/commands/eth_api.cpp index 1be948e4a4..ad5028e420 100644 --- a/silkworm/rpc/commands/eth_api.cpp +++ b/silkworm/rpc/commands/eth_api.cpp @@ -1138,19 +1138,8 @@ Task<void> EthereumRpcApi::handle_eth_get_storage_at(const nlohmann::json& reque co_await tx->close(); // RAII not (yet) available with coroutines } -//! The current supported version of the Otterscan API -static constexpr int kCurrentApiLevel{8}; - -bool first_time = true; -silkworm::ChainConfig global_chain_config; - // https://eth.wiki/json-rpc/API#eth_call Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::string& reply) { -#ifdef notdef - make_glaze_json_null_content(request, reply); - co_return; -#endif - if (!request.contains("params")) { auto error_msg = "missing value for required argument 0"; SILK_ERROR << error_msg << request.dump(); @@ -1182,10 +1171,7 @@ Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::s try { const auto chain_storage{tx->create_storage()}; rpc::BlockReader block_reader{*chain_storage, *tx}; - if (first_time) { - global_chain_config = co_await chain_storage->read_chain_config(); - first_time = false; - } + auto chain_config = co_await chain_storage->read_chain_config(); const auto [block_num, is_latest_block] = co_await block_reader.get_block_num(block_id, /*latest_required=*/true); tx->set_state_cache_enabled(/*cache_enabled=*/is_latest_block); @@ -1205,7 +1191,7 @@ Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::s } const auto execution_result = co_await EVMExecutor::call( - global_chain_config, *chain_storage, workers_, block_with_hash->block, txn, txn_id, [&state_factory](auto& io_executor, std::optional<TxnId> curr_txn_id, auto& storage) { + chain_config, *chain_storage, workers_, block_with_hash->block, txn, txn_id, [&state_factory](auto& io_executor, std::optional<TxnId> curr_txn_id, auto& storage) { return state_factory.create_state(io_executor, storage, curr_txn_id); }, /* tracers */ {}, /* refund */ true, /* gas_bailout */ false, accounts_overrides); From 625558206f77896197e3369fd175239c57b750db Mon Sep 17 00:00:00 2001 From: lupin012 <58134934+lupin012@users.noreply.github.com> Date: Tue, 11 Mar 2025 18:26:26 +0100 Subject: [PATCH 3/3] add const --- silkworm/rpc/commands/eth_api.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/silkworm/rpc/commands/eth_api.cpp b/silkworm/rpc/commands/eth_api.cpp index ad5028e420..5d162990d9 100644 --- a/silkworm/rpc/commands/eth_api.cpp +++ b/silkworm/rpc/commands/eth_api.cpp @@ -1171,7 +1171,7 @@ Task<void> EthereumRpcApi::handle_eth_call(const nlohmann::json& request, std::s try { const auto chain_storage{tx->create_storage()}; rpc::BlockReader block_reader{*chain_storage, *tx}; - auto chain_config = co_await chain_storage->read_chain_config(); + const auto chain_config = co_await chain_storage->read_chain_config(); const auto [block_num, is_latest_block] = co_await block_reader.get_block_num(block_id, /*latest_required=*/true); tx->set_state_cache_enabled(/*cache_enabled=*/is_latest_block);