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

34
@dataclass
class BlockState:

pre_state

45
    pre_state: PreState

account_reads

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

account_writes

47
    account_writes: Dict[Address, Optional[Account]] = field(
48
        default_factory=dict
49
    )

storage_reads

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

storage_writes

51
    storage_writes: Dict[Address, Dict[Bytes32, U256]] = field(
52
        default_factory=dict
53
    )

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

56
@dataclass
class TransactionState:

parent

68
    parent: BlockState

account_reads

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

account_writes

70
    account_writes: Dict[Address, Optional[Account]] = field(
71
        default_factory=dict
72
    )

storage_reads

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

storage_writes

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

created_accounts

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

transient_storage

78
    transient_storage: Dict[Tuple[Address, Bytes32], U256] = field(
79
        default_factory=dict
80
    )

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]:
86
    """
87
    Get the ``Account`` object at an address. Return ``None`` (rather than
88
    ``EMPTY_ACCOUNT``) if there is no account at the address.
89
90
    Parameters
91
    ----------
92
    tx_state :
93
        The transaction state.
94
    address :
95
        Address to look up.
96
97
    Returns
98
    -------
99
    account : ``Optional[Account]``
100
        Account at address.
101
102
    """
103
    if address in tx_state.account_writes:
104
        return tx_state.account_writes[address]
105
    if address in tx_state.parent.account_writes:
106
        return tx_state.parent.account_writes[address]
107
    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:
111
    """
112
    Get the ``Account`` object at an address. Return ``EMPTY_ACCOUNT``
113
    if there is no account at the address.
114
115
    Use ``get_account_optional()`` if you care about the difference
116
    between a non-existent account and ``EMPTY_ACCOUNT``.
117
118
    Parameters
119
    ----------
120
    tx_state :
121
        The transaction state.
122
    address :
123
        Address to look up.
124
125
    Returns
126
    -------
127
    account : ``Account``
128
        Account at address.
129
130
    """
131
    account = get_account_optional(tx_state, address)
132
    if isinstance(account, Account):
133
        return account
134
    else:
135
        return EMPTY_ACCOUNT

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:
141
    """
142
    Get a value at a storage key on an account. Return ``U256(0)`` if
143
    the storage key has not been set previously.
144
145
    Parameters
146
    ----------
147
    tx_state :
148
        The transaction state.
149
    address :
150
        Address of the account.
151
    key :
152
        Key to look up.
153
154
    Returns
155
    -------
156
    value : ``U256``
157
        Value at the key.
158
159
    """
160
    if address in tx_state.storage_writes:
161
        if key in tx_state.storage_writes[address]:
162
            return tx_state.storage_writes[address][key]
163
    if address in tx_state.parent.storage_writes:
164
        if key in tx_state.parent.storage_writes[address]:
165
            return tx_state.parent.storage_writes[address][key]
166
    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:
172
    """
173
    Get the original value in a storage slot i.e. the value before the
174
    current transaction began. Read from block-level writes, then
175
    pre_state. Return ``U256(0)`` for accounts created in the current
176
    transaction.
177
178
    Parameters
179
    ----------
180
    tx_state :
181
        The transaction state.
182
    address :
183
        Address of the account to read the value from.
184
    key :
185
        Key of the storage slot.
186
187
    """
188
    if address in tx_state.created_accounts:
189
        return U256(0)
190
    if address in tx_state.parent.storage_writes:
191
        if key in tx_state.parent.storage_writes[address]:
192
            return tx_state.parent.storage_writes[address][key]
193
    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:
199
    """
200
    Get a value at a storage key on an account from transient storage.
201
    Return ``U256(0)`` if the storage key has not been set previously.
202
203
    Parameters
204
    ----------
205
    tx_state :
206
        The transaction state.
207
    address :
208
        Address of the account.
209
    key :
210
        Key to look up.
211
212
    Returns
213
    -------
214
    value : ``U256``
215
        Value at the key.
216
217
    """
218
    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:
