ethereum.forks.frontier.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.

storage_clears records addresses whose storage was wiped by a pre-EIP-6780 SELFDESTRUCT earlier in the block, so later reads must not fall back to pre_state storage.

39
@final
40
@dataclass
class BlockState:

pre_state

52
    pre_state: PreState

account_writes

53
    account_writes: Dict[Address, Optional[Account]] = field(
54
        default_factory=dict
55
    )

storage_writes

56
    storage_writes: Dict[Address, Dict[Bytes32, U256]] = field(
57
        default_factory=dict
58
    )

code_writes

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

storage_clears

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

63
@final
64
@dataclass
class TransactionState:

parent

72
    parent: BlockState

account_writes

73
    account_writes: Dict[Address, Optional[Account]] = field(
74
        default_factory=dict
75
    )

storage_writes

76
    storage_writes: Dict[Address, Dict[Bytes32, U256]] = field(
77
        default_factory=dict
78
    )

code_writes

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

created_accounts

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

storage_clears

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

transient_storage

82
    transient_storage: Dict[Tuple[Address, Bytes32], U256] = field(
83
        default_factory=dict
84
    )

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]:
90
    <snip>
107
    if address in tx_state.account_writes:
108
        return tx_state.account_writes[address]
109
    if address in tx_state.parent.account_writes:
110
        return tx_state.parent.account_writes[address]
111
    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:
115
    <snip>
135
    account = get_account_optional(tx_state, address)
136
    if account is None:
137
        return EMPTY_ACCOUNT
138
    else:
139
        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:
143
    <snip>
161
    if code_hash == EMPTY_CODE_HASH:
162
        return b""
163
    if code_hash in tx_state.code_writes:
164
        return tx_state.code_writes[code_hash]
165
    if code_hash in tx_state.parent.code_writes:
166
        return tx_state.parent.code_writes[code_hash]
167
    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:
173
    <snip>
192
    if address in tx_state.storage_writes:
193
        if key in tx_state.storage_writes[address]:
194
            return tx_state.storage_writes[address][key]
195
    if address in tx_state.storage_clears:
196
        return U256(0)
197
    if address in tx_state.parent.storage_writes:
198
        if key in tx_state.parent.storage_writes[address]:
199
            return tx_state.parent.storage_writes[address][key]
200
    if address in tx_state.parent.storage_clears:
201
        return U256(0)
202
    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:
208
    <snip>
224
    if address in tx_state.created_accounts:
225
        return U256(0)
226
    if address in tx_state.parent.storage_writes:
227
        if key in tx_state.parent.storage_writes[address]:
228
            return tx_state.parent.storage_writes[address][key]
229
    if address in tx_state.parent.storage_clears:
230
        return U256(0)
231
    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:
237
    <snip>
256
    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:
260
    <snip>
276
    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:
280
    <snip>
283
    account = get_account(tx_state, address)
284
    if account.nonce != Uint(0) or account.code_hash != EMPTY_CODE_HASH:
285
        return False
286
287
    if account_has_storage(tx_state, address):
288
        return False
289
290
    return True

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:
294
    <snip>
310
    if tx_state.storage_writes.get(address):
311
        return True
312
    if address in tx_state.storage_clears:
313
        return False
314
    if tx_state.parent.storage_writes.get(address):
315
        return True
316
    if address in tx_state.parent.storage_clears:
317
        return False
318
    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:
324
    <snip>
342
    account = get_account_optional(tx_state, address)
343
    return (
344
        account is not None
345
        and account.nonce == Uint(0)
346
        and account.code_hash == EMPTY_CODE_HASH
347
        and account.balance == 0
348
    )

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:
352
    <snip>
368
    account = get_account_optional(tx_state, address)
369
    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:
377
    <snip>
392
    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:
401
    <snip>
416
    assert get_account_optional(tx_state, address) is not None
417
    if address not in tx_state.storage_writes:
418
        tx_state.storage_writes[address] = {}
419
    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:
423
    <snip>
439
    destroy_storage(tx_state, address)
440
    set_account(tx_state, address, None)

destroy_storage

Completely remove the storage at address.

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:
444
    <snip>
457
    if address in tx_state.storage_writes:
458
        del tx_state.storage_writes[address]
459
    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:
463
    <snip>
481
    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:
490
    <snip>
