Skip to content

Commit 195db7d

Browse files
authored
rpcdaemon: align eth_estimateGas implementation to erigon (#2787)
1 parent 013c04b commit 195db7d

10 files changed

+143
-148
lines changed

silkworm/core/execution/evm.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ CallResult EVM::execute(const Transaction& txn, uint64_t gas) noexcept {
9898

9999
const auto gas_left = static_cast<uint64_t>(res.gas_left);
100100
const auto gas_refund = static_cast<uint64_t>(res.gas_refund);
101-
return {ValidationResult::kOk, res.status_code, gas_left, gas_refund, {res.output_data, res.output_size}};
101+
return {ValidationResult::kOk, res.status_code, gas_left, gas_refund, std::nullopt, {res.output_data, res.output_size}};
102102
}
103103

104104
evmc::Result EVM::create(const evmc_message& message) noexcept {

silkworm/core/execution/evm.hpp

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ struct CallResult {
4444
evmc_status_code status{EVMC_SUCCESS};
4545
uint64_t gas_left{0};
4646
uint64_t gas_refund{0};
47+
std::optional<uint64_t> gas_used{0};
4748
Bytes data;
4849
std::string error_message;
4950
};

silkworm/core/execution/evm_test.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,7 @@ class TestTracer : public EvmTracer {
571571
execution_end_called_ = true;
572572
const auto gas_left = static_cast<uint64_t>(res.gas_left);
573573
const auto gas_refund = static_cast<uint64_t>(res.gas_refund);
574-
result_ = {ValidationResult::kOk, res.status_code, gas_left, gas_refund, {res.output_data, res.output_size}};
574+
result_ = {ValidationResult::kOk, res.status_code, gas_left, gas_refund, std::nullopt, {res.output_data, res.output_size}};
575575
if (contract_address_ && !pc_stack_.empty()) {
576576
const auto pc = pc_stack_.back();
577577
storage_stack_[pc] =

silkworm/core/execution/processor.cpp

+5-2
Original file line numberDiff line numberDiff line change
@@ -325,8 +325,11 @@ CallResult ExecutionProcessor::call(const Transaction& txn, const std::vector<st
325325
uint64_t gas_left{result.gas_left};
326326
uint64_t gas_used{txn.gas_limit - result.gas_left};
327327

328+
uint64_t gas_refund = 0;
328329
if (refund && !evm().bailout) {
329-
gas_used = txn.gas_limit - calculate_refund_gas(txn, result.gas_left, result.gas_refund);
330+
const uint64_t gas_left_plus_refund = calculate_refund_gas(txn, result.gas_left, result.gas_refund);
331+
gas_refund = gas_left_plus_refund - result.gas_left;
332+
gas_used = txn.gas_limit - gas_left_plus_refund;
330333
// EIP-7623: Increase calldata cost
331334
if (evm().revision() >= EVMC_PRAGUE) {
332335
gas_used = std::max(gas_used, protocol::floor_cost(txn));
@@ -349,7 +352,7 @@ CallResult ExecutionProcessor::call(const Transaction& txn, const std::vector<st
349352

350353
evm_.remove_tracers();
351354

352-
return {ValidationResult::kOk, result.status, gas_left, gas_used, result.data, result.error_message};
355+
return {ValidationResult::kOk, result.status, gas_left, gas_refund, gas_used, result.data, result.error_message};
353356
}
354357

355358
void ExecutionProcessor::reset() {

silkworm/rpc/commands/eth_api.cpp

+12-12
Original file line numberDiff line numberDiff line change
@@ -885,11 +885,12 @@ Task<void> EthereumRpcApi::handle_eth_estimate_gas(const nlohmann::json& request
885885
const auto chain_storage{tx->create_storage()};
886886
rpc::BlockReader block_reader{*chain_storage, *tx, state_cache_};
887887

888-
std::optional<BlockNum> block_num_for_gas_limit;
888+
std::string block_id;
889889
if (params.size() >= 2) {
890-
const auto block_id = params[1].get<std::string>();
890+
block_id = params[1].get<std::string>();
891891
SILK_DEBUG << "block_id: " << block_id;
892-
block_num_for_gas_limit = co_await block_reader.get_block_num(block_id);
892+
} else {
893+
block_id = kLatestBlockId;
893894
}
894895

895896
AccountsOverrides accounts_overrides;
@@ -899,19 +900,18 @@ Task<void> EthereumRpcApi::handle_eth_estimate_gas(const nlohmann::json& request
899900
}
900901

901902
try {
902-
const BlockNumOrHash block_num_or_hash{kLatestBlockId};
903-
904903
const auto chain_config = co_await chain_storage->read_chain_config();
905-
const auto latest_block_num = co_await block_reader.get_block_num(kLatestBlockId);
906-
SILK_DEBUG << "chain_id: " << chain_config.chain_id << ", latest_block_num: " << latest_block_num;
904+
const auto req_block_num = co_await block_reader.get_block_num(block_id);
905+
SILK_DEBUG << "chain_id: " << chain_config.chain_id << ", block_num: " << req_block_num;
907906

908-
const auto latest_block_with_hash = co_await core::read_block_by_number(*block_cache_, *chain_storage, latest_block_num);
909-
if (!latest_block_with_hash) {
910-
reply = make_json_error(request, 100, "block not found");
907+
const auto block_with_hash = co_await core::read_block_by_number(*block_cache_, *chain_storage, req_block_num);
908+
if (!block_with_hash) {
909+
std::string error_msg = "could not find the block " + std::to_string(req_block_num) + " in cache or db";
910+
reply = make_json_error(request, -32000, error_msg);
911911
co_await tx->close(); // RAII not (yet) available with coroutines
912912
co_return;
913913
}
914-
const auto latest_block = latest_block_with_hash->block;
914+
const auto block = block_with_hash->block;
915915

916916
rpc::BlockHeaderProvider block_header_provider = [&chain_storage](BlockNum block_num) {
917917
return chain_storage->read_canonical_header(block_num);
@@ -923,7 +923,7 @@ Task<void> EthereumRpcApi::handle_eth_estimate_gas(const nlohmann::json& request
923923
};
924924

925925
rpc::EstimateGasOracle estimate_gas_oracle{block_header_provider, account_reader, chain_config, workers_, *tx, *chain_storage, accounts_overrides};
926-
const auto estimated_gas = co_await estimate_gas_oracle.estimate_gas(call, latest_block, /*txn_id=*/std::nullopt, block_num_for_gas_limit);
926+
const auto estimated_gas = co_await estimate_gas_oracle.estimate_gas(call, block, /*txn_id=*/std::nullopt);
927927

928928
reply = make_json_content(request, to_quantity(estimated_gas));
929929
} catch (const std::invalid_argument&) {

silkworm/rpc/core/estimate_gas_oracle.cpp

+36-32
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,31 @@
2626

2727
namespace silkworm::rpc {
2828

29-
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) {
29+
Task<intx::uint256> EstimateGasOracle::estimate_gas(const Call& call, const silkworm::Block& block, std::optional<TxnId> txn_id) {
3030
SILK_DEBUG << "EstimateGasOracle::estimate_gas called";
3131

3232
const auto block_num = block.header.number;
3333

3434
uint64_t hi = 0;
35-
uint64_t lo = kTxGas - 1;
35+
uint64_t lo;
3636

3737
if (call.gas.value_or(0) >= kTxGas) {
3838
SILK_DEBUG << "Set gas limit using call args: " << call.gas.value_or(0);
3939
hi = call.gas.value();
4040
} else {
41-
const auto header = co_await block_header_provider_(block_num_for_gas_limit.value_or(block_num));
41+
const auto header = co_await block_header_provider_(block_num);
4242
if (!header) {
4343
co_return 0;
4444
}
4545
hi = header->gas_limit;
4646
SILK_DEBUG << "Set gas limit using block: " << header->gas_limit;
4747
}
4848

49+
if (hi > kGasCap) {
50+
SILK_WARN << "caller gas above allowance, capping: requested " << hi << ", cap " << kGasCap;
51+
hi = kGasCap;
52+
}
53+
4954
std::optional<intx::uint256> gas_price = call.gas_price;
5055
if (gas_price && gas_price != 0) {
5156
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
7176
}
7277
}
7378

74-
if (hi > kGasCap) {
75-
SILK_WARN << "caller gas above allowance, capping: requested " << hi << ", cap " << kGasCap;
76-
hi = kGasCap;
77-
}
78-
auto cap = hi;
79-
80-
SILK_DEBUG << "hi: " << hi << ", lo: " << lo << ", cap: " << cap;
81-
8279
auto this_executor = co_await boost::asio::this_coro::executor;
8380

8481
execution::StateFactory state_factory{transaction_, /*state_cache=*/nullptr};
@@ -88,33 +85,40 @@ Task<intx::uint256> EstimateGasOracle::estimate_gas(const Call& call, const silk
8885

8986
ExecutionResult result{evmc_status_code::EVMC_SUCCESS};
9087
silkworm::Transaction transaction{call.to_transaction()};
88+
89+
auto state_overrides = std::make_shared<state::OverrideState>(*state, accounts_overrides_);
90+
EVMExecutor executor{block, config_, workers_, state_overrides};
91+
transaction.gas_limit = hi;
92+
result = try_execution(executor, transaction);
93+
if (!result.success()) {
94+
return result;
95+
}
96+
97+
// Assuming a contract can freely run all the instructions, we have
98+
// the true amount of gas it wants to consume to execute fully.
99+
// We want to ensure that the gas used doesn't fall below this
100+
auto true_gas = result.gas_used.value_or(0);
101+
uint64_t refund = result.gas_refund.value_or(0);
102+
lo = std::min(true_gas + refund - 1, kTxGas - 1);
103+
104+
SILK_DEBUG << "hi: " << hi << ", lo: " << lo;
105+
91106
while (lo + 1 < hi) {
92-
auto state_overrides = std::make_shared<state::OverrideState>(*state, accounts_overrides_);
93-
EVMExecutor executor{block, config_, workers_, state_overrides};
107+
state_overrides = std::make_shared<state::OverrideState>(*state, accounts_overrides_);
108+
EVMExecutor curr_executor{block, config_, workers_, state_overrides};
94109
auto mid = (hi + lo) / 2;
95110
transaction.gas_limit = mid;
96-
result = try_execution(executor, transaction);
97-
if (result.success()) {
98-
hi = mid;
99-
} else {
100-
if (result.pre_check_error_code && result.pre_check_error_code != PreCheckErrorCode::kIntrinsicGasTooLow) {
101-
result.status_code = evmc_status_code::EVMC_SUCCESS;
102-
return result;
103-
}
111+
result = try_execution(curr_executor, transaction);
112+
if (result.pre_check_error_code) {
113+
break;
114+
}
115+
if (!result.success() || result.gas_used.value_or(0) < true_gas) {
104116
lo = mid;
117+
} else {
118+
hi = mid;
105119
}
106120
}
107-
108-
if (hi == cap) {
109-
auto state_overrides = std::make_shared<state::OverrideState>(*state, accounts_overrides_);
110-
EVMExecutor executor{block, config_, workers_, state_overrides};
111-
transaction.gas_limit = hi;
112-
result = try_execution(executor, transaction);
113-
SILK_DEBUG << "HI == cap tested again with " << (result.status_code == evmc_status_code::EVMC_SUCCESS ? "succeed" : "failed");
114-
} else if (!result.pre_check_error_code || result.pre_check_error_code == PreCheckErrorCode::kIntrinsicGasTooLow) {
115-
result.pre_check_error = std::nullopt;
116-
result.status_code = evmc_status_code::EVMC_SUCCESS;
117-
}
121+
result.status_code = evmc_status_code::EVMC_SUCCESS;
118122

119123
SILK_DEBUG << "EstimateGasOracle::estimate_gas returns " << hi;
120124

silkworm/rpc/core/estimate_gas_oracle.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class EstimateGasOracle {
9292
EstimateGasOracle(const EstimateGasOracle&) = delete;
9393
EstimateGasOracle& operator=(const EstimateGasOracle&) = delete;
9494

95-
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 = {});
95+
Task<intx::uint256> estimate_gas(const Call& call, const silkworm::Block& latest_block, std::optional<TxnId> txn_id);
9696

9797
protected:
9898
virtual ExecutionResult try_execution(EVMExecutor& executor, const silkworm::Transaction& transaction);

0 commit comments

Comments
 (0)