Skip to content

Commit

Permalink
implement EIP-1153: Transient storage
Browse files Browse the repository at this point in the history
new EVM opcodes:
- TLOAD  0xb3
- TSTORE 0xb4
  • Loading branch information
jangko committed Jun 23, 2023
1 parent 15cc9f9 commit 6544adf
Show file tree
Hide file tree
Showing 13 changed files with 261 additions and 4 deletions.
3 changes: 3 additions & 0 deletions nimbus/core/executor/process_transaction.nim
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,9 @@ proc asyncProcessTransactionImpl(
let txRes = roDB.validateTransaction(tx, sender, header.gasLimit, baseFee256, fork)
if txRes.isOk:

# EIP-1153
vmState.stateDB.clearTransientStorage()

# Execute the transaction.
let
accTx = vmState.stateDB.beginSavepoint
Expand Down
3 changes: 3 additions & 0 deletions nimbus/core/tx_pool/tx_tasks/tx_packer.nim
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,9 @@ proc vmExecGrabItem(pst: TxPackerStateRef; item: TxItemRef): Result[bool,void]
if not xp.classifyValidatePacked(vmState, item):
return ok(false) # continue with next account

# EIP-1153
vmState.stateDB.clearTransientStorage()

let
accTx = vmState.stateDB.beginSavepoint
gasUsed = pst.runTx(item) # this is the crucial part, running the tx
Expand Down
10 changes: 10 additions & 0 deletions nimbus/db/access_list.nim
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# Nimbus
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.

import
tables, sets,
stint,
Expand Down
36 changes: 35 additions & 1 deletion nimbus/db/accounts_cache.nim
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
# Nimbus
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.

import
std/[tables, hashes, sets],
eth/[common, rlp], eth/trie/[hexary, db, trie_defs],
../constants, ../utils/utils, storage_types,
../../stateless/multi_keys,
./distinct_tries,
./access_list as ac_access_list
./access_list as ac_access_list,
./transient_storage

const
debugAccountsCache = false
Expand Down Expand Up @@ -53,6 +64,7 @@ type
selfDestruct: HashSet[EthAddress]
logEntries: seq[Log]
accessList: ac_access_list.AccessList
transientStorage: TransientStorage
state: TransactionState
when debugAccountsCache:
depth: int
Expand Down Expand Up @@ -119,6 +131,7 @@ proc beginSavepoint*(ac: var AccountsCache): SavePoint =
new result
result.cache = initTable[EthAddress, RefAccount]()
result.accessList.init()
result.transientStorage.init()
result.state = Pending
result.parentSavepoint = ac.savePoint
ac.savePoint = result
Expand Down Expand Up @@ -151,6 +164,7 @@ proc commit*(ac: var AccountsCache, sp: SavePoint) =
for k, v in sp.cache:
sp.parentSavepoint.cache[k] = v

ac.savePoint.transientStorage.merge(sp.transientStorage)
ac.savePoint.accessList.merge(sp.accessList)
ac.savePoint.selfDestruct.incl sp.selfDestruct
ac.savePoint.logEntries.add sp.logEntries
Expand Down Expand Up @@ -688,6 +702,24 @@ func inAccessList*(ac: AccountsCache, address: EthAddress, slot: UInt256): bool
return
sp = sp.parentSavepoint

func getTransientStorage*(ac: AccountsCache,
address: EthAddress, slot: UInt256): UInt256 =
var sp = ac.savePoint
while sp != nil:
let (ok, res) = sp.transientStorage.getStorage(address, slot)
if ok:
return res
sp = sp.parentSavepoint

proc setTransientStorage*(ac: AccountsCache,
address: EthAddress, slot, val: UInt256) =
ac.savePoint.transientStorage.setStorage(address, slot, val)

proc clearTransientStorage*(ac: AccountsCache) {.inline.} =
# make sure all savepoint already committed
doAssert(ac.savePoint.parentSavepoint.isNil)
ac.savePoint.transientStorage.clear()

proc rootHash*(db: ReadOnlyStateDB): KeccakHash {.borrow.}
proc getCodeHash*(db: ReadOnlyStateDB, address: EthAddress): Hash256 {.borrow.}
proc getStorageRoot*(db: ReadOnlyStateDB, address: EthAddress): Hash256 {.borrow.}
Expand All @@ -703,3 +735,5 @@ proc isEmptyAccount*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
proc getCommittedStorage*(db: ReadOnlyStateDB, address: EthAddress, slot: UInt256): UInt256 {.borrow.}
func inAccessList*(ac: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
func inAccessList*(ac: ReadOnlyStateDB, address: EthAddress, slot: UInt256): bool {.borrow.}
func getTransientStorage*(ac: ReadOnlyStateDB,
address: EthAddress, slot: UInt256): UInt256 {.borrow.}
10 changes: 10 additions & 0 deletions nimbus/db/storage_types.nim
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
# Nimbus
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.

import
eth/common

Expand Down
69 changes: 69 additions & 0 deletions nimbus/db/transient_storage.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Nimbus
# Copyright (c) 2023 Status Research & Development GmbH
# Licensed under either of
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
# http://www.apache.org/licenses/LICENSE-2.0)
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or
# http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.

import
tables,
stint,
eth/common

type
StorageTable = ref object
map: Table[UInt256, UInt256]

TransientStorage* = object
map: Table[EthAddress, StorageTable]

#######################################################################
# Private helpers
#######################################################################

proc merge(a, b: StorageTable) =
for k, v in b.map:
a.map[k] = v

#######################################################################
# Public functions
#######################################################################

proc init*(ac: var TransientStorage) =
ac.map = initTable[EthAddress, StorageTable]()

proc init*(_: type TransientStorage): TransientStorage {.inline.} =
result.init()

func getStorage*(ac: TransientStorage,
address: EthAddress, slot: UInt256): (bool, UInt256) =
var table = ac.map.getOrDefault(address)
if table.isNil:
return (false, 0.u256)

table.map.withValue(slot, val):
return (true, val[])
do:
return (false, 0.u256)

proc setStorage*(ac: var TransientStorage,
address: EthAddress, slot, value: UInt256) =
var table = ac.map.getOrDefault(address)
if table.isNil:
table = StorageTable()
ac.map[address] = table

table.map[slot] = value

proc merge*(ac: var TransientStorage, other: TransientStorage) =
for k, v in other.map:
ac.map.withValue(k, val):
val[].merge(v)
do:
ac.map[k] = v

proc clear*(ac: var TransientStorage) {.inline.} =
ac.map.clear()
16 changes: 16 additions & 0 deletions nimbus/evm/computation.nim
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,22 @@ template getCode*(c: Computation, address: EthAddress): seq[byte] =
else:
c.vmState.readOnlyStateDB.getCode(address)

template setTransientStorage*(c: Computation, slot, val: UInt256) =
when evmc_enabled:
# TODO: EIP-1153
discard
else:
c.vmState.stateDB.
setTransientStorage(c.msg.contractAddress, slot, val)

template getTransientStorage*(c: Computation, slot: UInt256): UInt256 =
when evmc_enabled:
# TODO: EIP-1153
0.u256
else:
c.vmState.readOnlyStateDB.
getTransientStorage(c.msg.contractAddress, slot)

proc newComputation*(vmState: BaseVMState, message: Message,
salt: ContractSalt = ZERO_CONTRACTSALT): Computation =
new result
Expand Down
8 changes: 7 additions & 1 deletion nimbus/evm/interpreter/gas_costs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type
GasBlockhash, # Payment for BLOCKHASH operation.
GasExtCodeHash, # Payment for contract's code hashing
GasInitcodeWord # Payment for each word (rounded up) for initcode
GasWarmStorageRead # Transient storage read and write cost.

GasFeeSchedule = array[GasFeeKind, GasInt]

Expand Down Expand Up @@ -706,6 +707,10 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
Log3: memExpansion `prefix gasLog3`,
Log4: memExpansion `prefix gasLog4`,

# b0s: Transient storage operations
Tload: fixed GasWarmStorageRead,
Tstore: fixed GasWarmStorageRead,

# f0s: System operations
Create: complex `prefix gasCreate`,
Call: complex `prefix gasCall`,
Expand Down Expand Up @@ -759,7 +764,8 @@ const
GasCopy: 3,
GasBlockhash: 20,
GasExtCodeHash: 400,
GasInitcodeWord: 0 # Changed to 2 in EIP-3860
GasInitcodeWord: 0, # Changed to 2 in EIP-3860
GasWarmStorageRead: WarmStorageReadCost
]

# Create the schedule for each forks
Expand Down
7 changes: 6 additions & 1 deletion nimbus/evm/interpreter/op_codes.nim
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,12 @@ type

Nop0xA5, Nop0xA6, Nop0xA7, Nop0xA8, Nop0xA9, Nop0xAA,
Nop0xAB, Nop0xAC, Nop0xAD, Nop0xAE, Nop0xAF, Nop0xB0,
Nop0xB1, Nop0xB2, Nop0xB3, Nop0xB4, Nop0xB5, Nop0xB6,
Nop0xB1, Nop0xB2,

Tload = 0xb3, ## Load word from transient storage.
Tstore = 0xb4, ## Save word to transient storage.

Nop0xB5, Nop0xB6,
Nop0xB7, Nop0xB8, Nop0xB9, Nop0xBA, Nop0xBB, Nop0xBC,
Nop0xBD, Nop0xBE, Nop0xBF, Nop0xC0, Nop0xC1, Nop0xC2,
Nop0xC3, Nop0xC4, Nop0xC5, Nop0xC6, Nop0xC7, Nop0xC8,
Expand Down
4 changes: 4 additions & 0 deletions nimbus/evm/interpreter/op_handlers/oph_defs.nim
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ const
Vm2OpShanghaiAndLater* =
Vm2OpParisAndLater - {FkParis}

# TODO: fix this after EIP-1153 accepted in a fork
Vm2Op1153AndLater* =
Vm2OpShanghaiAndLater - {FkShanghai}

# ------------------------------------------------------------------------------
# End
# ------------------------------------------------------------------------------
32 changes: 32 additions & 0 deletions nimbus/evm/interpreter/op_handlers/oph_memory.nim
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,22 @@ const
## on machine state during execution.
discard

tloadOp: Vm2OpFn = proc (k: var Vm2Ctx) =
## 0xb3, Load word from transient storage.
let
slot = k.cpt.stack.peek()
val = k.cpt.getTransientStorage(slot)
k.cpt.stack.top(val)

tstoreOp: Vm2OpFn = proc (k: var Vm2Ctx) =
## 0xb4, Save word to transient storage.
checkInStaticContext(k.cpt)

let
slot = k.cpt.stack.popInt()
val = k.cpt.stack.popInt()
k.cpt.setTransientStorage(slot, val)

#[
EIP-2315: temporary disabled
Reason : not included in berlin hard fork
Expand Down Expand Up @@ -493,6 +509,22 @@ const
info: "Mark a valid destination for jumps",
exec: (prep: vm2OpIgnore,
run: jumpDestOp,
post: vm2OpIgnore)),

