Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion category/execution/ethereum/execute_block_header.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#include <category/core/bytes.hpp>
#include <category/core/endian.hpp>
#include <category/core/int.hpp>
#include <category/core/monad_exception.hpp>
#include <category/execution/ethereum/block_hash_history.hpp>
#include <category/execution/ethereum/core/block.hpp>
#include <category/execution/ethereum/event/exec_event_ctypes.h>
Expand Down Expand Up @@ -79,7 +80,8 @@ void execute_block_header(
staking::execute_block_prelude<traits>(state);
}

MONAD_ASSERT(block_state.can_merge(state));
MONAD_ASSERT_THROW(
block_state.can_merge(state), "block state cannot be merged");
Comment on lines +83 to +84

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] This hardens the block-prologue merge, but the same user-controlled trigger still aborts the process via two sibling asserts on the eth_simulateV1 path: MONAD_ASSERT(block_state.can_merge(state)) in execute_block.cpp:344 (epilogue merge) and execute_transaction.cpp:490 (post-retry transaction merge). The failure mechanism your test exercises — create_contract resets storage, so the State's recorded original diverges from a storage override already sitting in block_state — is reproducible per-transaction: a simulated call that CREATEs at an address carrying a state_diff override fails can_merge at execute_transaction.cpp:470, and the retry deterministically re-derives the same mismatch and trips the hard assert at line 490. Consider converting those two sites to MONAD_ASSERT_THROW as well (they're inside the try/catch that maps MonadException to EVMC_INTERNAL_ERROR), or noting why they're unreachable from the simulate path.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the execute_block.cpp:344 is theoretically triggerable via an Ethereum Prague request by overriding an EIP-7002/7251 deploy with code that recreates an overridden account and touches its storage. However, simulated headers leave requests_hash unset, and block overrides cannot set it. Execution will therefore fail at the earlier requests_hash.has_value() check. Nonetheless, this configuration is currently unsupported.

I will think a bit about execute_transaction.cpp:470.

block_state.merge(state);
record_account_access_events(
exec_recorder, MONAD_ACCT_ACCESS_BLOCK_PROLOGUE, state);
Expand Down
115 changes: 115 additions & 0 deletions category/rpc/monad_executor_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <category/core/int.hpp>
#include <category/core/keccak.hpp>
#include <category/core/runtime/uint256.hpp>
#include <category/execution/ethereum/block_hash_history.hpp>
#include <category/execution/ethereum/chain/chain_config.h>
#include <category/execution/ethereum/core/account.hpp>
#include <category/execution/ethereum/core/block.hpp>
Expand Down Expand Up @@ -8254,3 +8255,117 @@ TEST_F(EthCallFixture, eth_simulate_v1_beacon_roots)
monad_state_override_vec_destroy(state_overrides);
monad_executor_destroy(executor);
}

TEST_F(EthCallFixture, eth_simulate_v1_block_history_state_override)
{
static constexpr uint64_t base_block_number = 255;
static constexpr Address sender =
0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266_address;

commit_sequential(
tdb,
StateDeltas{
{sender,
StateDelta{
.account =
{std::nullopt,
Account{.balance = uint256_t{1'000'000}, .nonce = 0}}}}},
{},
BlockHeader{.number = 0});

for (uint64_t i = 1; i <= base_block_number; ++i) {
commit_sequential(tdb, {}, {}, BlockHeader{.number = i});
}

auto *executor = create_executor(dbname.string());
auto *const state_overrides = monad_state_override_vec_create(1);
auto *const block_overrides = monad_block_override_vec_create(1);

uint8_t const code = 0;
bytes32_t const base = store_be_as<bytes32_t>(uint256_t{base_block_number});
bytes32_t const slot_value =
0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee_bytes32;

add_override_address_at(
state_overrides,
0,
BLOCK_HISTORY_ADDRESS.bytes,
sizeof(BLOCK_HISTORY_ADDRESS.bytes));
set_override_code_at(
state_overrides,
0,
BLOCK_HISTORY_ADDRESS.bytes,
sizeof(BLOCK_HISTORY_ADDRESS.bytes),
&code,
0);
set_override_nonce_at(
state_overrides,
0,
BLOCK_HISTORY_ADDRESS.bytes,
sizeof(BLOCK_HISTORY_ADDRESS.bytes),
0);
set_override_state_diff_at(
state_overrides,
0,
BLOCK_HISTORY_ADDRESS.bytes,
sizeof(BLOCK_HISTORY_ADDRESS.bytes),
base.bytes,
sizeof(base.bytes),
slot_value.bytes,
sizeof(slot_value.bytes));

auto const rlp_senders = to_vec(rlp::encode_list2(
rlp::encode_list2(rlp::encode_address(std::make_optional(sender)))));

Transaction const tx{
.max_fee_per_gas = 1,
.gas_limit = 200'000'000,
.to = BLOCK_HISTORY_ADDRESS,
.type = TransactionType::eip1559,
.max_priority_fee_per_gas = 0,
};
auto const encoded_tx = rlp::encode_transaction(tx);
auto const rlp_calls = to_vec(rlp::encode_list2(
rlp::encode_list2(rlp::encode_string2(byte_string_view(encoded_tx)))));

BlockHeader const header{
.number = base_block_number,
.gas_limit = 200'000'000,
};
auto const rlp_header = to_vec(rlp::encode_block_header(header));
auto const rlp_block_id = to_vec(rlp_finalized_id);

struct callback_context ctx;
boost::fibers::future<void> f = ctx.promise.get_future();

monad_executor_eth_simulate_submit(
executor,
CHAIN_CONFIG_MONAD_DEVNET,
rlp_senders.data(),
rlp_senders.size(),
rlp_calls.data(),
rlp_calls.size(),
base_block_number,
rlp_header.data(),
rlp_header.size(),
rlp_block_id.data(),
rlp_block_id.size(),
rlp_finalized_id.data(),
rlp_finalized_id.size(),
simulate_gas_limit,
simulate_max_calls,
state_overrides,
block_overrides,
false,
complete_callback,
(void *)&ctx);
f.get();

ASSERT_EQ(ctx.result->status_code, EVMC_INTERNAL_ERROR);
ASSERT_NE(ctx.result->message, nullptr);
EXPECT_STREQ(ctx.result->message, "block state cannot be merged");

monad_block_override_vec_destroy(block_overrides);
monad_state_override_vec_destroy(state_overrides);
monad_executor_destroy(executor);
}
Loading