Skip to content

Commit 85af75f

Browse files
committed
[event] record state access events
There are two changes here: - record_txn_events.cpp gains functions for recording State objects as `ACCOUNT_ACCESS_LIST_HEADER`, `ACCOUNT_ACCESS`, and `STORAGE_ACCESS` events - All state-affecting changes to the block that occur before transaction and after the transaction are also recorded
1 parent 29f1c15 commit 85af75f

File tree

6 files changed

+262
-12
lines changed

6 files changed

+262
-12
lines changed

category/execution/ethereum/event/record_txn_events.cpp

Lines changed: 235 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313
// You should have received a copy of the GNU General Public License
1414
// along with this program. If not, see <http://www.gnu.org/licenses/>.
1515

16+
#include <category/core/assert.h>
1617
#include <category/core/bytes.hpp>
1718
#include <category/core/config.hpp>
19+
#include <category/core/int.hpp>
1820
#include <category/core/keccak.hpp>
1921
#include <category/core/result.hpp>
22+
#include <category/execution/ethereum/core/account.hpp>
2023
#include <category/execution/ethereum/core/address.hpp>
2124
#include <category/execution/ethereum/core/eth_ctypes.h>
2225
#include <category/execution/ethereum/core/receipt.hpp>
@@ -26,11 +29,16 @@
2629
#include <category/execution/ethereum/event/exec_event_recorder.hpp>
2730
#include <category/execution/ethereum/event/record_txn_events.hpp>
2831
#include <category/execution/ethereum/execute_transaction.hpp>
32+
#include <category/execution/ethereum/state3/account_state.hpp>
33+
#include <category/execution/ethereum/state3/state.hpp>
34+
#include <category/execution/ethereum/state3/version_stack.hpp>
2935
#include <category/execution/ethereum/trace/call_frame.hpp>
3036
#include <category/execution/ethereum/validate_transaction.hpp>
3137

3238
#include <bit>
39+
#include <cstddef>
3340
#include <cstdint>
41+
#include <memory>
3442
#include <optional>
3543
#include <span>
3644
#include <utility>
@@ -67,6 +75,216 @@ void init_txn_header_start(
6775
static_cast<uint32_t>(txn.authorization_list.size());
6876
}
6977

