ethereum.forks.bpo5.state_trackerethereum.forks.amsterdam.state_tracker

State Tracking for Block Execution.

Track state changes on top of a read-only PreState. At block end, accumulated diffs feed into PreState.compute_state_root().

.. contents:: Table of Contents :backlinks: none :local:

Introduction

Replace the mutable State class with lightweight state trackers that record diffs. BlockState accumulates committed transaction changes across a block. TransactionState tracks in-flight changes within a single transaction and supports copy-on-write rollback.

BlockState

Accumulate committed transaction-level changes across a block.

Read chain: block writes -> pre_state.

account_reads and storage_reads accumulate across all transactions for BAL generation.

storage_clears records addresses whose pre-existing storage was wiped earlier in the block, so later reads must not fall back to pre_state. Contract creation over a storage-only account and deletion of an empty account that still holds storage both wipe storage.

42
@final
43
@dataclass
class BlockState:

pre_state

60
    pre_state: PreState

account_reads

61
    account_reads: Set[Address] = field(default_factory=set)

account_writes

62
    account_writes: Dict[Address, Optional[Account]] = field(
63
        default_factory=dict
64
    )

storage_reads

65
    storage_reads: Set[Tuple[Address, Bytes32]] = field(default_factory=set)

storage_writes

66
    storage_writes: Dict[Address, Dict[Bytes32, U256]] = field(
67
        default_factory=dict
68
    )

code_writes

69
    code_writes: Dict[Hash32, Bytes] = field(default_factory=dict)

storage_clears

70
    storage_clears: Set[Address] = field(default_factory=set)

TransactionState

Track in-flight state changes within a single transaction.

Read chain: tx writes -> block writes -> pre_state.

storage_reads and account_reads are shared references that survive rollback (reads from failed calls still appear in the Block Access List).

73
@final
74
@dataclass
class TransactionState:

parent

86
    parent: BlockState

account_reads

87
    account_reads: Set[Address] = field(default_factory=set)

account_writes

88
    account_writes: Dict[Address, Optional[Account]] = field(
89
        default_factory=dict
90
    )

storage_reads

91
    storage_reads: Set[Tuple[Address, Bytes32]] = field(default_factory=set)

storage_writes

92
    storage_writes: Dict[Address, Dict[Bytes32, U256]] = field(
93
        default_factory=dict
94
    )

code_writes

95
    code_writes: Dict[Hash32, Bytes] = field(default_factory=dict)

created_accounts

96
    created_accounts: Set[Address] = field(default_factory=set)

storage_clears

97
    storage_clears: Set[Address] = field(default_factory=set)

transient_storage

98
    transient_storage: Dict[Tuple[Address, Bytes32], U256] = field(
99
        default_factory=dict
100
    )

get_pre_state_account_optional

Get the Account object at an address that existed before the current transaction, or None (rather than EMPTY_ACCOUNT) if there was no account at the address at that point.

Use get_pre_state_account() if the difference between a non-existent account and EMPTY_ACCOUNT isn't important.

Parameters

tx_state : The transaction state. address : Address to look up.

Returns

account : Optional[Account] Account at address before the current transaction.

def get_pre_state_account_optional(​tx_state: TransactionState, ​​address: Address​) -> Optional[Account]:
106
    <snip>
130
    tx_state.account_reads.add(address)
131
    if address in tx_state.parent.account_writes:
132
        return tx_state.parent.account_writes[address]
133
    return tx_state.parent.pre_state.get_account_optional(address)

get_pre_state_account

Get the Account object at an address that existed before the current transaction, or EMPTY_ACCOUNT) if there was no account at the address at that point.

Use get_pre_state_account_optional() if the difference between a non-existent account and EMPTY_ACCOUNT is material.

Parameters

tx_state : The transaction state. address : Address to look up.

Returns

account : Account Account at address before the current transaction.

def get_pre_state_account(​tx_state: TransactionState, ​​address: Address​) -> Account:
139
    <snip>  # noqa: E501
163
    account = get_pre_state_account_optional(tx_state, address)
164
    if account is None:
165
        return EMPTY_ACCOUNT
166
    else:
167
        return account

get_account_optional

