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.
| 42 | @dataclass |
|---|
class BlockState:
pre_state¶
| 53 | pre_state: PreState |
|---|
account_reads¶
| 54 | account_reads: Set[Address] = field(default_factory=set) |
|---|
account_writes¶
| 55 | account_writes: Dict[Address, Optional[Account]] = field( |
|---|---|
| 56 | default_factory=dict |
| 57 | ) |
storage_reads¶
| 58 | storage_reads: Set[Tuple[Address, Bytes32]] = field(default_factory=set) |
|---|
storage_writes¶
| 59 | storage_writes: Dict[Address, Dict[Bytes32, U256]] = field( |
|---|---|
| 60 | default_factory=dict |
| 61 | ) |
code_writes¶
| 62 | 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).
| 65 | @dataclass |
|---|
class TransactionState:
parent¶
| 77 | parent: BlockState |
|---|
account_reads¶
| 78 | account_reads: Set[Address] = field(default_factory=set) |
|---|
account_writes¶
| 79 | account_writes: Dict[Address, Optional[Account]] = field( |
|---|---|
| 80 | default_factory=dict |
| 81 | ) |
storage_reads¶
| 82 | storage_reads: Set[Tuple[Address, Bytes32]] = field(default_factory=set) |
|---|
storage_writes¶
| 83 | storage_writes: Dict[Address, Dict[Bytes32, U256]] = field( |
|---|---|
| 84 | default_factory=dict |
| 85 | ) |
code_writes¶
| 86 | code_writes: Dict[Hash32, Bytes] = field(default_factory=dict) |
|---|
created_accounts¶
| 87 | created_accounts: Set[Address] = field(default_factory=set) |
|---|
transient_storage¶
| 88 | transient_storage: Dict[Tuple[Address, Bytes32], U256] = field( |
|---|---|
| 89 | default_factory=dict |
| 90 | ) |
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]:
| 96 | """ |
|---|---|
| 97 | Get the ``Account`` object at an address. Return ``None`` (rather than |
| 98 | ``EMPTY_ACCOUNT``) if there is no account at the address. |
| 99 | |
| 100 | Parameters |
| 101 | ---------- |
| 102 | tx_state : |
| 103 | The transaction state. |
| 104 | address : |
| 105 | Address to look up. |
| 106 | |
| 107 | Returns |
| 108 | ------- |
| 109 | account : ``Optional[Account]`` |
| 110 | Account at address. |
| 111 | |
| 112 | """ |
| 113 | tx_state.account_reads.add(address) |
| 114 | if address in tx_state.account_writes: |
| 115 | return tx_state.account_writes[address] |
| 116 | if address in tx_state.parent.account_writes: |
| 117 | return tx_state.parent.account_writes[address] |
| 118 | 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:
| 122 | """ |
|---|---|
| 123 | Get the ``Account`` object at an address. Return ``EMPTY_ACCOUNT`` |
| 124 | if there is no account at the address. |
| 125 | |
| 126 | Use ``get_account_optional()`` if you care about the difference |
| 127 | between a non-existent account and ``EMPTY_ACCOUNT``. |
| 128 | |
| 129 | Parameters |
| 130 | ---------- |
| 131 | tx_state : |
| 132 | The transaction state. |
| 133 | address : |
| 134 | Address to look up. |
| 135 | |
| 136 | Returns |
| 137 | ------- |
| 138 | account : ``Account`` |
| 139 | Account at address. |
| 140 | |
| 141 | """ |
| 142 | account = get_account_optional(tx_state, address) |
| 143 | if isinstance(account, Account): |
| 144 | return account |
| 145 | else: |
| 146 | return EMPTY_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:
| 150 | """ |
|---|---|
| 151 | Get the bytecode for a given code hash. |
| 152 | |
| 153 | Read chain: tx code_writes -> block code_writes -> pre_state. |
| 154 | |
| 155 | Parameters |
| 156 | ---------- |
| 157 | tx_state : |
| 158 | The transaction state. |
| 159 | code_hash : |
| 160 | Hash of the code to look up. |
| 161 | |
| 162 | Returns |
| 163 | ------- |
| 164 | code : ``Bytes`` |
| 165 | The bytecode. |
| 166 | |
| 167 | """ |
| 168 | if code_hash == EMPTY_CODE_HASH: |
| 169 | return b"" |
| 170 | if code_hash in tx_state.code_writes: |
| 171 | return tx_state.code_writes[code_hash] |
| 172 | if code_hash in tx_state.parent.code_writes: |
| 173 | return tx_state.parent.code_writes[code_hash] |
| 174 | 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:
| 180 | """ |
|---|---|
| 181 | Get a value at a storage key on an account. Return ``U256(0)`` if |
| 182 | the storage key has not been set previously. |
| 183 | |
| 184 | Parameters |
| 185 | ---------- |
| 186 | tx_state : |
| 187 | The transaction state. |
| 188 | address : |
| 189 | Address of the account. |
| 190 | key : |
| 191 | Key to look up. |
| 192 | |
| 193 | Returns |
| 194 | ------- |
| 195 | value : ``U256`` |
| 196 | Value at the key. |
| 197 | |
| 198 | """ |
| 199 | tx_state.storage_reads.add((address, key)) |
| 200 | if address in tx_state.storage_writes: |
| 201 | if key in tx_state.storage_writes[address]: |
| 202 | return tx_state.storage_writes[address][key] |
| 203 | if address in tx_state.parent.storage_writes: |
| 204 | if key in tx_state.parent.storage_writes[address]: |
| 205 | return tx_state.parent.storage_writes[address][key] |
| 206 | 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:
| 212 | """ |
|---|---|
| 213 | Get the original value in a storage slot i.e. the value before the |
| 214 | current transaction began. Read from block-level writes, then |
| 215 | pre_state. Return ``U256(0)`` for accounts created in the current |
| 216 | transaction. |
| 217 | |
| 218 | Parameters |
| 219 | ---------- |
| 220 | tx_state : |
| 221 | The transaction state. |
| 222 | address : |
| 223 | Address of the account to read the value from. |
| 224 | key : |
| 225 | Key of the storage slot. |
| 226 | |
| 227 | """ |
| 228 | if address in tx_state.created_accounts: |
| 229 | return U256(0) |
| 230 | if address in tx_state.parent.storage_writes: |
| 231 | if key in tx_state.parent.storage_writes[address]: |
| 232 | return tx_state.parent.storage_writes[address][key] |
| 233 | 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:
| 239 | """ |
|---|---|
| 240 | Get a value at a storage key on an account from transient storage. |
| 241 | Return ``U256(0)`` if the storage key has not been set previously. |
| 242 | |
| 243 | Parameters |
| 244 | ---------- |
| 245 | tx_state : |
| 246 | The transaction state. |
| 247 | address : |
| 248 | Address of the account. |
| 249 | key : |
| 250 | Key to look up. |
| 251 | |
| 252 | Returns |
| 253 | ------- |
| 254 | value : ``U256`` |
| 255 | Value at the key. |
| 256 | |
| 257 | """ |
| 258 | 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:
| 262 | """ |
|---|---|
| 263 | Check if an account exists in the state trie. |
| 264 | |
| 265 | Parameters |
| 266 | ---------- |
| 267 | tx_state : |
| 268 | The transaction state. |
| 269 | address : |
| 270 | Address of the account that needs to be checked. |
| 271 | |
| 272 | Returns |
| 273 | ------- |
| 274 | account_exists : ``bool`` |
| 275 | True if account exists in the state trie, False otherwise. |
| 276 | |
| 277 | """ |
| 278 | 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:
| 284 | """ |
|---|---|
| 285 | Check if an account has non-zero nonce or non-empty code. |
| 286 | |
| 287 | Parameters |
| 288 | ---------- |
| 289 | tx_state : |
| 290 | The transaction state. |
| 291 | address : |
| 292 | Address of the account that needs to be checked. |
| 293 | |
| 294 | Returns |
| 295 | ------- |
| 296 | has_code_or_nonce : ``bool`` |
| 297 | True if the account has non-zero nonce or non-empty code, |
| 298 | False otherwise. |
| 299 | |
| 300 | """ |
| 301 | account = get_account(tx_state, address) |
| 302 | 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:
| 306 | """ |
|---|---|
| 307 | Check if an account has storage. |
| 308 | |
| 309 | Parameters |
| 310 | ---------- |
| 311 | tx_state : |
| 312 | The transaction state. |
| 313 | address : |
| 314 | Address of the account that needs to be checked. |
| 315 | |
| 316 | Returns |
| 317 | ------- |
| 318 | has_storage : ``bool`` |
| 319 | True if the account has storage, False otherwise. |
| 320 | |
| 321 | """ |
| 322 | if tx_state.storage_writes.get(address): |
| 323 | return True |
| 324 | if tx_state.parent.storage_writes.get(address): |
| 325 | return True |
| 326 | 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:
| 332 | """ |
|---|---|
| 333 | Check if an account exists and has zero nonce, empty code and zero |
| 334 | balance. |
| 335 | |
| 336 | Parameters |
| 337 | ---------- |
| 338 | tx_state : |
| 339 | The transaction state. |
| 340 | address : |
| 341 | Address of the account that needs to be checked. |
| 342 | |
| 343 | Returns |
| 344 | ------- |
| 345 | exists_and_is_empty : ``bool`` |
| 346 | True if an account exists and has zero nonce, empty code and |
| 347 | zero balance, False otherwise. |
| 348 | |
| 349 | """ |
| 350 | account = get_account_optional(tx_state, address) |
| 351 | return ( |
| 352 | account is not None |
| 353 | and account.nonce == Uint(0) |
| 354 | and account.code_hash == EMPTY_CODE_HASH |
| 355 | and account.balance == 0 |
| 356 | ) |
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:
| 360 | """ |
|---|---|
| 361 | Check whether an account is both in the state and non-empty. |
| 362 | |
| 363 | Parameters |
| 364 | ---------- |
| 365 | tx_state : |
| 366 | The transaction state. |
| 367 | address : |
| 368 | Address of the account that needs to be checked. |
| 369 | |
| 370 | Returns |
| 371 | ------- |
| 372 | is_alive : ``bool`` |
| 373 | True if the account is alive. |
| 374 | |
| 375 | """ |
| 376 | account = get_account_optional(tx_state, address) |
| 377 | 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:
| 385 | """ |
|---|---|
| 386 | Set the ``Account`` object at an address. Setting to ``None`` |
| 387 | deletes the account (but not its storage, see |
| 388 | ``destroy_account()``). |
| 389 | |
| 390 | Parameters |
| 391 | ---------- |
| 392 | tx_state : |
| 393 | The transaction state. |
| 394 | address : |
| 395 | Address to set. |
| 396 | account : |
| 397 | Account to set at address. |
| 398 | |
| 399 | """ |
| 400 | 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:
| 409 | """ |
|---|---|
| 410 | Set a value at a storage key on an account. |
| 411 | |
| 412 | Parameters |
| 413 | ---------- |
| 414 | tx_state : |
| 415 | The transaction state. |
| 416 | address : |
| 417 | Address of the account. |
| 418 | key : |
| 419 | Key to set. |
| 420 | value : |
| 421 | Value to set at the key. |
| 422 | |
| 423 | """ |
| 424 | assert get_account_optional(tx_state, address) is not None |
| 425 | if address not in tx_state.storage_writes: |
| 426 | tx_state.storage_writes[address] = {} |
| 427 | 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:
| 431 | """ |
|---|---|
| 432 | Completely remove the account at ``address`` and all of its storage. |
| 433 | |
| 434 | This function is made available exclusively for the ``SELFDESTRUCT`` |
| 435 | opcode. It is expected that ``SELFDESTRUCT`` will be disabled in a |
| 436 | future hardfork and this function will be removed. Only supports same |
| 437 | transaction destruction. |
| 438 | |
| 439 | Parameters |
| 440 | ---------- |
| 441 | tx_state : |
| 442 | The transaction state. |
| 443 | address : |
| 444 | Address of account to destroy. |
| 445 | |
| 446 | """ |
| 447 | destroy_storage(tx_state, address) |
| 448 | 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:
| 452 | """ |
|---|---|
| 453 | Completely remove the storage at ``address``. |
| 454 | |
| 455 | Convert storage writes to reads before deleting so that accesses |
| 456 | from created-then-destroyed accounts appear in the Block Access |
| 457 | List. Only supports same transaction destruction. |
| 458 | |
| 459 | Parameters |
| 460 | ---------- |
| 461 | tx_state : |
| 462 | The transaction state. |
| 463 | address : |
| 464 | Address of account whose storage is to be deleted. |
| 465 | |
| 466 | """ |
| 467 | if address in tx_state.storage_writes: |
| 468 | for key in tx_state.storage_writes[address]: |
| 469 | tx_state.storage_reads.add((address, key)) |
| 470 | 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:
| 474 | """ |
|---|---|
| 475 | Mark an account as having been created in the current transaction. |
| 476 | This information is used by ``get_storage_original()`` to handle an |
| 477 | obscure edgecase, and to respect the constraints added to |
| 478 | SELFDESTRUCT by EIP-6780. |
| 479 | |
| 480 | The marker is not removed even if the account creation reverts. |
| 481 | Since the account cannot have had code prior to its creation and |
| 482 | can't call ``get_storage_original()``, this is harmless. |
| 483 | |
| 484 | Parameters |
| 485 | ---------- |
| 486 | tx_state : |
| 487 | The transaction state. |
| 488 | address : |
| 489 | Address of the account that has been created. |
| 490 | |
| 491 | """ |
| 492 | 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:
| 501 | """ |
|---|---|
| 502 | Set a value at a storage key on an account in transient storage. |
| 503 | |
| 504 | Parameters |
| 505 | ---------- |
| 506 | tx_state : |
| 507 | The transaction state. |
| 508 | address : |
| 509 | Address of the account. |
| 510 | key : |
| 511 | Key to set. |
| 512 | value : |
| 513 | Value to set at the key. |
| 514 | |
| 515 | """ |
| 516 | if value == U256(0): |
| 517 | tx_state.transient_storage.pop((address, key), None) |
| 518 | else: |
| 519 | 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:
| 527 | """ |
|---|---|
| 528 | Modify an ``Account`` in the state. If, after modification, the |
| 529 | account exists and has zero nonce, empty code, and zero balance, it |
| 530 | is destroyed. |
| 531 | """ |
| 532 | set_account(tx_state, address, modify(get_account(tx_state, address), f)) |
| 533 | if account_exists_and_is_empty(tx_state, address): |
| 534 | 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:
| 543 | """ |
|---|---|
| 544 | Move funds between accounts. |
| 545 | |
| 546 | Parameters |
| 547 | ---------- |
| 548 | tx_state : |
| 549 | The transaction state. |
| 550 | sender_address : |
| 551 | Address of the sender. |
| 552 | recipient_address : |
| 553 | Address of the recipient. |
| 554 | amount : |
| 555 | The amount to transfer. |
| 556 | |
| 557 | """ |
| 558 | |
| 559 | def reduce_sender_balance(sender: Account) -> None: |
| 560 | if sender.balance < amount: |
| 561 | raise AssertionError |
| 562 | sender.balance -= amount |
| 563 | |
| 564 | def increase_recipient_balance(recipient: Account) -> None: |
| 565 | recipient.balance += amount |
| 566 | |
| 567 | modify_state(tx_state, sender_address, reduce_sender_balance) |
| 568 | 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:
| 574 | """ |
|---|---|
| 575 | Set the balance of an account. |
| 576 | |
| 577 | Parameters |
| 578 | ---------- |
| 579 | tx_state : |
| 580 | The transaction state. |
| 581 | address : |
| 582 | Address of the account whose balance needs to be set. |
| 583 | amount : |
| 584 | The amount that needs to be set in the balance. |
| 585 | |
| 586 | """ |
| 587 | |
| 588 | def set_balance(account: Account) -> None: |
| 589 | account.balance = amount |
| 590 | |
| 591 | 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:
| 595 | """ |
|---|---|
| 596 | Increment the nonce of an account. |
| 597 | |
| 598 | Parameters |
| 599 | ---------- |
| 600 | tx_state : |
| 601 | The transaction state. |
| 602 | address : |
| 603 | Address of the account whose nonce needs to be incremented. |
| 604 | |
| 605 | """ |
| 606 | |
| 607 | def increase_nonce(sender: Account) -> None: |
| 608 | sender.nonce += Uint(1) |
| 609 | |
| 610 | 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:
| 616 | """ |
|---|---|
| 617 | Set Account code. |
| 618 | |
| 619 | Parameters |
| 620 | ---------- |
| 621 | tx_state : |
| 622 | The transaction state. |
| 623 | address : |
| 624 | Address of the account whose code needs to be updated. |
| 625 | code : |
| 626 | The bytecode that needs to be set. |
| 627 | |
| 628 | """ |
| 629 | code_hash = keccak256(code) |
| 630 | if code_hash != EMPTY_CODE_HASH: |
| 631 | tx_state.code_writes[code_hash] = code |
| 632 | |
| 633 | def write_code_hash(sender: Account) -> None: |
| 634 | sender.code_hash = code_hash |
| 635 | |
| 636 | modify_state(tx_state, address, write_code_hash) |
storage_at_snapshot ¶
Return the value of a storage slot as recorded in snapshot.
The snapshot is taken at frame entry, so this is the slot's
frame-entry value. Read chain: snapshot writes -> block writes ->
pre_state. Returns U256(0) for accounts the snapshot already
knew were created in the current transaction.
def storage_at_snapshot(snapshot: TransactionState, address: Address, key: Bytes32) -> U256:
| 645 | """ |
|---|---|
| 646 | Return the value of a storage slot as recorded in ``snapshot``. |
| 647 | |
| 648 | The snapshot is taken at frame entry, so this is the slot's |
| 649 | frame-entry value. Read chain: snapshot writes -> block writes -> |
| 650 | pre_state. Returns ``U256(0)`` for accounts the snapshot already |
| 651 | knew were created in the current transaction. |
| 652 | """ |
| 653 | if address in snapshot.storage_writes: |
| 654 | if key in snapshot.storage_writes[address]: |
| 655 | return snapshot.storage_writes[address][key] |
| 656 | if address in snapshot.parent.storage_writes: |
| 657 | if key in snapshot.parent.storage_writes[address]: |
| 658 | return snapshot.parent.storage_writes[address][key] |
| 659 | if address in snapshot.created_accounts: |
| 660 | return U256(0) |
| 661 | return snapshot.parent.pre_state.get_storage(address, key) |
account_at_snapshot ¶
Return the Account object at address in snapshot
(i.e., at frame entry), or None if no account existed there.
def account_at_snapshot(snapshot: TransactionState, address: Address) -> Optional[Account]:
| 667 | """ |
|---|---|
| 668 | Return the ``Account`` object at ``address`` in ``snapshot`` |
| 669 | (i.e., at frame entry), or ``None`` if no account existed there. |
| 670 | """ |
| 671 | if address in snapshot.account_writes: |
| 672 | return snapshot.account_writes[address] |
| 673 | if address in snapshot.parent.account_writes: |
| 674 | return snapshot.parent.account_writes[address] |
| 675 | return snapshot.parent.pre_state.get_account_optional(address) |
account_existed_at_tx_entry ¶
Return whether an account existed at the start of the current transaction (i.e., before any tx-level writes).
def account_existed_at_tx_entry(tx_state: TransactionState, address: Address) -> bool:
| 681 | """ |
|---|---|
| 682 | Return whether an account existed at the start of the current |
| 683 | transaction (i.e., before any tx-level writes). |
| 684 | """ |
| 685 | if address in tx_state.created_accounts: |
| 686 | return False |
| 687 | if address in tx_state.parent.account_writes: |
| 688 | return tx_state.parent.account_writes[address] is not None |
| 689 | return tx_state.parent.pre_state.get_account_optional(address) is not None |
compute_state_byte_diff ¶
Compute the signed net byte delta between snapshot (frame
entry) and tx_state (frame exit) for EIP-8037 state-gas
accounting. Multiply the return value by CPSB to obtain gas.
Storage slots use the EIP-8037 four-case rule with both the
frame-entry value (from snapshot) and the transaction-entry
value (via get_storage_original):
New slot (exit non-zero, frame-entry zero, tx-entry zero): +32
Cleared slot, zero at tx start (exit zero, frame-entry non-zero, tx-entry zero): -32
Cleared slot, non-zero at tx start: 0 (the regular-gas
refund_counterpath remains inline at SSTORE per EIP-3529)Other transitions: 0
Accounts: +112 for each new account (exists at exit, didn't exist
at frame entry, didn't exist at tx entry). -112 for each account
removed via destroy_account whose pre-existence held at both
frame entry and tx entry.
Code: +len(code) per account whose code_hash transitioned
from EMPTY_CODE_HASH to non-empty since the snapshot. Keying
by account (not by code hash) ensures two accounts deploying
identical bytecode each pay for their own code deposit.
def compute_state_byte_diff(snapshot: TransactionState, tx_state: TransactionState) -> int:
| 695 | """ |
|---|---|
| 696 | Compute the signed net byte delta between ``snapshot`` (frame |
| 697 | entry) and ``tx_state`` (frame exit) for EIP-8037 state-gas |
| 698 | accounting. Multiply the return value by ``CPSB`` to obtain gas. |
| 699 | |
| 700 | Storage slots use the EIP-8037 four-case rule with both the |
| 701 | frame-entry value (from ``snapshot``) and the transaction-entry |
| 702 | value (via ``get_storage_original``): |
| 703 | |
| 704 | * New slot (exit non-zero, frame-entry zero, tx-entry zero): +32 |
| 705 | * Cleared slot, zero at tx start (exit zero, frame-entry |
| 706 | non-zero, tx-entry zero): -32 |
| 707 | * Cleared slot, non-zero at tx start: 0 (the regular-gas |
| 708 | ``refund_counter`` path remains inline at SSTORE per |
| 709 | EIP-3529) |
| 710 | * Other transitions: 0 |
| 711 | |
| 712 | Accounts: +112 for each new account (exists at exit, didn't exist |
| 713 | at frame entry, didn't exist at tx entry). -112 for each account |
| 714 | removed via ``destroy_account`` whose pre-existence held at both |
| 715 | frame entry and tx entry. |
| 716 | |
| 717 | Code: +len(code) per account whose ``code_hash`` transitioned |
| 718 | from ``EMPTY_CODE_HASH`` to non-empty since the snapshot. Keying |
| 719 | by account (not by code hash) ensures two accounts deploying |
| 720 | identical bytecode each pay for their own code deposit. |
| 721 | """ |
| 722 | delta = 0 |
| 723 | |
| 724 | for address, slots in tx_state.storage_writes.items(): |
| 725 | for key, value_now in slots.items(): |
| 726 | frame_entry = storage_at_snapshot(snapshot, address, key) |
| 727 | tx_entry = get_storage_original(tx_state, address, key) |
| 728 | if ( |
| 729 | value_now != U256(0) |
| 730 | and frame_entry == U256(0) |
| 731 | and tx_entry == U256(0) |
| 732 | ): |
| 733 | delta += 32 |
| 734 | elif ( |
| 735 | value_now == U256(0) |
| 736 | and frame_entry != U256(0) |
| 737 | and tx_entry == U256(0) |
| 738 | ): |
| 739 | delta -= 32 |
| 740 | |
| 741 | for address, slots in snapshot.storage_writes.items(): |
| 742 | if address in tx_state.storage_writes: |
| 743 | continue |
| 744 | for key, frame_entry in slots.items(): |
| 745 | if frame_entry == U256(0): |
| 746 | continue |
| 747 | tx_entry = get_storage_original(tx_state, address, key) |
| 748 | if tx_entry == U256(0): |
| 749 | delta -= 32 |
| 750 | |
| 751 | for address, account_now in tx_state.account_writes.items(): |
| 752 | snapshot_account = account_at_snapshot(snapshot, address) |
| 753 | existed_at_frame_entry = snapshot_account is not None |
| 754 | existed_at_tx_entry = account_existed_at_tx_entry(tx_state, address) |
| 755 | |
| 756 | if ( |
| 757 | account_now is not None |
| 758 | and not existed_at_frame_entry |
| 759 | and not existed_at_tx_entry |
| 760 | ): |
| 761 | delta += 112 |
| 762 | elif ( |
| 763 | account_now is None |
| 764 | and existed_at_frame_entry |
| 765 | and existed_at_tx_entry |
| 766 | ): |
| 767 | delta -= 112 |
| 768 | |
| 769 | # Code deposit, keyed per-account: two accounts deploying the |
| 770 | # same bytecode share one `code_writes` entry, so iterating |
| 771 | # `code_writes` would only charge once. |
| 772 | if account_now is None or account_now.code_hash == EMPTY_CODE_HASH: |
| 773 | continue |
| 774 | code_hash_at_frame_entry = ( |
| 775 | snapshot_account.code_hash |
| 776 | if snapshot_account is not None |
| 777 | else EMPTY_CODE_HASH |
| 778 | ) |
| 779 | if code_hash_at_frame_entry != account_now.code_hash: |
| 780 | delta += len(get_code(tx_state, account_now.code_hash)) |
| 781 | |
| 782 | return delta |
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:
| 786 | """ |
|---|---|
| 787 | Create a snapshot of the transaction state for rollback. |
| 788 | |
| 789 | Deep-copy writes and transient storage. The parent reference, |
| 790 | ``created_accounts``, ``storage_reads``, and ``account_reads`` |
| 791 | are shared (not rolled back). |
| 792 | |
| 793 | Parameters |
| 794 | ---------- |
| 795 | tx_state : |
| 796 | The transaction state to snapshot. |
| 797 | |
| 798 | Returns |
| 799 | ------- |
| 800 | snapshot : ``TransactionState`` |
| 801 | A copy of the transaction state. |
| 802 | |
| 803 | """ |
| 804 | return TransactionState( |
| 805 | parent=tx_state.parent, |
| 806 | account_writes=dict(tx_state.account_writes), |
| 807 | storage_writes={ |
| 808 | addr: dict(slots) |
| 809 | for addr, slots in tx_state.storage_writes.items() |
| 810 | }, |
| 811 | code_writes=dict(tx_state.code_writes), |
| 812 | created_accounts=set(tx_state.created_accounts), |
| 813 | transient_storage=dict(tx_state.transient_storage), |
| 814 | storage_reads=tx_state.storage_reads, |
| 815 | account_reads=tx_state.account_reads, |
| 816 | ) |
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:
| 822 | """ |
|---|---|
| 823 | Restore transaction state from a snapshot (rollback on failure). |
| 824 | |
| 825 | Parameters |
| 826 | ---------- |
| 827 | tx_state : |
| 828 | The transaction state to restore. |
| 829 | snapshot : |
| 830 | The snapshot to restore from. |
| 831 | |
| 832 | """ |
| 833 | tx_state.account_writes = snapshot.account_writes |
| 834 | tx_state.storage_writes = snapshot.storage_writes |
| 835 | tx_state.code_writes = snapshot.code_writes |
| 836 | 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:
| 846 | """ |
|---|---|
| 847 | Merge transaction writes into the block state and clear for reuse. |
| 848 | |
| 849 | Update the BAL builder incrementally by diffing this transaction's |
| 850 | writes against the block's cumulative state. Merge reads and |
| 851 | touches into block-level sets. |
| 852 | |
| 853 | Parameters |
| 854 | ---------- |
| 855 | tx_state : |
| 856 | The transaction state to commit. |
| 857 | builder : |
| 858 | The BAL builder for incremental updates. |
| 859 | |
| 860 | """ |
| 861 | from .block_access_lists import update_builder_from_tx |
| 862 | |
| 863 | block = tx_state.parent |
| 864 | |
| 865 | # Update BAL builder before merging writes into block state |
| 866 | update_builder_from_tx(builder, tx_state) |
| 867 | |
| 868 | # Merge reads and touches into block-level sets |
| 869 | block.storage_reads.update(tx_state.storage_reads) |
| 870 | block.account_reads.update(tx_state.account_reads) |
| 871 | |
| 872 | # Merge cumulative writes |
| 873 | for address, account in tx_state.account_writes.items(): |
| 874 | block.account_writes[address] = account |
| 875 | |
| 876 | for address, slots in tx_state.storage_writes.items(): |
| 877 | if address not in block.storage_writes: |
| 878 | block.storage_writes[address] = {} |
| 879 | block.storage_writes[address].update(slots) |
| 880 | |
| 881 | block.code_writes.update(tx_state.code_writes) |
| 882 | |
| 883 | tx_state.account_writes.clear() |
| 884 | tx_state.storage_writes.clear() |
| 885 | tx_state.code_writes.clear() |
| 886 | tx_state.created_accounts.clear() |
| 887 | tx_state.transient_storage.clear() |
| 888 | tx_state.storage_reads = set() |
| 889 | 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:
| 893 | """ |
|---|---|
| 894 | Extract account, storage, and code diff from the block state. |
| 895 | |
| 896 | Parameters |
| 897 | ---------- |
| 898 | block_state : |
| 899 | The block state. |
| 900 | |
| 901 | Returns |
| 902 | ------- |
| 903 | diff : `BlockDiff` |
| 904 | Account, storage, and code changes accumulated during block execution. |
| 905 | |
| 906 | """ |
| 907 | return BlockDiff( |
| 908 | account_changes=block_state.account_writes, |
| 909 | storage_changes=block_state.storage_writes, |
| 910 | code_changes=block_state.code_writes, |
| 911 | ) |