|
| 1 | +# Nimbus |
| 2 | +# Copyright (c) 2025 Status Research & Development GmbH |
| 3 | +# Licensed under either of |
| 4 | +# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE)) |
| 5 | +# * MIT license ([LICENSE-MIT](LICENSE-MIT)) |
| 6 | +# at your option. |
| 7 | +# This file may not be copied, modified, or distributed except according to |
| 8 | +# those terms. |
| 9 | + |
| 10 | +{.push raises: [], gcsafe.} |
| 11 | + |
| 12 | +import |
| 13 | + std/[tables, sets, algorithm], |
| 14 | + eth/common/[block_access_lists, block_access_lists_rlp], |
| 15 | + stint, |
| 16 | + stew/byteutils |
| 17 | + |
| 18 | +export block_access_lists |
| 19 | + |
| 20 | +type |
| 21 | + # Account data stored in the builder during block execution. This type tracks |
| 22 | + # all changes made to a single account throughout the execution of a block, |
| 23 | + # organized by the type of change and the block access list index where it |
| 24 | + # occurred. |
| 25 | + AccountData = object |
| 26 | + storageChanges: Table[UInt256, Table[int, UInt256]] |
| 27 | + ## Maps storage key -> block access list index -> storage value |
| 28 | + storageReads: HashSet[UInt256] |
| 29 | + ## Set of storage keys |
| 30 | + balanceChanges: Table[int, UInt256] |
| 31 | + ## Maps block access list index -> balance |
| 32 | + nonceChanges: Table[int, AccountNonce] |
| 33 | + ## Maps block access list index -> nonce |
| 34 | + codeChanges: Table[int, seq[byte]] |
| 35 | + ## Maps block access list index -> code |
| 36 | + |
| 37 | + # Builder for constructing a BlockAccessList efficiently during transaction |
| 38 | + # execution. The builder accumulates all account and storage accesses during |
| 39 | + # block execution and constructs a deterministic access list. Changes are |
| 40 | + # tracked by address, field type, and block access list index to enable |
| 41 | + # efficient reconstruction of state changes. |
| 42 | + BlockAccessListBuilderRef* = ref object |
| 43 | + accounts: Table[Address, AccountData] |
| 44 | + ## Maps address -> account data |
| 45 | + |
| 46 | +proc init*(T: type AccountData): T = |
| 47 | + AccountData() |
| 48 | + |
| 49 | +# Disallow copying of AccountData |
| 50 | +proc `=copy`(dest: var AccountData; src: AccountData) {.error: "Copying AccountData is forbidden".} = |
| 51 | + discard |
| 52 | + |
| 53 | +proc init*(T: type BlockAccessListBuilderRef): T = |
| 54 | + BlockAccessListBuilderRef() |
| 55 | + |
| 56 | +proc ensureAccount(builder: BlockAccessListBuilderRef, address: Address) = |
| 57 | + if address notin builder.accounts: |
| 58 | + builder.accounts[address] = AccountData.init() |
| 59 | + |
| 60 | +template addTouchedAccount*(builder: BlockAccessListBuilderRef, address: Address) = |
| 61 | + ensureAccount(builder, address) |
| 62 | + |
| 63 | +proc addStorageWrite*( |
| 64 | + builder: BlockAccessListBuilderRef, |
| 65 | + address: Address, |
| 66 | + slot: UInt256, |
| 67 | + blockAccessIndex: int, |
| 68 | + newValue: UInt256) = |
| 69 | + |
| 70 | + builder.ensureAccount(address) |
| 71 | + |
| 72 | + builder.accounts.withValue(address, accData): |
| 73 | + if slot notin accData[].storageChanges: |
| 74 | + accData[].storageChanges[slot] = default(Table[int, UInt256]) |
| 75 | + accData[].storageChanges.withValue(slot, slotChanges): |
| 76 | + slotChanges[][blockAccessIndex] = newValue |
| 77 | + |
| 78 | +proc addStorageRead*(builder: BlockAccessListBuilderRef, address: Address, slot: UInt256) = |
| 79 | + builder.ensureAccount(address) |
| 80 | + |
| 81 | + builder.accounts.withValue(address, accData): |
| 82 | + accData[].storageReads.incl(slot) |
| 83 | + |
| 84 | +proc addBalanceChange*( |
| 85 | + builder: BlockAccessListBuilderRef, |
| 86 | + address: Address, |
| 87 | + blockAccessIndex: int, |
| 88 | + postBalance: UInt256) = |
| 89 | + builder.ensureAccount(address) |
| 90 | + |
| 91 | + builder.accounts.withValue(address, accData): |
| 92 | + accData[].balanceChanges[blockAccessIndex] = postBalance |
| 93 | + |
| 94 | +proc addNonceChange*( |
| 95 | + builder: BlockAccessListBuilderRef, |
| 96 | + address: Address, |
| 97 | + blockAccessIndex: int, |
| 98 | + newNonce: AccountNonce) = |
| 99 | + builder.ensureAccount(address) |
| 100 | + |
| 101 | + builder.accounts.withValue(address, accData): |
| 102 | + accData[].nonceChanges[blockAccessIndex] = newNonce |
| 103 | + |
| 104 | +proc addCodeChange*( |
| 105 | + builder: BlockAccessListBuilderRef, |
| 106 | + address: Address, |
| 107 | + blockAccessIndex: int, |
| 108 | + newCode: seq[byte]) = |
| 109 | + builder.ensureAccount(address) |
| 110 | + |
| 111 | + builder.accounts.withValue(address, accData): |
| 112 | + accData[].codeChanges[blockAccessIndex] = newCode |
| 113 | + |
| 114 | +proc balIndexCmp(x, y: StorageChange | BalanceChange | NonceChange | CodeChange): int = |
| 115 | + cmp(x.blockAccessIndex, y.blockAccessIndex) |
| 116 | + |
| 117 | +proc slotCmp(x, y: SlotChanges): int = |
| 118 | + cmp(x.slot, y.slot) |
| 119 | + |
| 120 | +proc addressCmp(x, y: AccountChanges): int = |
| 121 | + cmp(x.address.data.toHex(), y.address.data.toHex()) |
| 122 | + |
| 123 | +proc buildBlockAccessList*(builder: BlockAccessListBuilderRef): BlockAccessList = |
| 124 | + var blockAccessList: BlockAccessList |
| 125 | + |
| 126 | + for address, accData in builder.accounts.mpairs(): |
| 127 | + # Collect and sort storageChanges |
| 128 | + var storageChanges: seq[SlotChanges] |
| 129 | + for slot, changes in accData.storageChanges: |
| 130 | + var slotChanges: seq[StorageChange] |
| 131 | + |
| 132 | + for balIndex, value in changes: |
| 133 | + slotChanges.add((BlockAccessIndex(balIndex), StorageValue(value))) |
| 134 | + slotChanges.sort(balIndexCmp) |
| 135 | + |
| 136 | + storageChanges.add((StorageKey(slot), slotChanges)) |
| 137 | + storageChanges.sort(slotCmp) |
| 138 | + |
| 139 | + # Collect and sort storageReads |
| 140 | + var storageReads: seq[StorageKey] |
| 141 | + for slot in accData.storageReads: |
| 142 | + if slot notin accData.storageChanges: |
| 143 | + storageReads.add(slot) |
| 144 | + storageReads.sort() |
| 145 | + |
| 146 | + # Collect and sort balanceChanges |
| 147 | + var balanceChanges: seq[BalanceChange] |
| 148 | + for balIndex, balance in accData.balanceChanges: |
| 149 | + balanceChanges.add((BlockAccessIndex(balIndex), Balance(balance))) |
| 150 | + balanceChanges.sort(balIndexCmp) |
| 151 | + |
| 152 | + # Collect and sort nonceChanges |
| 153 | + var nonceChanges: seq[NonceChange] |
| 154 | + for balIndex, nonce in accData.nonceChanges: |
| 155 | + nonceChanges.add((BlockAccessIndex(balIndex), Nonce(nonce))) |
| 156 | + nonceChanges.sort(balIndexCmp) |
| 157 | + |
| 158 | + # Collect and sort codeChanges |
| 159 | + var codeChanges: seq[CodeChange] |
| 160 | + for balIndex, code in accData.codeChanges: |
| 161 | + codeChanges.add((BlockAccessIndex(balIndex), CodeData(code))) |
| 162 | + codeChanges.sort(balIndexCmp) |
| 163 | + |
| 164 | + blockAccessList.add(AccountChanges( |
| 165 | + address: address, |
| 166 | + storageChanges: storageChanges, |
| 167 | + storageReads: storageReads, |
| 168 | + balanceChanges: balanceChanges, |
| 169 | + nonceChanges: nonceChanges, |
| 170 | + codeChanges: codeChanges |
| 171 | + )) |
| 172 | + |
| 173 | + blockAccessList.sort(addressCmp) |
| 174 | + |
| 175 | + blockAccessList |
0 commit comments