(opCode: Tload, ## 0xb3, Load word from transient storage.
forks: Vm2Op1153AndLater,
name: "tLoad",
info: "Load word from transient storage",
exec: (prep: vm2OpIgnore,
run: tloadOp,
post: vm2OpIgnore)),

(opCode: Tstore, ## 0xb4, Save word to transient storage.
forks: Vm2Op1153AndLater,
name: "tStore",
info: "Save word to transient storage",
exec: (prep: vm2OpIgnore,
run: tstoreOp,
post: vm2OpIgnore))]

#[
Expand Down
1 change: 0 additions & 1 deletion nimbus/evm/state_transactions.nim
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt,
vmState.determineFork
vmState.gasCosts = vmState.fork.forkToSchedule


# FIXME-awkwardFactoring: the factoring out of the pre and
# post parts feels awkward to me, but for now I'd really like
# not to have too much duplicated code between sync and async.
Expand Down
66 changes: 66 additions & 0 deletions tests/test_state_db.nim
Original file line number Diff line number Diff line change
Expand Up @@ -216,5 +216,71 @@ proc stateDBMain*() =
check ac.verifySlots(0xcc, 0x01)
check ac.verifySlots(0xdd, 0x04)

test "transient storage operations":
var ac = init(AccountsCache, acDB)

