Skip to content

Commit 1eb4163

Browse files
committed
refactor(spec-specs): Refactor state changes and frame hierarchy (#1841)
* refactor(spec-specs): Refactor state changes and their frames * chore(spec-specs): cleanup BAL logic; organize gas check for SSTORE * refactor(spec-specs): Changes from comments on PR #1841 * enhance: don't set defaults for state_changes
1 parent 38b6b48 commit 1eb4163

File tree

10 files changed

+490
-603
lines changed

10 files changed

+490
-603
lines changed

src/ethereum/forks/amsterdam/fork.py

Lines changed: 56 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@
3030

3131
from . import vm
3232
from .block_access_lists.builder import build_block_access_list
33-
from .block_access_lists.rlp_types import BlockAccessIndex
3433
from .block_access_lists.rlp_utils import compute_block_access_list_hash
3534
from .blocks import Block, Header, Log, Receipt, Withdrawal, encode_receipt
3635
from .bloom import logs_bloom
@@ -65,16 +64,16 @@
6564
state_root,
6665
)
6766
from .state_tracker import (
67+
StateChanges,
6868
capture_pre_balance,
6969
commit_transaction_frame,
7070
create_child_frame,
71-
get_block_access_index,
72-
handle_in_transaction_selfdestruct,
71+
filter_net_zero_frame_changes,
7372
increment_block_access_index,
74-
merge_on_success,
75-
normalize_balance_changes_for_transaction,
7673
track_address,
7774
track_balance_change,
75+
track_nonce_change,
76+
track_selfdestruct,
7877
)
7978
from .transactions import (
8079
AccessListTransaction,
@@ -249,6 +248,7 @@ def state_transition(chain: BlockChain, block: Block) -> None:
249248
prev_randao=block.header.prev_randao,
250249
excess_blob_gas=block.header.excess_blob_gas,
251250
parent_beacon_block_root=block.header.parent_beacon_block_root,
251+
state_changes=StateChanges(),
252252
)
253253