78+
// Tracks information about an accessed account, including (1) the prestate and
79+
// the (2) the modified state if a write access modified anything, with helper
80+
// functions to determine what was modified
81+
struct AccountAccessInfo
82+
{
83+
Address const *address;
84+
OriginalAccountState const *prestate; // State as it existed in original
85+
AccountState const *modified_state; // Last state as it existed in current
86+
87+
bool is_read_only_access() const
88+
{
89+
return modified_state == nullptr;
90+
}
91+
92+
std::pair<uint64_t, bool> get_nonce_modification() const
93+
{
94+
if (is_read_only_access()) {
95+
return {0, false};
96+
}
97+
98+
std::optional<Account> const &prestate_account =
99+
get_account_for_trace(*prestate);
100+
std::optional<Account> const &modified_account =
101+
get_account_for_trace(*modified_state);
102+
103+
uint64_t const prestate_nonce =
104+
is_dead(prestate_account) ? 0 : prestate_account->nonce;
105+
uint64_t const modified_nonce =
106+
is_dead(modified_account) ? 0 : modified_account->nonce;
107+
return {modified_nonce, prestate_nonce != modified_nonce};
108+
}
109+
110+
std::pair<uint256_t, bool> get_balance_modification() const
111+
{
112+
if (is_read_only_access()) {
113+
return {0, false};
114+
}
115+
116+
std::optional<Account> const &prestate_account =
117+
get_account_for_trace(*prestate);
118+
std::optional<Account> const &modified_account =
119+
get_account_for_trace(*modified_state);
120+
121+
uint256_t const prestate_balance =
122+
is_dead(prestate_account) ? 0 : prestate_account->balance;
123+
uint256_t const modified_balance =
124+
is_dead(modified_account) ? 0 : modified_account->balance;
125+
return {modified_balance, prestate_balance != modified_balance};
126+
}
127+
};
128+
129+
/// Reserves either a block-level or transaction-level event, depending on
130+
/// whether opt_txn_num is set or not; the account access events are allocated
131+
/// this way, as some of them occur at system scope
132+
template <typename T>
133+
ReservedExecEvent<T> reserve_event(
134+
ExecutionEventRecorder *exec_recorder, monad_exec_event_type event_type,
135+
std::optional<uint32_t> opt_txn_num)
136+
{
137+
return opt_txn_num
138+
? exec_recorder->reserve_txn_event<T>(event_type, *opt_txn_num)
139+
: exec_recorder->reserve_block_event<T>(event_type);
140+
}
141+
142+
// Records a MONAD_EXEC_STORAGE_ACCESS event for all reads and writes in the
143+
// AccountState prestate and modified maps
144+
void record_storage_events(
145+
ExecutionEventRecorder *exec_recorder,
146+
monad_exec_account_access_context ctx, std::optional<uint32_t> opt_txn_num,
147+
uint32_t account_index, Address const *address,
148+
AccountState::StorageMap const *prestate_storage,
149+
AccountState::StorageMap const *modified_storage, bool is_transient)
150+
{
151+
for (size_t index = 0; auto const &[key, value] : *prestate_storage) {
152+
bool is_modified = false;
153+
bytes32_t end_value = {};
154+
155+
if (modified_storage) {
156+
if (bytes32_t const *const v = modified_storage->find(key)) {
157+
end_value = *v;
158+
is_modified = end_value != value;
159+
}
160+
}
161+
162+
ReservedExecEvent const storage_access =
163+
reserve_event<monad_exec_storage_access>(
164+
exec_recorder, MONAD_EXEC_STORAGE_ACCESS, opt_txn_num);
165+
*storage_access.payload = monad_exec_storage_access{
166+
.address = *address,
167+
.index = static_cast<uint32_t>(index),
168+
.access_context = ctx,
169+
.modified = is_modified,
170+
.transient = is_transient,
171+
.key = key,
172+
.start_value = value,
173+
.end_value = end_value,
174+
};
175+
storage_access.event->content_ext[MONAD_FLOW_ACCOUNT_INDEX] =
176+
account_index;
177+
exec_recorder->commit(storage_access);
178+
++index;
179+
}
180+
}
181+
182+
// Records an MONAD_EXEC_ACCOUNT_ACCESS event, and delegates to
183+
// record_storage_events to record both the ordinary and transient storage
184+
// accesses
185+
void record_account_events(
186+
ExecutionEventRecorder *exec_recorder,
187+
monad_exec_account_access_context ctx, std::optional<uint32_t> opt_txn_num,
188+
uint32_t index, AccountAccessInfo const &account_info)
189+
{
190+
MONAD_ASSERT(account_info.prestate);
191+
monad_c_eth_account_state initial_state;
192+
std::optional<Account> const &prestate_account =
193+
get_account_for_trace(*account_info.prestate);
194+
bool const prestate_valid = !is_dead(prestate_account);
195+
196+
initial_state.nonce = prestate_valid ? prestate_account->nonce : 0;
197+
initial_state.balance = prestate_valid ? prestate_account->balance : 0;
198+
initial_state.code_hash =
199+
prestate_valid ? prestate_account->code_hash : NULL_HASH;
200+
201+
auto const [modified_balance, is_balance_modified] =
202+
account_info.get_balance_modification();
203+
auto const [modified_nonce, is_nonce_modified] =
204+
account_info.get_nonce_modification();
205+
206+
ReservedExecEvent const account_access =
207+
reserve_event<monad_exec_account_access>(
208+
exec_recorder, MONAD_EXEC_ACCOUNT_ACCESS, opt_txn_num);
209+
*account_access.payload = monad_exec_account_access{
210+
.index = index,
211+
.address = *account_info.address,
212+
.access_context = ctx,
213+
.is_balance_modified = is_balance_modified,
214+
.is_nonce_modified = is_nonce_modified,
215+
.prestate = initial_state,
216+
.modified_balance = modified_balance,
217+
.modified_nonce = modified_nonce,
218+
.storage_key_count =
219+
static_cast<uint32_t>(size(account_info.prestate->storage_)),
220+
.transient_count = static_cast<uint32_t>(
221+
size(account_info.prestate->transient_storage_))};
222+
exec_recorder->commit(account_access);
223+
224+
auto const *const post_state_storage_map =
225+
account_info.is_read_only_access()
226+
? nullptr
227+
: &account_info.modified_state->storage_;
228+
record_storage_events(
229+
exec_recorder,
230+
ctx,
231+
opt_txn_num,
232+
index,
233+
account_info.address,
234+
&account_info.prestate->storage_,
235+
post_state_storage_map,
236+
false);
237+
238+
auto const *const post_state_transient_map =
239+
account_info.is_read_only_access()
240+
? nullptr
241+
: &account_info.modified_state->transient_storage_;
242+
record_storage_events(
243+
exec_recorder,
244+
ctx,
245+
opt_txn_num,
246+
index,
247+
account_info.address,
248+
&account_info.prestate->transient_storage_,
249+
post_state_transient_map,
250+
true);
251+
}
252+
253+
// Function that records all state accesses and changes that occurred in some
254+
// scope, either the block prologue, block epilogue, or in the scope of some
255+
// transaction
256+
void record_account_access_events_internal(
257+
ExecutionEventRecorder *exec_recorder,
258+
monad_exec_account_access_context ctx, std::optional<uint32_t> opt_txn_num,
259+
State const &state)
260+
{
261+
auto const &prestate_map = state.original();
262+
263+
ReservedExecEvent const list_header =
264+
reserve_event<monad_exec_account_access_list_header>(
265+
exec_recorder, MONAD_EXEC_ACCOUNT_ACCESS_LIST_HEADER, opt_txn_num);
266+
*list_header.payload = monad_exec_account_access_list_header{
267+
.entry_count = static_cast<uint32_t>(prestate_map.size()),
268+
.access_context = ctx};
269+
exec_recorder->commit(list_header);
270+
271+
auto const &current_state_map = state.current();
272+
for (uint32_t index = 0; auto const &[address, prestate] : prestate_map) {
273+
AccountState const *current_state = nullptr;
274+
if (auto const i = current_state_map.find(address);
275+
i != end(current_state_map)) {
276+
current_state = std::addressof(i->second.recent());
277+
}
278+
record_account_events(
279+
exec_recorder,
280+
ctx,
281+
opt_txn_num,
282+
index,
283+
AccountAccessInfo{&address, &prestate, current_state});
284+
index++;
285+
}
286+
}
287+
70288
MONAD_ANONYMOUS_NAMESPACE_END
71289

