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_and_trie_changes().

.. 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.

42
@final
43
@dataclass
class BlockState:

pre_state

54
    pre_state: PreState

account_reads

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

account_writes

56
    account_writes: Dict[Address, Optional[Account]] = field(
57
        default_factory=dict
58
    )

storage_reads

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

storage_writes

60
    storage_writes: Dict[Address, Dict[Bytes32, U256]] = field(
61
        default_factory=dict
62
    )

code_writes

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

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).

66
@final
67
@dataclass
class TransactionState:

parent

79
    parent: BlockState

account_reads

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

account_writes

81
    account_writes: Dict[Address, Optional[Account]] = field(
82
        default_factory=dict
83
    )

storage_reads

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

storage_writes

85
    storage_writes: Dict[Address, Dict[Bytes32, U256]] = field(
86
        default_factory=dict
87
    )

code_writes

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

created_accounts

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

transient_storage

90
    transient_storage: Dict[Tuple[Address, Bytes32], U256] = field(
91
        default_factory=dict
92
    )

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]:
98
    <snip>
115
    tx_state.account_reads.add(address)
116
    if address in tx_state.account_writes:
117
        return tx_state.account_writes[address]
118
    if address in tx_state.parent.account_writes:
119
        return tx_state.parent.account_writes[address]