222
    """
223
    Check if an account exists in the state trie.
224
225
    Parameters
226
    ----------
227
    tx_state :
228
        The transaction state.
229
    address :
230
        Address of the account that needs to be checked.
231
232
    Returns
233
    -------
234
    account_exists : ``bool``
235
        True if account exists in the state trie, False otherwise.
236
237
    """
238
    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:
244
    """
245
    Check if an account has non-zero nonce or non-empty code.
246
247
    Parameters
248
    ----------
249
    tx_state :
250
        The transaction state.
251
    address :
252
        Address of the account that needs to be checked.
253
254
    Returns
255
    -------
256
    has_code_or_nonce : ``bool``
257
        True if the account has non-zero nonce or non-empty code,
258
        False otherwise.
259
260
    """
261
    account = get_account(tx_state, address)
262
    return account.nonce != Uint(0) or account.code != b""

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:
266
    """
267
    Check if an account has storage.
268
269
    Parameters
270
    ----------
271
    tx_state :
272
        The transaction state.
273
    address :
274
        Address of the account that needs to be checked.
275
276
    Returns
277
    -------
278
    has_storage : ``bool``
279
        True if the account has storage, False otherwise.
280
281
    """
282
    if tx_state.storage_writes.get(address):
283
        return True
284
    if tx_state.parent.storage_writes.get(address):
285
        return True
286
    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:
292
    """
293
    Check if an account exists and has zero nonce, empty code and zero
294
    balance.
295
296
    Parameters
297
    ----------
298
    tx_state :
299
        The transaction state.
300
    address :
301
        Address of the account that needs to be checked.
302
303
    Returns
304
    -------
305
    exists_and_is_empty : ``bool``
306
        True if an account exists and has zero nonce, empty code and
307
        zero balance, False otherwise.
308
309
    """
310
    account = get_account_optional(tx_state, address)
311
    return (
312
        account is not None
313
        and account.nonce == Uint(0)
314
        and account.code == b""
315
        and account.balance == 0
316
    )

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:
320
    """
321
    Check whether an account is both in the state and non-empty.
322
323
    Parameters
324
    ----------
325
    tx_state :
326
        The transaction state.
327
    address :
328
        Address of the account that needs to be checked.
329
330
    Returns
331
    -------
332
    is_alive : ``bool``
333
        True if the account is alive.
334
335
    """
336
    account = get_account_optional(tx_state, address)
337
    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:
345
    """
346
    Set the ``Account`` object at an address. Setting to ``None``
347
    deletes the account (but not its storage, see
348
    ``destroy_account()``).
349
350
    Parameters
351
    ----------
352
    tx_state :
353
        The transaction state.
354
    address :
355
        Address to set.
356
    account :
357
        Account to set at address.
358
359
    """
360
    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:
369
    """
370
    Set a value at a storage key on an account.
371
372
    Parameters
373
    ----------
374
    tx_state :
375
        The transaction state.
376
    address :
377
        Address of the account.
378
    key :
379
        Key to set.
380
    value :
381
        Value to set at the key.
382
383
    """
384
    assert get_account_optional(tx_state, address) is not None
385
    if address not in tx_state.storage_writes:
386
        tx_state.storage_writes[address] = {}
387
    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:
391
    """
392
    Completely remove the account at ``address`` and all of its storage.
393
394
    This function is made available exclusively for the ``SELFDESTRUCT``
395
    opcode. It is expected that ``SELFDESTRUCT`` will be disabled in a
396
    future hardfork and this function will be removed. Only supports same
397
    transaction destruction.
398
399
    Parameters
400
    ----------
401
    tx_state :
402
        The transaction state.
403
    address :
404
        Address of account to destroy.
405
406
    """
407
    destroy_storage(tx_state, address)
408
    set_account(tx_state, address, None)

destroy_storage

Completely remove the storage at address.

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:
412
    """
413
    Completely remove the storage at ``address``.
414
415
    Convert storage writes to reads before deleting so that accesses
416
    from created-then-destroyed accounts appear in the Block Access
417
    List. Only supports same transaction destruction.
418
419
    Parameters
420
    ----------
421
    tx_state :
422
        The transaction state.
423
    address :
424
        Address of account whose storage is to be deleted.
425
426
    """