72290
MONAD_NAMESPACE_BEGIN
@@ -136,7 +354,7 @@ void record_txn_header_events(
136354

137355
void record_txn_output_events(
138356
uint32_t const txn_num, Receipt const &receipt,
139-
std::span<CallFrame const> const call_frames)
357+
std::span<CallFrame const> const call_frames, State const &txn_state)
140358
{
141359
ExecutionEventRecorder *const exec_recorder = g_exec_event_recorder.get();
142360
if (exec_recorder == nullptr) {
@@ -203,6 +421,10 @@ void record_txn_output_events(
203421
++index;
204422
}
205423

424+
// Account access records for the transaction
425+
record_account_access_events_internal(
426+
exec_recorder, MONAD_ACCT_ACCESS_TRANSACTION, txn_num, txn_state);
427+
206428
exec_recorder->record_txn_marker_event(MONAD_EXEC_TXN_END, txn_num);
207429
}
208430

@@ -241,4 +463,16 @@ void record_txn_error_event(
241463
}
242464
}
243465

466+
// The externally-visible wrapper of the account-access-recording function that
467+
// is called from execute_block.cpp, to record prologue and epilogue accesses;
468+
// transaction-scope state accesses use record_txn_output_events instead
469+
void record_account_access_events(
470+
monad_exec_account_access_context ctx, State const &state)
471+
{
472+
if (ExecutionEventRecorder *const e = g_exec_event_recorder.get()) {
473+
return record_account_access_events_internal(
474+
e, ctx, std::nullopt, state);
475+
}
476+
}
477+
244478
MONAD_NAMESPACE_END

category/execution/ethereum/event/record_txn_events.hpp

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,16 @@
2323
#include <optional>
2424
#include <span>
2525

26+
enum monad_exec_account_access_context : uint8_t;
27+
2628
MONAD_NAMESPACE_BEGIN
2729

2830
struct CallFrame;
2931
struct Receipt;
3032
struct Transaction;
3133

