Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 164 additions & 45 deletions execution_chain/block_access_list/block_access_list_tracker.nim

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions execution_chain/core/chain/forked_chain.nim
Original file line number Diff line number Diff line change
Expand Up @@ -514,8 +514,6 @@ proc validateBlock(c: ForkedChainRef,
txFrame.dispose()
return err(error)

c.writeBaggage(blk, blkHash, txFrame, receipts)

# Checkpoint creates a snapshot of ancestor changes in txFrame - it is an
# expensive operation, specially when creating a new branch (ie when blk
# is being applied to a block that is currently not a head).
Expand Down
14 changes: 11 additions & 3 deletions execution_chain/core/chain/forked_chain/chain_private.nim
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import
proc writeBaggage*(c: ForkedChainRef,
blk: Block, blkHash: Hash32,
txFrame: CoreDbTxRef,
receipts: openArray[StoredReceipt]) =
receipts: openArray[StoredReceipt],
blockAccessList: Opt[BlockAccessList]) =
template header(): Header =
blk.header

Expand All @@ -34,7 +35,7 @@ proc writeBaggage*(c: ForkedChainRef,
txFrame.persistWithdrawals(
header.withdrawalsRoot.expect("WithdrawalsRoot should be verified before"),
blk.withdrawals.get)
if blk.blockAccessList.isSome:
if blockAccessList.isSome:
txFrame.persistBlockAccessList(
header.blockAccessListHash.expect("blockAccessListHash should be verified before"),
blk.blockAccessList.get)
Expand All @@ -49,7 +50,12 @@ proc processBlock*(c: ForkedChainRef,
blk.header

let vmState = BaseVMState()
vmState.init(parentBlk.header, header, c.com, txFrame)
vmState.init(
parentBlk.header,
header,
c.com,
txFrame,
enableBalTracker = c.com.isAmsterdamOrLater(header.timestamp))

?c.com.validateHeaderAndKinship(blk, vmState.parent, txFrame)

Expand Down Expand Up @@ -93,4 +99,6 @@ proc processBlock*(c: ForkedChainRef,
# because validateUncles still need it
?txFrame.persistHeader(blkHash, header, c.com.startOfHistory)

c.writeBaggage(blk, blkHash, txFrame, vmState.receipts, vmState.blockAccessList)

ok(move(vmState.receipts))
2 changes: 0 additions & 2 deletions execution_chain/core/chain/forked_chain/chain_serialize.nim
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,6 @@ proc replayBlock(fc: ForkedChainRef;
txFrame.dispose()
return err(error)

fc.writeBaggage(blk.blk, blk.hash, txFrame, receipts)

# Checkpoint creates a snapshot of ancestor changes in txFrame - it is an
# expensive operation, specially when creating a new branch (ie when blk
# is being applied to a block that is currently not a head).
Expand Down
42 changes: 36 additions & 6 deletions execution_chain/core/executor/process_block.nim
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ proc processTransactions*(
if sender == default(Address):
return err("Could not get sender for tx with index " & $(txIndex))

if vmState.balTrackerEnabled:
vmState.balTracker.setBlockAccessIndex(txIndex + 1)

let rc = vmState.processTransaction(tx, sender, header)
if rc.isErr:
return err("Error processing tx with index " & $(txIndex) & ":" & rc.error)
Expand All @@ -110,6 +113,11 @@ proc procBlkPreamble(
template header(): Header =
blk.header

# Setup block access list tracker for pre‑execution system calls
if vmState.balTrackerEnabled:
vmState.balTracker.setBlockAccessIndex(0)
vmState.balTracker.beginCallFrame()

let com = vmState.com
if com.daoForkSupport and com.daoForkBlock.get == header.number:
vmState.mutateLedger:
Expand Down Expand Up @@ -144,17 +152,19 @@ proc procBlkPreamble(
if com.isAmsterdamOrLater(header.timestamp):
if header.blockAccessListHash.isNone:
return err("Post-Amsterdam block header must have blockAccessListHash")
elif blk.blockAccessList.isNone:
return err("Post-Amsterdam block body must have blockAccessList")
elif not skipValidation:
if not skipValidation and blk.blockAccessList.isSome:
if blk.blockAccessList.get.validate(header.blockAccessListHash.get).isErr():
return err("Mismatched blockAccessListHash")
else:
if header.blockAccessListHash.isSome:
return err("Pre-Amsterdam block header must not have blockAccessListHash")
elif blk.blockAccessList.isSome:
if blk.blockAccessList.isSome:
return err("Pre-Amsterdam block body must not have blockAccessList")

# Commit block access list tracker changes for pre‑execution system calls
if vmState.balTrackerEnabled:
vmState.balTracker.commitCallFrame()

if header.txRoot != EMPTY_ROOT_HASH:
if blk.transactions.len == 0:
return err("Transactions missing from body")
Expand All @@ -166,14 +176,24 @@ proc procBlkPreamble(
elif blk.transactions.len > 0:
return err("Transactions in block with empty txRoot")

# Setup block access list tracker for post‑execution system calls
if vmState.balTrackerEnabled:
vmState.balTracker.setBlockAccessIndex(blk.transactions.len() + 1)
vmState.balTracker.beginCallFrame()

if com.isShanghaiOrLater(header.timestamp):
if header.withdrawalsRoot.isNone:
return err("Post-Shanghai block header must have withdrawalsRoot")
if blk.withdrawals.isNone:
return err("Post-Shanghai block body must have withdrawals")

for withdrawal in blk.withdrawals.get:
vmState.ledger.addBalance(withdrawal.address, withdrawal.weiAmount)
if vmState.balTrackerEnabled:
for withdrawal in blk.withdrawals.get:
vmState.balTracker.trackAddBalanceChange(withdrawal.address, withdrawal.weiAmount)
vmState.ledger.addBalance(withdrawal.address, withdrawal.weiAmount)
else:
for withdrawal in blk.withdrawals.get:
vmState.ledger.addBalance(withdrawal.address, withdrawal.weiAmount)
else:
if header.withdrawalsRoot.isSome:
return err("Pre-Shanghai block header must not have withdrawalsRoot")
Expand Down Expand Up @@ -229,6 +249,16 @@ proc procBlkEpilogue(
withdrawalReqs = ?processDequeueWithdrawalRequests(vmState)
consolidationReqs = ?processDequeueConsolidationRequests(vmState)

if vmState.balTrackerEnabled:
# Commit block access list tracker changes for post‑execution system calls
vmState.balTracker.commitCallFrame()

if header.blockAccessListHash.isSome():
let bal = vmState.balTracker.getBlockAccessList().get()
bal.validate(header.blockAccessListHash.get).isOkOr:
return err("block access list mismatch, expect: " &
$header.blockAccessListHash.get & ", got: " & $bal.computeBlockAccessListHash())

if not skipStateRootCheck:
let stateRoot = vmState.ledger.getStateRoot()
if header.stateRoot != stateRoot:
Expand Down
17 changes: 12 additions & 5 deletions execution_chain/core/executor/process_transaction.nim
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,15 @@ proc commitOrRollbackDependingOnGasUsed(
# an early stop. It would rather detect differing values for the block
# header `gasUsed` and the `vmState.cumulativeGasUsed` at a later stage.
if header.gasLimit < vmState.cumulativeGasUsed + gasUsed:
if vmState.balTrackerEnabled:
vmState.balTracker.rollbackCallFrame()
vmState.ledger.rollback(accTx)
err(&"invalid tx: block header gasLimit reached. gasLimit={header.gasLimit}, gasUsed={vmState.cumulativeGasUsed}, addition={gasUsed}")
else:
# Accept transaction and collect mining fee.
if vmState.balTrackerEnabled:
vmState.balTracker.trackAddBalanceChange(vmState.coinbase(), gasUsed.u256 * priorityFee.u256)
vmState.balTracker.commitCallFrame()
vmState.ledger.commit(accTx)
vmState.ledger.addBalance(vmState.coinbase(), gasUsed.u256 * priorityFee.u256)
vmState.cumulativeGasUsed += gasUsed
Expand Down Expand Up @@ -108,13 +113,15 @@ proc processTransactionImpl(
let
com = vmState.com
txRes = roDB.validateTransaction(tx, sender, header.gasLimit, baseFee256, excessBlobGas, com, fork)
res = if txRes.isOk:
res = if txRes.isOk:
# Execute the transaction.
vmState.captureTxStart(tx.gasLimit)
let
accTx = vmState.ledger.beginSavepoint
var
callResult = tx.txCallEvm(sender, vmState, baseFee)

if vmState.balTrackerEnabled:
vmState.balTracker.beginCallFrame()
let accTx = vmState.ledger.beginSavepoint()

var callResult = tx.txCallEvm(sender, vmState, baseFee)
vmState.captureTxEnd(tx.gasLimit - callResult.gasUsed)

let tmp = commitOrRollbackDependingOnGasUsed(
Expand Down
6 changes: 2 additions & 4 deletions execution_chain/core/validate.nim
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,13 @@ func validateBlockAccessList*(
if com.isAmsterdamOrLater(header.timestamp):
if header.blockAccessListHash.isNone:
return err("Post-Amsterdam block header must have blockAccessListHash")
elif blockAccessList.isNone:
return err("Post-Amsterdam block body must have blockAccessList")
else:
if blockAccessList.isSome:
if blockAccessList.get.validate(header.blockAccessListHash.get).isErr():
return err("Mismatched blockAccessListHash blockNumber = " & $header.number)
else:
if header.blockAccessListHash.isSome:
return err("Pre-Amsterdam block header must not have blockAccessListHash")
elif blockAccessList.isSome:
if blockAccessList.isSome:
return err("Pre-Amsterdam block body must not have blockAccessList")

return ok()
Expand Down
7 changes: 7 additions & 0 deletions execution_chain/db/ledger.nim
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,13 @@ proc selfDestruct6780*(ac: LedgerRef, address: Address) =
if NewlyCreated in acc.flags:
ac.selfDestruct(address)

proc shouldSelfDestruct6780*(ac: LedgerRef, address: Address): bool =
let acc = ac.getAccount(address, false)
if acc.isNil:
return false

NewlyCreated in acc.flags

proc selfDestructLen*(ac: LedgerRef): int =
ac.savePoint.selfDestruct.len

Expand Down
64 changes: 50 additions & 14 deletions execution_chain/evm/computation.nim
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import
chronicles, chronos

export
common
common, state

logScope:
topics = "vm computation"
Expand Down Expand Up @@ -81,23 +81,34 @@ proc getBlockHash*(c: Computation, number: BlockNumber): Hash32 =
c.vmState.getAncestorHash(number)

template accountExists*(c: Computation, address: Address): bool =
if c.vmState.balTrackerEnabled:
c.vmState.balTracker.trackAddressAccess(address)

if c.fork >= FkSpurious:
not c.vmState.readOnlyLedger.isDeadAccount(address)
else:
c.vmState.readOnlyLedger.accountExists(address)

template getStorage*(c: Computation, slot: UInt256): UInt256 =
if c.vmState.balTrackerEnabled:
c.vmState.balTracker.trackStorageRead(c.msg.contractAddress, slot)
c.vmState.readOnlyLedger.getStorage(c.msg.contractAddress, slot)

template getBalance*(c: Computation, address: Address): UInt256 =
if c.vmState.balTrackerEnabled:
c.vmState.balTracker.trackAddressAccess(address)
c.vmState.readOnlyLedger.getBalance(address)

template getCodeSize*(c: Computation, address: Address): uint =
if c.vmState.balTrackerEnabled:
c.vmState.balTracker.trackAddressAccess(address)
uint(c.vmState.readOnlyLedger.getCodeSize(address))

template getCodeHash*(c: Computation, address: Address): Hash32 =
let
db = c.vmState.readOnlyLedger
if c.vmState.balTrackerEnabled:
c.vmState.balTracker.trackAddressAccess(address)

let db = c.vmState.readOnlyLedger
if not db.accountExists(address) or db.isEmptyAccount(address):
default(Hash32)
else:
Expand All @@ -107,6 +118,8 @@ template selfDestruct*(c: Computation, address: Address) =
c.execSelfDestruct(address)

template getCode*(c: Computation, address: Address): CodeBytesRef =
if c.vmState.balTrackerEnabled:
c.vmState.balTracker.trackAddressAccess(address)
c.vmState.readOnlyLedger.getCode(address)

template setTransientStorage*(c: Computation, slot, val: UInt256) =
Expand Down Expand Up @@ -151,9 +164,13 @@ func shouldBurnGas*(c: Computation): bool =
c.isError and c.error.burnsGas

proc snapshot*(c: Computation) =
if c.vmState.balTrackerEnabled:
c.vmState.balTracker.beginCallFrame()
c.savePoint = c.vmState.ledger.beginSavepoint()

proc commit*(c: Computation) =
if c.vmState.balTrackerEnabled:
c.vmState.balTracker.commitCallFrame()
c.vmState.ledger.commit(c.savePoint)

proc dispose*(c: Computation) =
Expand All @@ -167,6 +184,8 @@ proc dispose*(c: Computation) =
c.savePoint = nil

proc rollback*(c: Computation) =
if c.vmState.balTrackerEnabled:
c.vmState.balTracker.rollbackCallFrame()
c.vmState.ledger.rollback(c.savePoint)

func setError*(c: Computation, msg: sink string, burnsGas = false) =
Expand Down Expand Up @@ -228,6 +247,8 @@ proc writeContract*(c: Computation) =
reason = "Write new contract code").
expect("enough gas since we checked against gasRemaining")
c.vmState.mutateLedger:
if c.vmState.balTrackerEnabled:
c.vmState.balTracker.trackCodeChange(c.msg.contractAddress, c.output)
db.setCode(c.msg.contractAddress, c.output)
withExtra trace, "Writing new contract code"
return
Expand Down Expand Up @@ -258,18 +279,33 @@ proc execSelfDestruct*(c: Computation, beneficiary: Address) =

# Register the account to be deleted
if c.fork >= FkCancun:
# Zeroing contract balance except beneficiary
# is the same address
db.subBalance(c.msg.contractAddress, localBalance)

# Transfer to beneficiary
db.addBalance(beneficiary, localBalance)

db.selfDestruct6780(c.msg.contractAddress)
if c.vmState.balTrackerEnabled:
# Zeroing contract balance except beneficiary is the same address
c.vmState.balTracker.trackSubBalanceChange(c.msg.contractAddress, localBalance)
db.subBalance(c.msg.contractAddress, localBalance)
# Transfer to beneficiary
c.vmState.balTracker.trackAddBalanceChange(beneficiary, localBalance)
db.addBalance(beneficiary, localBalance)
if db.shouldSelfDestruct6780(c.msg.contractAddress):
c.vmState.balTracker.trackInTransactionSelfDestruct(c.msg.contractAddress)
db.selfDestruct6780(c.msg.contractAddress)
else:
# Zeroing contract balance except beneficiary is the same address
db.subBalance(c.msg.contractAddress, localBalance)
# Transfer to beneficiary
db.addBalance(beneficiary, localBalance)
db.selfDestruct6780(c.msg.contractAddress)
else:
# Transfer to beneficiary
db.addBalance(beneficiary, localBalance)
db.selfDestruct(c.msg.contractAddress)
if c.vmState.balTrackerEnabled:
# Transfer to beneficiary
c.vmState.balTracker.trackAddBalanceChange(beneficiary, localBalance)
db.addBalance(beneficiary, localBalance)
c.vmState.balTracker.trackSelfDestruct(c.msg.contractAddress)
db.selfDestruct(c.msg.contractAddress)
else:
# Transfer to beneficiary
db.addBalance(beneficiary, localBalance)
db.selfDestruct(c.msg.contractAddress)

trace "SELFDESTRUCT",
contractAddress = c.msg.contractAddress.toHex,
Expand Down
4 changes: 4 additions & 0 deletions execution_chain/evm/interpreter/op_handlers/oph_memory.nim
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ proc sstoreImpl(c: Computation, slot, newValue: UInt256): EvmResultVoid =
? c.opcodeGasCost(Sstore, res.gasCost, "SSTORE")
c.gasMeter.refundGas(res.gasRefund)

if c.vmState.balTrackerEnabled:
c.vmState.balTracker.trackStorageWrite(c.msg.contractAddress, slot, newValue)
c.vmState.mutateLedger:
db.setStorage(c.msg.contractAddress, slot, newValue)
ok()
Expand All @@ -63,6 +65,8 @@ proc sstoreNetGasMeteringImpl(c: Computation; slot, newValue: UInt256, coldAcces

c.gasMeter.refundGas(res.gasRefund)

if c.vmState.balTrackerEnabled:
c.vmState.balTracker.trackStorageWrite(c.msg.contractAddress, slot, newValue)
c.vmState.mutateLedger:
db.setStorage(c.msg.contractAddress, slot, newValue)
ok()
Expand Down
Loading
Loading