proc tStore(ac: AccountsCache, address, slot, val: int) =
ac.setTransientStorage(address.initAddr, slot.u256, val.u256)

proc tLoad(ac: AccountsCache, address, slot: int): UInt256 =
ac.getTransientStorage(address.initAddr, slot.u256)

proc vts(ac: AccountsCache, address, slot, val: int): bool =
ac.tLoad(address, slot) == val.u256

ac.tStore(0xaa, 3, 66)
ac.tStore(0xbb, 1, 33)
ac.tStore(0xbb, 2, 99)

check ac.vts(0xaa, 3, 66)
check ac.vts(0xbb, 1, 33)
check ac.vts(0xbb, 2, 99)
check ac.vts(0xaa, 1, 33) == false
check ac.vts(0xbb, 1, 66) == false

var sp = ac.beginSavepoint
# some new ones
ac.tStore(0xaa, 3, 77)
ac.tStore(0xbb, 1, 55)
ac.tStore(0xcc, 7, 88)

check ac.vts(0xaa, 3, 77)
check ac.vts(0xbb, 1, 55)
check ac.vts(0xcc, 7, 88)

check ac.vts(0xaa, 3, 66) == false
check ac.vts(0xbb, 1, 33) == false
check ac.vts(0xbb, 2, 99)

ac.rollback(sp)
check ac.vts(0xaa, 3, 66)
check ac.vts(0xbb, 1, 33)
check ac.vts(0xbb, 2, 99)
check ac.vts(0xcc, 7, 88) == false

sp = ac.beginSavepoint
ac.tStore(0xaa, 3, 44)
ac.tStore(0xaa, 4, 55)
ac.tStore(0xbb, 1, 22)
ac.tStore(0xdd, 2, 66)

ac.commit(sp)
check ac.vts(0xaa, 3, 44)
check ac.vts(0xaa, 4, 55)
check ac.vts(0xbb, 1, 22)
check ac.vts(0xbb, 1, 55) == false
check ac.vts(0xbb, 2, 99)
check ac.vts(0xcc, 7, 88) == false
check ac.vts(0xdd, 2, 66)

ac.clearTransientStorage()
check ac.vts(0xaa, 3, 44) == false
check ac.vts(0xaa, 4, 55) == false
check ac.vts(0xbb, 1, 22) == false
check ac.vts(0xbb, 1, 55) == false
check ac.vts(0xbb, 2, 99) == false
check ac.vts(0xcc, 7, 88) == false
check ac.vts(0xdd, 2, 66) == false

when isMainModule:
stateDBMain()

0 comments on commit 6544adf

Please sign in to comment.