120
    return tx_state.parent.pre_state.get_account_optional(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:
124
    <snip>
144
    account = get_account_optional(tx_state, address)
145
    if account is None:
146
        return EMPTY_ACCOUNT
147
    else:
148
        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:
152
    <snip>
170
    if code_hash == EMPTY_CODE_HASH:
171
        return b""
172
    if code_hash in tx_state.code_writes:
173
        return tx_state.code_writes[code_hash]
174
    if code_hash in tx_state.parent.code_writes:
175
        return tx_state.parent.code_writes[code_hash]
176
    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:
182
    <snip>
201
    tx_state.storage_reads.add((address, key))
202
    if address in tx_state.storage_writes:
203
        if key in tx_state.storage_writes[address]:
204
            return tx_state.storage_writes[address][key]
205
    if address in tx_state.parent.storage_writes:
206
        if key in tx_state.parent.storage_writes[address]:
207
            return tx_state.parent.storage_writes[address][key]
208
    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:
214
    <snip>
230
    if address in tx_state.created_accounts:
231
        return U256(0)
232
    if address in tx_state.parent.storage_writes:
233
        if key in tx_state.parent.storage_writes[address]:
234
            return tx_state.parent.storage_writes[address][key]
235
    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:
241
    <snip>
260
    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:
264
    <snip>
280
    return get_account_optional(tx_state, address) is not None

account_has_code_or_nonce

Check if an account has non-zero nonce or non-empty code.

Parameters

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

Returns

has_code_or_nonce : bool True if the account has non-zero nonce or non-empty code, False otherwise.

def account_has_code_or_nonce(tx_state: TransactionState, ​​address: Address) -> bool:
286
    <snip>
303
    account = get_account(tx_state, address)
304
    return account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH

account_has_storage

Check if an account has storage.

Parameters

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

Returns

has_storage : bool True if the account has storage, False otherwise.

def account_has_storage(tx_state: TransactionState, ​​address: Address) -> bool:
308
    <snip>
324
    if tx_state.storage_writes.get(address):
325
        return True
326
    if tx_state.parent.storage_writes.get(address):
327
        return True
328
    return tx_state.parent.pre_state.account_has_storage(address)

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:
334
    <snip>
352
    account = get_account_optional(tx_state, address)
353
    return (
354
        account is not None
355
        and account.nonce == Uint(0)
356
        and account.code_hash == EMPTY_CODE_HASH
357
        and account.balance == 0
358
    )

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:
362
    <snip>
378
    account = get_account_optional(tx_state, address)
379
    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:
387
    <snip>
402
    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:
411
    <snip>
426
    assert get_account_optional(tx_state, address) is not None
427
    if address not in tx_state.storage_writes:
428
        tx_state.storage_writes[address] = {}
429
    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 SELFDESTRUCT opcode. It is expected that SELFDESTRUCT will be disabled in a future hardfork and this function will be removed. Only supports same transaction destruction.

Parameters

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

def destroy_account(tx_state: TransactionState, ​​address: Address) -> None:
433
    <snip>
449
    destroy_storage(tx_state, address)
450
    set_account(tx_state, address, None)

destroy_storage

Completely remove the storage at address.

Only supports same transaction destruction.Convert storage writes to reads before deleting so that accesses from created-then-destroyed accounts appear in the Block Access List. Only supports same transaction destruction.

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:
454
    <snip>
469
    if address in tx_state.storage_writes:
452
        del tx_state.storage_writes[address]
470
        for key in tx_state.storage_writes[address]:
471
            tx_state.storage_reads.add((address, key))
472
        del tx_state.storage_writes[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:
476
    <snip>
494
    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:
503
    <snip>
518
    if value == U256(0):
519
        tx_state.transient_storage.pop((address, key), None)
520
    else:
521
        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:
529
    <snip>
534
    set_account(tx_state, address, modify(get_account(tx_state, address), f))
535
    if account_exists_and_is_empty(tx_state, address):
536
        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:
545
    <snip>
560
561
    def reduce_sender_balance(sender: Account) -> None:
562
        if sender.balance < amount:
563
            raise AssertionError
564
        sender.balance -= amount
565
566
    def increase_recipient_balance(recipient: Account) -> None:
567
        recipient.balance += amount
568
569
    modify_state(tx_state, sender_address, reduce_sender_balance)
570
    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:
576
    <snip>
589
590
    def increase_balance(account: Account) -> None:
591
        account.balance += amount
592
593
    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:
599
    <snip>
612
613
    def set_balance(account: Account) -> None:
614
        account.balance = amount
615
616
    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:
620
    <snip>
631
632
    def increase_nonce(sender: Account) -> None:
633
        sender.nonce += Uint(1)
634
635
    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:
641
    <snip>
654
    code_hash = keccak256(code)
655
    if code_hash != EMPTY_CODE_HASH:
656
        tx_state.code_writes[code_hash] = code
657
658
    def write_code_hash(sender: Account) -> None:
659
        sender.code_hash = code_hash
660
661
    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:
668
    <snip>
686
    return TransactionState(
687
        parent=tx_state.parent,
688
        account_writes=dict(tx_state.account_writes),
689
        storage_writes={
690
            addr: dict(slots)
691
            for addr, slots in tx_state.storage_writes.items()
692
        },
693
        code_writes=dict(tx_state.code_writes),
694
        created_accounts=tx_state.created_accounts,
695
        transient_storage=dict(tx_state.transient_storage),
696
        storage_reads=tx_state.storage_reads,
697
        account_reads=tx_state.account_reads,
698
    )

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:
704
    <snip>
715
    tx_state.account_writes = snapshot.account_writes
716
    tx_state.storage_writes = snapshot.storage_writes
717
    tx_state.code_writes = snapshot.code_writes
718
    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 block's cumulative state. 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:
728
    <snip>
743
    from .block_access_lists import update_builder_from_tx
744
745
    block = tx_state.parent
746
713
    for address, account in tx_state.account_writes.items():
747
    # Update BAL builder before merging writes into block state
748
    update_builder_from_tx(builder, tx_state)
749
750
    # Merge reads and touches into block-level sets
751
    block.storage_reads.update(tx_state.storage_reads)
752
    block.account_reads.update(tx_state.account_reads)
753
754
    # Merge cumulative writes
755
    for address, account in tx_state.account_writes.items():
756
        block.account_writes[address] = account
757
758
    for address, slots in tx_state.storage_writes.items():
759
        if address not in block.storage_writes:
760
            block.storage_writes[address] = {}
761
        block.storage_writes[address].update(slots)
762
763
    block.code_writes.update(tx_state.code_writes)
764
765
    tx_state.account_writes.clear()
766
    tx_state.storage_writes.clear()
767
    tx_state.code_writes.clear()
768
    tx_state.created_accounts.clear()
727
    tx_state.transient_storage.clear()
769
    tx_state.transient_storage.clear()
770
    tx_state.storage_reads = set()
771
    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:
775
    <snip>
789
    return BlockDiff(
790
        account_changes=block_state.account_writes,
791
        storage_changes=block_state.storage_writes,
792
        code_changes=block_state.code_writes,
793
    )