505
    if value == U256(0):
506
        tx_state.transient_storage.pop((address, key), None)
507
    else:
508
        tx_state.transient_storage[(address, key)] = value

modify_state

Modify an Account in the state.

def modify_state(tx_state: TransactionState, ​​address: Address, ​​f: Callable[[Account], None]) -> None:
516
    <snip>
519
    set_account(tx_state, address, modify(get_account(tx_state, address), f))

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:
528
    <snip>
543
544
    def reduce_sender_balance(sender: Account) -> None:
545
        if sender.balance < amount:
546
            raise AssertionError
547
        sender.balance -= amount
548
549
    def increase_recipient_balance(recipient: Account) -> None:
550
        recipient.balance += amount
551
552
    modify_state(tx_state, sender_address, reduce_sender_balance)
553
    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:
559
    <snip>
572
573
    def increase_balance(account: Account) -> None:
574
        account.balance += amount
575
576
    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:
582
    <snip>
595
596
    def set_balance(account: Account) -> None:
597
        account.balance = amount
598
599
    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:
603
    <snip>
614
615
    def increase_nonce(sender: Account) -> None:
616
        sender.nonce += Uint(1)
617
618
    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:
624
    <snip>
637
    code_hash = keccak256(code)
638
    if code_hash != EMPTY_CODE_HASH:
639
        tx_state.code_writes[code_hash] = code
640
641
    def write_code_hash(sender: Account) -> None:
642
        sender.code_hash = code_hash
643
644
    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 and created_accounts 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:
651
    <snip>
668
    return TransactionState(
669
        parent=tx_state.parent,
670
        account_writes=dict(tx_state.account_writes),
671
        storage_writes={
672
            addr: dict(slots)
673
            for addr, slots in tx_state.storage_writes.items()
674
        },
675
        code_writes=dict(tx_state.code_writes),
676
        created_accounts=tx_state.created_accounts,
677
        storage_clears=set(tx_state.storage_clears),
678
        transient_storage=dict(tx_state.transient_storage),
679
    )

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:
685
    <snip>
696
    tx_state.account_writes = snapshot.account_writes
697
    tx_state.storage_writes = snapshot.storage_writes
698
    tx_state.code_writes = snapshot.code_writes
699
    tx_state.storage_clears = snapshot.storage_clears
700
    tx_state.transient_storage = snapshot.transient_storage

incorporate_tx_into_block

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

Parameters

tx_state : The transaction state to commit.

def incorporate_tx_into_block(tx_state: TransactionState) -> None:
707
    <snip>
716
    block = tx_state.parent
717
718
    for address, account in tx_state.account_writes.items():
719
        block.account_writes[address] = account
720
721
    for address in tx_state.storage_clears:
722
        block.storage_clears.add(address)
723
        block.storage_writes.pop(address, None)
724
725
    for address, slots in tx_state.storage_writes.items():
726
        if address not in block.storage_writes:
727
            block.storage_writes[address] = {}
728
        block.storage_writes[address].update(slots)
729
730
    block.code_writes.update(tx_state.code_writes)
731
732
    tx_state.account_writes.clear()
733
    tx_state.storage_writes.clear()
734
    tx_state.code_writes.clear()
735
    tx_state.created_accounts.clear()
736
    tx_state.storage_clears.clear()
737
    tx_state.transient_storage.clear()

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:
741
    <snip>
755
    return BlockDiff(
756
        account_changes=block_state.account_writes,
757
        storage_changes=block_state.storage_writes,
758
        code_changes=block_state.code_writes,
759
        storage_clears=block_state.storage_clears,
760
    )

destroy_touched_empty_accounts

Destroy all touched accounts that are empty.

Parameters

tx_state : The transaction state. touched_accounts : All the accounts that have been touched in the current transaction.

def destroy_touched_empty_accounts(tx_state: TransactionState, ​​touched_accounts: Set[Address]) -> None:
766
    <snip>
777
    for address in touched_accounts:
778
        if account_exists_and_is_empty(tx_state, address):
779
            destroy_account(tx_state, address)

touch_account

Initialize an account to state.

Parameters

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

def touch_account(tx_state: TransactionState, ​​address: Address) -> None:
783
    <snip>
794
    if not account_exists(tx_state, address):
795
        set_account(tx_state, address, EMPTY_ACCOUNT)