Get the Account object at an address. Return None (rather than EMPTY_ACCOUNT) if there is no account at the address.

Parameters

tx_state : The transaction state. address : Address to look up.

Returns

account : Optional[Account] Account at address.

def get_account_optional(​tx_state: TransactionState, ​​address: Address​) -> Optional[Account]:
173
    <snip>
190
    tx_state.account_reads.add(address)
191
    if address in tx_state.account_writes:
192
        return tx_state.account_writes[address]
111
    if address in tx_state.parent.account_writes:
112
        return tx_state.parent.account_writes[address]
113
    return tx_state.parent.pre_state.get_account_optional(address)
193
    return get_pre_state_account_optional(tx_state, address)

get_account

Get the Account object at an address. Return EMPTY_ACCOUNT if there is no account at the address.

Use get_account_optional() if you care about the difference between a non-existent account and EMPTY_ACCOUNT.

Parameters

tx_state : The transaction state. address : Address to look up.

Returns

account : Account Account at address.

def get_account(​tx_state: TransactionState, ​​address: Address​) -> Account:
197
    <snip>
217
    account = get_account_optional(tx_state, address)
218
    if account is None:
219
        return EMPTY_ACCOUNT
220
    else:
221
        return account

get_code

Get the bytecode for a given code hash.

Read chain: tx code_writes -> block code_writes -> pre_state.

Parameters

tx_state : The transaction state. code_hash : Hash of the code to look up.

Returns

code : Bytes The bytecode.

def get_code(​tx_state: TransactionState, ​​code_hash: Hash32​) -> Bytes:
225
    <snip>
243
    if code_hash == EMPTY_CODE_HASH:
244
        return b""
245
    if code_hash in tx_state.code_writes:
246
        return tx_state.code_writes[code_hash]
247
    if code_hash in tx_state.parent.code_writes:
248
        return tx_state.parent.code_writes[code_hash]
249
    return tx_state.parent.pre_state.get_code(code_hash)

get_storage

Get a value at a storage key on an account. Return U256(0) if the storage key has not been set previously.

Parameters

tx_state : The transaction state. address : Address of the account. key : Key to look up.

Returns

value : U256 Value at the key.

def get_storage(​tx_state: TransactionState, ​​address: Address, ​​key: Bytes32​) -> U256:
255
    <snip>
274
    tx_state.storage_reads.add((address, key))
275
    if address in tx_state.storage_writes:
276
        if key in tx_state.storage_writes[address]:
277
            return tx_state.storage_writes[address][key]
278
    if address in tx_state.storage_clears:
279
        return U256(0)
280
    if address in tx_state.parent.storage_writes:
281
        if key in tx_state.parent.storage_writes[address]:
282
            return tx_state.parent.storage_writes[address][key]
283
    if address in tx_state.parent.storage_clears:
284
        return U256(0)
285
    return tx_state.parent.pre_state.get_storage(address, key)

get_storage_original

Get the original value in a storage slot i.e. the value before the current transaction began. Read from block-level writes, then pre_state. Return U256(0) for accounts created in the current transaction.

Parameters

tx_state : The transaction state. address : Address of the account to read the value from. key : Key of the storage slot.

def get_storage_original(​tx_state: TransactionState, ​​address: Address, ​​key: Bytes32​) -> U256:
291
    <snip>
307
    if address in tx_state.created_accounts:
308
        return U256(0)
309
    if address in tx_state.parent.storage_writes:
310
        if key in tx_state.parent.storage_writes[address]:
311
            return tx_state.parent.storage_writes[address][key]
312
    if address in tx_state.parent.storage_clears:
313
        return U256(0)
314
    return tx_state.parent.pre_state.get_storage(address, key)

get_transient_storage

Get a value at a storage key on an account from transient storage. Return U256(0) if the storage key has not been set previously.

Parameters

tx_state : The transaction state. address : Address of the account. key : Key to look up.

Returns

value : U256 Value at the key.

def get_transient_storage(​tx_state: TransactionState, ​​address: Address, ​​key: Bytes32​) -> U256:
320
    <snip>
339
    return tx_state.transient_storage.get((address, key), U256(0))

account_exists

Check if an account exists in the state trie.

Parameters