34+
class State;
35+
3236
/// Record the transaction header events (TXN_HEADER_START, the EIP-2930
3337
/// and EIP-7702 events, and TXN_HEADER_END)
3438
void record_txn_header_events(
@@ -38,11 +42,17 @@ void record_txn_header_events(
3842
/// Record TXN_EVM_OUTPUT, and all subsequent execution output events
3943
/// (TXN_LOG, TXN_CALL_FRAME, etc.)
4044
void record_txn_output_events(
41-
uint32_t txn_num, Receipt const &, std::span<CallFrame const>);
45+
uint32_t txn_num, Receipt const &, std::span<CallFrame const>,
46+
State const &);
4247

4348
/// Record TXN_REJECT or EVM_ERROR events depending on what happened during
4449
/// transaction execution
4550
void record_txn_error_event(
4651
uint32_t txn_num, Result<Receipt>::error_type const &);
4752

53+
/// Record all account state accesses (both reads and writes) described by a
54+
/// State object
55+
void record_account_access_events(
56+
monad_exec_account_access_context, State const &);
57+
4858
MONAD_NAMESPACE_END

category/execution/ethereum/execute_block.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ void execute_block_header(
205205

206206
MONAD_ASSERT(block_state.can_merge(state));
207207
block_state.merge(state);
208+
record_account_access_events(MONAD_ACCT_ACCESS_BLOCK_PROLOGUE, state);
208209
}
209210

210211
EXPLICIT_TRAITS(execute_block_header);
@@ -373,6 +374,7 @@ Result<std::vector<Receipt>> execute_block(
373374

374375
MONAD_ASSERT(block_state.can_merge(state));
375376
block_state.merge(state);
377+
record_account_access_events(MONAD_ACCT_ACCESS_BLOCK_EPILOGUE, state);
376378

377379
return retvals;
378380
}

category/execution/ethereum/execute_transaction.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,8 @@ Result<Receipt> ExecuteTransaction<traits>::operator()()
421421
record_txn_output_events(
422422
static_cast<uint32_t>(this->i_),
423423
receipt,
424-
call_tracer_.get_call_frames());
424+
call_tracer_.get_call_frames(),
425+
state);
425426
return receipt;
426427
}
427428
}
@@ -446,7 +447,8 @@ Result<Receipt> ExecuteTransaction<traits>::operator()()
446447
record_txn_output_events(
447448
static_cast<uint32_t>(this->i_),
448449
receipt,
449-
call_tracer_.get_call_frames());
450+
call_tracer_.get_call_frames(),
451+
state);
450452
return receipt;
451453
}
452454
}

category/execution/ethereum/state3/account_state.hpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,11 @@ class AccountState : public AccountSubstate
6161
friend class State;
6262
friend class BlockState;
6363

64-
// the classes below can access the account_ field just for logging but
65-
// CANNOT use it to make decisions affecting the final state (state of
66-
// accounts) of execution.
67-
friend struct trace::PrestateTracer;
68-
friend struct trace::StateDiffTracer;
64+
friend std::optional<Account> const &
65+
get_account_for_trace(AccountState const &as)
66+
{
67+
return as.account_;
68+
}
6969

7070
public:
7171
StorageMap storage_{};

category/execution/ethereum/trace/state_tracer.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,12 @@ namespace trace
8383

8484
// Possible diff.
8585
auto const &current_account_state = current_stack.recent();
86-
auto const &current_account = current_account_state.account_;
86+
auto const &current_account =
87+
get_account_for_trace(current_account_state);
8788
auto const &current_storage = current_account_state.storage_;
8889
auto const &original_account_state = it->second;
89-
auto const &original_account = original_account_state.account_;
90+
auto const &original_account =
91+
get_account_for_trace(original_account_state);
9092
auto const &original_storage = original_account_state.storage_;
9193

9294
// Nothing to do if the account has been created and destructed
@@ -228,7 +230,7 @@ namespace trace
228230
json PrestateTracer::account_state_to_json(
229231
OriginalAccountState const &as, State &state)
230232
{
231-
auto const &account = as.account_;
233+
auto const &account = get_account_for_trace(as);
232234
auto const &storage = as.storage_;
233235
json res = account_to_json(account, state);
234236
if (!storage.empty() && account.has_value()) {

0 commit comments

Comments
 (0)