254254
block_output = apply_body(
@@ -636,7 +636,7 @@ def process_system_transaction(
636636
"""
637637
# EIP-7928: Create a child frame for system transaction
638638
# This allows proper pre-state capture for net-zero filtering
639-
system_tx_state_changes = create_child_frame(block_env.block_state_changes)
639+
system_tx_state_changes = create_child_frame(block_env.state_changes)
640640

641641
tx_env = vm.TransactionEnvironment(
642642
origin=SYSTEM_ADDRESS,
@@ -649,8 +649,12 @@ def process_system_transaction(
649649
authorizations=(),
650650
index_in_block=None,
651651
tx_hash=None,
652+
state_changes=system_tx_state_changes,
652653
)
653654

655+
# Create call frame as child of tx frame
656+
call_frame = create_child_frame(tx_env.state_changes)
657+
654658
system_tx_message = Message(
655659
block_env=block_env,
656660
tx_env=tx_env,
@@ -669,14 +673,15 @@ def process_system_transaction(
669673
accessed_storage_keys=set(),
670674
disable_precompiles=False,
671675
parent_evm=None,
672-
transaction_state_changes=system_tx_state_changes,
676+
is_create=False,
677+
state_changes=call_frame,
673678
)
674679

675680
system_tx_output = process_message_call(system_tx_message)
676681

677-
# Merge system transaction changes back to block frame
682+
# Commit system transaction changes to block frame
678683
# System transactions always succeed (or block is invalid)
679-
merge_on_success(system_tx_state_changes)
684+
commit_transaction_frame(tx_env.state_changes)
680685

681686
return system_tx_output
682687

@@ -816,17 +821,17 @@ def apply_body(
816821
# EIP-7928: Increment block frame to post-execution index
817822
# After N transactions, block frame is at index N
818823
# Post-execution operations (withdrawals, etc.) use index N+1
819-
increment_block_access_index(block_env.block_state_changes)
824+
increment_block_access_index(block_env.state_changes)
820825

821826
process_withdrawals(block_env, block_output, withdrawals)
822827

823828
process_general_purpose_requests(
824829
block_env=block_env,
825830
block_output=block_output,
826831
)
827-
# Build block access list from block_env.block_state_changes
832+
# Build block access list from block_env.state_changes
828833
block_output.block_access_list = build_block_access_list(
829-
block_env.block_state_changes
834+
block_env.state_changes
830835
)
831836

832837
return block_output
@@ -909,9 +914,10 @@ def process_transaction(
909914
"""
910915
# EIP-7928: Create a transaction-level StateChanges frame
911916
# The frame will read the current block_access_index from the block frame
912-
increment_block_access_index(block_env.block_state_changes)
913-
tx_state_changes = create_child_frame(block_env.block_state_changes)
917+
increment_block_access_index(block_env.state_changes)
918+
tx_state_changes = create_child_frame(block_env.state_changes)
914919

920+
# Capture coinbase pre-balance for net-zero filtering
915921
coinbase_pre_balance = get_account(
916922
block_env.state, block_env.coinbase
917923
).balance
@@ -949,16 +955,27 @@ def process_transaction(
949955
effective_gas_fee = tx.gas * effective_gas_price
950956

951957
gas = tx.gas - intrinsic_gas
952-
increment_nonce(block_env.state, sender, tx_state_changes)
958+
959+
# Track sender nonce increment
960+
increment_nonce(block_env.state, sender)
961+
sender_nonce_after = get_account(block_env.state, sender).nonce
962+
track_nonce_change(tx_state_changes, sender, U64(sender_nonce_after))
963+
964+
# Track sender balance deduction for gas fee
965+
sender_balance_before = get_account(block_env.state, sender).balance
966+
track_address(tx_state_changes, sender)
967+
capture_pre_balance(tx_state_changes, sender, sender_balance_before)
953968

954969
sender_balance_after_gas_fee = (
955970
Uint(sender_account.balance) - effective_gas_fee - blob_gas_fee
956971
)
957972
set_account_balance(
958-
block_env.state,
973+
block_env.state, sender, U256(sender_balance_after_gas_fee)
974+
)
975+
track_balance_change(
976+
tx_state_changes,
959977
sender,
960978
U256(sender_balance_after_gas_fee),
961-
tx_state_changes,
962979
)
963980

964981
access_list_addresses = set()
@@ -993,13 +1010,13 @@ def process_transaction(
9931010
authorizations=authorizations,
9941011
index_in_block=index,
9951012
tx_hash=get_transaction_hash(encode_transaction(tx)),
1013+
state_changes=tx_state_changes,
9961014
)
9971015

9981016
message = prepare_message(
9991017
block_env,
10001018
tx_env,
10011019
tx,
1002-
tx_state_changes,
10031020
)
10041021

10051022
tx_output = process_message_call(message)
@@ -1029,22 +1046,24 @@ def process_transaction(
10291046
sender_balance_after_refund = get_account(
10301047
block_env.state, sender
10311048
).balance + U256(gas_refund_amount)
1032-
set_account_balance(
1033-
block_env.state,
1049+
set_account_balance(block_env.state, sender, sender_balance_after_refund)
1050+
track_balance_change(
1051+
tx_env.state_changes,
10341052
sender,
10351053
sender_balance_after_refund,
1036-
tx_state_changes,
10371054
)
10381055

10391056
coinbase_balance_after_mining_fee = get_account(
10401057
block_env.state, block_env.coinbase
10411058
).balance + U256(transaction_fee)
10421059

10431060
set_account_balance(
1044-
block_env.state,
1061+
block_env.state, block_env.coinbase, coinbase_balance_after_mining_fee
1062+
)
1063+
track_balance_change(
1064+
tx_env.state_changes,
10451065
block_env.coinbase,
10461066
coinbase_balance_after_mining_fee,
1047-
tx_state_changes,
10481067
)
10491068

10501069
if coinbase_balance_after_mining_fee == 0 and account_exists_and_is_empty(
@@ -1070,35 +1089,19 @@ def process_transaction(
10701089

10711090
block_output.block_logs += tx_output.logs
10721091

1073-
# EIP-7928: Handle in-transaction self-destruct BEFORE normalization
1074-
# Destroy accounts first so normalization sees correct post-tx state
1075-
# Only accounts created in same tx are in accounts_to_delete per EIP-6780
10761092
for address in tx_output.accounts_to_delete:
10771093
destroy_account(block_env.state, address)
10781094

1079-
# EIP-7928: Normalize balance changes for this transaction before merging
1080-
# into block frame. Must happen AFTER destroy_account so net-zero filtering
1081-
# sees the correct post-transaction balance (0 for destroyed accounts).
1082-
normalize_balance_changes_for_transaction(
1083-
tx_state_changes,
1084-
BlockAccessIndex(
1085-
get_block_access_index(block_env.block_state_changes)
1086-
),
1087-
block_env.state,
1088-
)
1095+
# EIP-7928: Filter net-zero changes before committing to block frame.
1096+
# Must happen AFTER destroy_account so filtering sees correct state.
1097+
filter_net_zero_frame_changes(tx_env.state_changes, block_env.state)
10891098

1090-
commit_transaction_frame(tx_state_changes)
1099+
commit_transaction_frame(tx_env.state_changes)
10911100

1092-
# EIP-7928: Handle in-transaction self-destruct normalization AFTER merge
1101+
# EIP-7928: Track in-transaction self-destruct normalization AFTER merge
10931102
# Convert storage writes to reads and remove nonce/code changes
10941103
for address in tx_output.accounts_to_delete:
1095-
handle_in_transaction_selfdestruct(
1096-
block_env.block_state_changes,
1097-
address,
1098-
BlockAccessIndex(
1099-
get_block_access_index(block_env.block_state_changes)
1100-
),
1101-
)
1104+
track_selfdestruct(block_env.state_changes, address)
11021105

11031106

11041107
def process_withdrawals(
@@ -1109,13 +1112,12 @@ def process_withdrawals(
11091112
"""
11101113
Increase the balance of the withdrawing account.
11111114
"""
1115+
# Capture pre-state for withdrawal balance filtering
11121116
withdrawal_addresses = {wd.address for wd in withdrawals}
11131117
for address in withdrawal_addresses:
11141118
pre_balance = get_account(block_env.state, address).balance
1115-
track_address(block_env.block_state_changes, address)
1116-
capture_pre_balance(
1117-
block_env.block_state_changes, address, pre_balance
1118-
)
1119+
track_address(block_env.state_changes, address)
1120+
capture_pre_balance(block_env.state_changes, address, pre_balance)
11191121

11201122
def increase_recipient_balance(recipient: Account) -> None:
11211123
recipient.balance += wd.amount * U256(10**9)
@@ -1131,22 +1133,16 @@ def increase_recipient_balance(recipient: Account) -> None:
11311133

11321134
new_balance = get_account(block_env.state, wd.address).balance
11331135
track_balance_change(
1134-
block_env.block_state_changes, wd.address, new_balance
1136+
block_env.state_changes,
1137+
wd.address,
1138+
new_balance,
11351139
)
11361140

11371141
if account_exists_and_is_empty(block_env.state, wd.address):
11381142
destroy_account(block_env.state, wd.address)
11391143

1140-
# EIP-7928: Normalize balance changes after all withdrawals
1141-
# Filters out net-zero changes
1142-
1143-
normalize_balance_changes_for_transaction(
1144-
block_env.block_state_changes,
1145-
BlockAccessIndex(
1146-
get_block_access_index(block_env.block_state_changes)
1147-
),
1148-
block_env.state,
1149-
)
1144+
# EIP-7928: Filter net-zero balance changes for withdrawals
1145+
filter_net_zero_frame_changes(block_env.state_changes, block_env.state)
11501146

11511147

11521148
def check_gas_limit(gas_limit: Uint, parent_gas_limit: Uint) -> bool:

0 commit comments

Comments
 (0)