tx_state : The transaction state. address : Address of the account that needs to be checked.

Returns

account_exists : bool True if account exists in the state trie, False otherwise.

def account_exists(​tx_state: TransactionState, ​​address: Address​) -> bool:
343
    <snip>
359
    return get_account_optional(tx_state, address) is not None

account_deployable

Check if an account's code can be written to.

def account_deployable(​tx_state: TransactionState, ​​address: Address​) -> bool:
363
    <snip>
366
    account = get_account(tx_state, address)
367
    if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH:
368
        return False
369
370
    return True

account_exists_and_is_empty

Check if an account exists and has zero nonce, empty code and zero balance.

Parameters

tx_state : The transaction state. address : Address of the account that needs to be checked.

Returns

exists_and_is_empty : bool True if an account exists and has zero nonce, empty code and zero balance, False otherwise.

def account_exists_and_is_empty(​tx_state: TransactionState, ​​address: Address​) -> bool:
376
    <snip>
394
    account = get_account_optional(tx_state, address)
395
    return (
396
        account is not None
397
        and account.nonce == Uint(0)
398
        and account.code_hash == EMPTY_CODE_HASH
399
        and account.balance == 0
400
    )

is_account_alive

Check whether an account is both in the state and non-empty.

Parameters

tx_state : The transaction state. address : Address of the account that needs to be checked.

Returns

is_alive : bool True if the account is alive.

def is_account_alive(​tx_state: TransactionState, ​​address: Address​) -> bool:
404
    <snip>
420
    account = get_account_optional(tx_state, address)
421
    return account is not None and account != EMPTY_ACCOUNT

set_account

Set the Account object at an address. Setting to None deletes the account (but not its storage, see destroy_account()).

Parameters

tx_state : The transaction state. address : Address to set. account : Account to set at address.

def set_account(​tx_state: TransactionState, ​​address: Address, ​​account: Optional[Account]​) -> None:
429
    <snip>
444
    tx_state.account_writes[address] = account

set_storage

Set a value at a storage key on an account.

Parameters

tx_state : The transaction state. address : Address of the account. key : Key to set. value : Value to set at the key.

def set_storage(​tx_state: TransactionState, ​​address: Address, ​​key: Bytes32, ​​value: U256​) -> None:
453
    <snip>
468
    assert get_account_optional(tx_state, address) is not None
469
    if address not in tx_state.storage_writes:
470
        tx_state.storage_writes[address] = {}
471
    tx_state.storage_writes[address][key] = value

destroy_account

Completely remove the account at address and all of its storage.

This function is made available exclusively for the Invoked by SELFDESTRUCTmodify_state (and the coinbase fee-credit path) to opcode. It is expected that SELFDESTRUCT will be disabled in aclean up an account that has become empty (zero nonce, empty future hardfork and this function will be removed. Only supports same transaction destruction.code, and zero balance) so it does not appear in the post-state.

Parameters

tx_state : The transaction state. address : Address of account to destroy.

def destroy_account(​tx_state: TransactionState, ​​address: Address​) -> None:
475
    <snip>
490
    destroy_storage(tx_state, address)
491
    set_account(tx_state, address, None)

clear_account_preserving_balance

Clear an account's nonce, code, and storage while preserving its balance.

Parameters

tx_state : The transaction state. address : Address of the account to modify.

def clear_account_preserving_balance(​tx_state: TransactionState, ​​address: Address​) -> None:
497
    <snip>
509
510
    def clear_account(account: Account) -> None:
511
        account.nonce = Uint(0)
512
        account.code_hash = EMPTY_CODE_HASH
513
514
    destroy_storage(tx_state, address)
515
    modify_state(tx_state, address, clear_account)

destroy_storage

Completely remove the storage at address.

Drop this transaction's writes and record the address inConvert storage writes to reads before deleting so that accesses from created-then-destroyed accounts appear in the Block Access List. Record the address in storage_clears so that reads no longer fall back to earlier so that reads no block writes or longer fall back to earlier block writes or pre_state.

Parameters

tx_state : The transaction state. address : Address of account whose storage is to be deleted.

def destroy_storage(​tx_state: TransactionState, ​​address: Address​) -> None:
519
    <snip>
535
    if address in tx_state.storage_writes:
431
        del tx_state.storage_writes[address]
536
        for key in tx_state.storage_writes[address]:
537
            tx_state.storage_reads.add((address, key))
538
        del tx_state.storage_writes[address]
539
    tx_state.storage_clears.add(address)

mark_account_created

Mark an account as having been created in the current transaction. This information is used by get_storage_original() to handle an obscure edgecase, and to respect the constraints added to SELFDESTRUCT by EIP-6780.

The marker is not removed even if the account creation reverts. Since the account cannot have had code prior to its creation and can't call get_storage_original(), this is harmless.

Parameters

tx_state : The transaction state. address : Address of the account that has been created.

def mark_account_created(​tx_state: TransactionState, ​​address: Address​) -> None:
543
    <snip>
561
    tx_state.created_accounts.add(address)

set_transient_storage

Set a value at a storage key on an account in transient storage.

Parameters

tx_state : The transaction state. address : Address of the account. key : Key to set. value : Value to set at the key.

def set_transient_storage(​tx_state: TransactionState, ​​address: Address, ​​key: Bytes32, ​​value: U256​) -> None:
570
    <snip>
585
    if value == U256(0):
586
        tx_state.transient_storage.pop((address, key), None)
587
    else:
588
        tx_state.transient_storage[(address, key)] = value

modify_state

Modify an Account in the state. If, after modification, the account exists and has zero nonce, empty code, and zero balance, it is destroyed.

def modify_state(​tx_state: TransactionState, ​​address: Address, ​​f: Callable[[Account], None]​) -> None:
596
    <snip>
601
    set_account(tx_state, address, modify(get_account(tx_state, address), f))
602
    if account_exists_and_is_empty(tx_state, address):
603
        destroy_account(tx_state, address)

move_ether

Move funds between accounts.

Parameters

tx_state : The transaction state. sender_address : Address of the sender. recipient_address : Address of the recipient. amount : The amount to transfer.

def move_ether(​tx_state: TransactionState, ​​sender_address: Address, ​​recipient_address: Address, ​​amount: U256​) -> None:
612
    <snip>
627
628
    def reduce_sender_balance(sender: Account) -> None:
629
        if sender.balance < amount:
630
            raise AssertionError
631
        sender.balance -= amount
632
633
    def increase_recipient_balance(recipient: Account) -> None:
634
        recipient.balance += amount
635
636
    modify_state(tx_state, sender_address, reduce_sender_balance)
637
    modify_state(tx_state, recipient_address, increase_recipient_balance)

create_ether

Add newly created ether to an account.

Parameters

tx_state : The transaction state. address : Address of the account to which ether is added. amount : The amount of ether to be added to the account of interest.

def create_ether(​tx_state: TransactionState, ​​address: Address, ​​amount: U256​) -> None:
643
    <snip>
656
657
    def increase_balance(account: Account) -> None:
658
        account.balance += amount
659
660
    modify_state(tx_state, address, increase_balance)

set_account_balance

Set the balance of an account.

Parameters

tx_state : The transaction state. address : Address of the account whose balance needs to be set. amount : The amount that needs to be set in the balance.

def set_account_balance(​tx_state: TransactionState, ​​address: Address, ​​amount: U256​) -> None:
666
    <snip>
679
680
    def set_balance(account: Account) -> None:
681
        account.balance = amount
682
683
    modify_state(tx_state, address, set_balance)

increment_nonce

Increment the nonce of an account.

Parameters

tx_state : The transaction state. address : Address of the account whose nonce needs to be incremented.

def increment_nonce(​tx_state: TransactionState, ​​address: Address​) -> None:
687
    <snip>
698
699
    def increase_nonce(sender: Account) -> None:
700
        sender.nonce += Uint(1)
701
702
    modify_state(tx_state, address, increase_nonce)

set_code

Set Account code.

Parameters

tx_state : The transaction state. address : Address of the account whose code needs to be updated. code : The bytecode that needs to be set.

def set_code(​tx_state: TransactionState, ​​address: Address, ​​code: Bytes​) -> None:
708
    <snip>
721
    code_hash = keccak256(code)
722
    if code_hash != EMPTY_CODE_HASH:
723
        tx_state.code_writes[code_hash] = code
724
725
    def write_code_hash(sender: Account) -> None:
726
        sender.code_hash = code_hash
727
728
    modify_state(tx_state, address, write_code_hash)

copy_tx_state

Create a snapshot of the transaction state for rollback.

Deep-copy writes and transient storage. The parent reference andDeep-copy writes and transient storage. The parent reference, created_accounts are shared (not rolled back)., storage_reads, and account_reads are shared (not rolled back).

Parameters

tx_state : The transaction state to snapshot.

Returns

snapshot : TransactionState A copy of the transaction state.

def copy_tx_state(​tx_state: TransactionState​) -> TransactionState:
735
    <snip>
753
    return TransactionState(
754
        parent=tx_state.parent,
755
        account_writes=dict(tx_state.account_writes),
756
        storage_writes={
757
            addr: dict(slots)
758
            for addr, slots in tx_state.storage_writes.items()
759
        },
760
        code_writes=dict(tx_state.code_writes),
761
        created_accounts=tx_state.created_accounts,
762
        storage_clears=set(tx_state.storage_clears),
763
        transient_storage=dict(tx_state.transient_storage),
764
        storage_reads=tx_state.storage_reads,
765
        account_reads=tx_state.account_reads,
766
    )

restore_tx_state

Restore transaction state from a snapshot (rollback on failure).

Parameters

tx_state : The transaction state to restore. snapshot : The snapshot to restore from.

def restore_tx_state(​tx_state: TransactionState, ​​snapshot: TransactionState​) -> None:
772
    <snip>
783
    tx_state.account_writes = snapshot.account_writes
784
    tx_state.storage_writes = snapshot.storage_writes
785
    tx_state.code_writes = snapshot.code_writes
786
    tx_state.storage_clears = snapshot.storage_clears
787
    tx_state.transient_storage = snapshot.transient_storage

incorporate_tx_into_block

Merge transaction writes into the block state and clear for reuse.

Update the BAL builder incrementally by diffing this transaction's writes against the state at the start of the current block access index. Merge reads and touches into block-level sets.

Parameters

tx_state : The transaction state to commit. builder : The BAL builder for incremental updates.

def incorporate_tx_into_block(​tx_state: TransactionState, ​​builder: "BlockAccessListBuilder"​) -> None:
797
    <snip>
812
    from .block_access_lists import update_builder_from_tx
813
814
    block = tx_state.parent
815
695
    for address, account in tx_state.account_writes.items():
816
    # Update BAL builder before merging writes into block state
817
    update_builder_from_tx(builder, tx_state)
818
819
    # Merge reads and touches into block-level sets
820
    block.storage_reads.update(tx_state.storage_reads)
821
    block.account_reads.update(tx_state.account_reads)
822
823
    # Merge cumulative writes
824
    for address, account in tx_state.account_writes.items():
825
        block.account_writes[address] = account
826
827
    for address in tx_state.storage_clears:
828
        block.storage_clears.add(address)
829
        block.storage_writes.pop(address, None)
830
831
    for address, slots in tx_state.storage_writes.items():
832
        if address not in block.storage_writes:
833
            block.storage_writes[address] = {}
834
        block.storage_writes[address].update(slots)
835
836
    block.code_writes.update(tx_state.code_writes)
837
838
    tx_state.account_writes.clear()
839
    tx_state.storage_writes.clear()
840
    tx_state.code_writes.clear()
841
    tx_state.created_accounts.clear()
842
    tx_state.storage_clears.clear()
714
    tx_state.transient_storage.clear()
843
    tx_state.transient_storage.clear()
844
    tx_state.storage_reads = set()
845
    tx_state.account_reads = set()

extract_block_diff

Extract account, storage, and code diff from the block state.

Parameters

block_state : The block state.

Returns

diff : BlockDiff Account, storage, and code changes accumulated during block execution.

def extract_block_diff(​block_state: BlockState​) -> BlockDiff:
849
    <snip>
863
    return BlockDiff(
864
        account_changes=block_state.account_writes,
865
        storage_changes=block_state.storage_writes,
866
        code_changes=block_state.code_writes,
867
        storage_clears=block_state.storage_clears,
868
    )