427
    if address in tx_state.storage_writes:
428
        for key in tx_state.storage_writes[address]:
429
            tx_state.storage_reads.add((address, key))
430
        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:
434
    """
435
    Mark an account as having been created in the current transaction.
436
    This information is used by ``get_storage_original()`` to handle an
437
    obscure edgecase, and to respect the constraints added to
438
    SELFDESTRUCT by EIP-6780.
439
440
    The marker is not removed even if the account creation reverts.
441
    Since the account cannot have had code prior to its creation and
442
    can't call ``get_storage_original()``, this is harmless.
443
444
    Parameters
445
    ----------
446
    tx_state :
447
        The transaction state.
448
    address :
449
        Address of the account that has been created.
450
451
    """
452
    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:
461
    """
462
    Set a value at a storage key on an account in transient storage.
463
464
    Parameters
465
    ----------
466
    tx_state :
467
        The transaction state.
468
    address :
469
        Address of the account.
470
    key :
471
        Key to set.
472
    value :
473
        Value to set at the key.
474
475
    """
476
    if value == U256(0):
477
        tx_state.transient_storage.pop((address, key), None)
478
    else:
479
        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:
487
    """
488
    Modify an ``Account`` in the state. If, after modification, the
489
    account exists and has zero nonce, empty code, and zero balance, it
490
    is destroyed.
491
    """
492
    set_account(tx_state, address, modify(get_account(tx_state, address), f))
493
    if account_exists_and_is_empty(tx_state, address):
494
        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:
503
    """
504
    Move funds between accounts.
505
506
    Parameters
507
    ----------
508
    tx_state :
509
        The transaction state.
510
    sender_address :
511
        Address of the sender.
512
    recipient_address :
513
        Address of the recipient.
514
    amount :
515
        The amount to transfer.
516
517
    """
518
519
    def reduce_sender_balance(sender: Account) -> None:
520
        if sender.balance < amount:
521
            raise AssertionError
522
        sender.balance -= amount
523
524
    def increase_recipient_balance(recipient: Account) -> None:
525
        recipient.balance += amount
526
527
    modify_state(tx_state, sender_address, reduce_sender_balance)
528
    modify_state(tx_state, recipient_address, increase_recipient_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:
534
    """
535
    Set the balance of an account.
536
537
    Parameters
538
    ----------
539
    tx_state :
540
        The transaction state.
541
    address :
542
        Address of the account whose balance needs to be set.
543
    amount :
544
        The amount that needs to be set in the balance.
545
546
    """
547
548
    def set_balance(account: Account) -> None:
549
        account.balance = amount
550
551
    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:
555
    """
556
    Increment the nonce of an account.
557
558
    Parameters
559
    ----------
560
    tx_state :
561
        The transaction state.
562
    address :
563
        Address of the account whose nonce needs to be incremented.
564
565
    """
566
567
    def increase_nonce(sender: Account) -> None:
568
        sender.nonce += Uint(1)
569
570
    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:
576
    """
577
    Set Account code.
578
579
    Parameters
580
    ----------
581
    tx_state :
582
        The transaction state.
583
    address :
584
        Address of the account whose code needs to be updated.
585
    code :
586
        The bytecode that needs to be set.
587
588
    """
589
590
    def write_code(sender: Account) -> None:
591
        sender.code = code
592
593
    modify_state(tx_state, address, write_code)

copy_tx_state

Create a snapshot of the transaction state for rollback.

Deep-copy writes and transient storage. The parent reference, created_accounts, 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:
600
    """
601
    Create a snapshot of the transaction state for rollback.
602
603
    Deep-copy writes and transient storage.  The parent reference,
604
    ``created_accounts``, ``storage_reads``, and ``account_reads``
605
    are shared (not rolled back).
606
607
    Parameters
608
    ----------
609
    tx_state :
610
        The transaction state to snapshot.
611
612
    Returns
613
    -------
614
    snapshot : ``TransactionState``
615
        A copy of the transaction state.
616
617
    """
618
    return TransactionState(
619
        parent=tx_state.parent,
620
        account_writes=dict(tx_state.account_writes),
621
        storage_writes={
622
            addr: dict(slots)
623
            for addr, slots in tx_state.storage_writes.items()
624
        },
625
        created_accounts=tx_state.created_accounts,
626
        transient_storage=dict(tx_state.transient_storage),
627
        storage_reads=tx_state.storage_reads,
628
        account_reads=tx_state.account_reads,
629
    )

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:
635
    """
636
    Restore transaction state from a snapshot (rollback on failure).
637
638
    Parameters
639
    ----------
640
    tx_state :
641
        The transaction state to restore.
642
    snapshot :
643
        The snapshot to restore from.
644
645
    """
646
    tx_state.account_writes = snapshot.account_writes
647
    tx_state.storage_writes = snapshot.storage_writes
648
    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:
658
    """
659
    Merge transaction writes into the block state and clear for reuse.
660
661
    Update the BAL builder incrementally by diffing this transaction's
662
    writes against the block's cumulative state.  Merge reads and
663
    touches into block-level sets.
664
665
    Parameters
666
    ----------
667
    tx_state :
668
        The transaction state to commit.
669
    builder :
670
        The BAL builder for incremental updates.
671
672
    """
673
    from .block_access_lists.builder import update_builder_from_tx
674
675
    block = tx_state.parent
676
677
    # Update BAL builder before merging writes into block state
678
    update_builder_from_tx(builder, tx_state)
679
680
    # Merge reads and touches into block-level sets
681
    block.storage_reads.update(tx_state.storage_reads)
682
    block.account_reads.update(tx_state.account_reads)
683
684
    # Merge cumulative writes
685
    for address, account in tx_state.account_writes.items():
686
        block.account_writes[address] = account
687
688
    for address, slots in tx_state.storage_writes.items():
689
        if address not in block.storage_writes:
690
            block.storage_writes[address] = {}
691
        block.storage_writes[address].update(slots)
692
693
    tx_state.account_writes.clear()
694
    tx_state.storage_writes.clear()
695
    tx_state.created_accounts.clear()
696
    tx_state.transient_storage.clear()
697
    tx_state.storage_reads = set()
698
    tx_state.account_reads = set()

extract_block_diffs

Extract account and storage diffs from the block state.

Parameters

block_state : The block state.

Returns

account_diffs : Account changes to apply. storage_diffs : Storage changes to apply.

def extract_block_diffs(block_state: BlockState) -> Tuple[Dict[Address, Optional[Account]], Dict[Address, Dict[Bytes32, U256]]]:
707
    """
708
    Extract account and storage diffs from the block state.
709
710
    Parameters
711
    ----------
712
    block_state :
713
        The block state.
714
715
    Returns
716
    -------
717
    account_diffs :
718
        Account changes to apply.
719
    storage_diffs :
720
        Storage changes to apply.
721
722
    """
723
    return block_state.account_writes, block_state.storage_writes

track_address

Record that an address was accessed.

Parameters

tx_state : The transaction state. address : The address that was accessed.

def track_address(tx_state: TransactionState, ​​address: Address) -> None:
730
    """
731
    Record that an address was accessed.
732
733
    Parameters
734
    ----------
735
    tx_state :
736
        The transaction state.
737
    address :
738
        The address that was accessed.
739
740
    """
741
    tx_state.account_reads.add(address)

track_storage_read

Record a storage read operation.

Parameters

tx_state : The transaction state. address : The address whose storage was read. key : The storage key that was read.

def track_storage_read(tx_state: TransactionState, ​​address: Address, ​​key: Bytes32) -> None:
747
    """
748
    Record a storage read operation.
749
750
    Parameters
751
    ----------
752
    tx_state :
753
        The transaction state.
754
    address :
755
        The address whose storage was read.
756
    key :
757
        The storage key that was read.
758
759
    """
760
    tx_state.storage_reads.add((address, key))