implement EIP-1153: Transient storage

new EVM opcodes:
- TLOAD  0xb3
- TSTORE 0xb4
This commit is contained in:
jangko 2023-03-21 20:27:12 +07:00
parent 15cc9f962e
commit 6544adf360
No known key found for this signature in database
GPG Key ID: 31702AE10541E6B9
13 changed files with 261 additions and 4 deletions

View File

@ -102,6 +102,9 @@ proc asyncProcessTransactionImpl(
let txRes = roDB.validateTransaction(tx, sender, header.gasLimit, baseFee256, fork) let txRes = roDB.validateTransaction(tx, sender, header.gasLimit, baseFee256, fork)
if txRes.isOk: if txRes.isOk:
# EIP-1153
vmState.stateDB.clearTransientStorage()
# Execute the transaction. # Execute the transaction.
let let
accTx = vmState.stateDB.beginSavepoint accTx = vmState.stateDB.beginSavepoint

View File

@ -186,6 +186,9 @@ proc vmExecGrabItem(pst: TxPackerStateRef; item: TxItemRef): Result[bool,void]
if not xp.classifyValidatePacked(vmState, item): if not xp.classifyValidatePacked(vmState, item):
return ok(false) # continue with next account return ok(false) # continue with next account
# EIP-1153
vmState.stateDB.clearTransientStorage()
let let
accTx = vmState.stateDB.beginSavepoint accTx = vmState.stateDB.beginSavepoint
gasUsed = pst.runTx(item) # this is the crucial part, running the tx gasUsed = pst.runTx(item) # this is the crucial part, running the tx

View File

@ -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 import
tables, sets, tables, sets,
stint, stint,

View File

@ -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 import
std/[tables, hashes, sets], std/[tables, hashes, sets],
eth/[common, rlp], eth/trie/[hexary, db, trie_defs], eth/[common, rlp], eth/trie/[hexary, db, trie_defs],
../constants, ../utils/utils, storage_types, ../constants, ../utils/utils, storage_types,
../../stateless/multi_keys, ../../stateless/multi_keys,
./distinct_tries, ./distinct_tries,
./access_list as ac_access_list ./access_list as ac_access_list,
./transient_storage
const const
debugAccountsCache = false debugAccountsCache = false
@ -53,6 +64,7 @@ type
selfDestruct: HashSet[EthAddress] selfDestruct: HashSet[EthAddress]
logEntries: seq[Log] logEntries: seq[Log]
accessList: ac_access_list.AccessList accessList: ac_access_list.AccessList
transientStorage: TransientStorage
state: TransactionState state: TransactionState
when debugAccountsCache: when debugAccountsCache:
depth: int depth: int
@ -119,6 +131,7 @@ proc beginSavepoint*(ac: var AccountsCache): SavePoint =
new result new result
result.cache = initTable[EthAddress, RefAccount]() result.cache = initTable[EthAddress, RefAccount]()
result.accessList.init() result.accessList.init()
result.transientStorage.init()
result.state = Pending result.state = Pending
result.parentSavepoint = ac.savePoint result.parentSavepoint = ac.savePoint
ac.savePoint = result ac.savePoint = result
@ -151,6 +164,7 @@ proc commit*(ac: var AccountsCache, sp: SavePoint) =
for k, v in sp.cache: for k, v in sp.cache:
sp.parentSavepoint.cache[k] = v sp.parentSavepoint.cache[k] = v
ac.savePoint.transientStorage.merge(sp.transientStorage)
ac.savePoint.accessList.merge(sp.accessList) ac.savePoint.accessList.merge(sp.accessList)
ac.savePoint.selfDestruct.incl sp.selfDestruct ac.savePoint.selfDestruct.incl sp.selfDestruct
ac.savePoint.logEntries.add sp.logEntries ac.savePoint.logEntries.add sp.logEntries
@ -688,6 +702,24 @@ func inAccessList*(ac: AccountsCache, address: EthAddress, slot: UInt256): bool
return return
sp = sp.parentSavepoint 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 rootHash*(db: ReadOnlyStateDB): KeccakHash {.borrow.}
proc getCodeHash*(db: ReadOnlyStateDB, address: EthAddress): Hash256 {.borrow.} proc getCodeHash*(db: ReadOnlyStateDB, address: EthAddress): Hash256 {.borrow.}
proc getStorageRoot*(db: ReadOnlyStateDB, address: EthAddress): Hash256 {.borrow.} proc getStorageRoot*(db: ReadOnlyStateDB, address: EthAddress): Hash256 {.borrow.}
@ -703,3 +735,5 @@ proc isEmptyAccount*(db: ReadOnlyStateDB, address: EthAddress): bool {.borrow.}
proc getCommittedStorage*(db: ReadOnlyStateDB, address: EthAddress, slot: UInt256): UInt256 {.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): bool {.borrow.}
func inAccessList*(ac: ReadOnlyStateDB, address: EthAddress, slot: UInt256): bool {.borrow.} func inAccessList*(ac: ReadOnlyStateDB, address: EthAddress, slot: UInt256): bool {.borrow.}
func getTransientStorage*(ac: ReadOnlyStateDB,
address: EthAddress, slot: UInt256): UInt256 {.borrow.}

View File

@ -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 import
eth/common eth/common

View File

@ -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()

View File

@ -186,6 +186,22 @@ template getCode*(c: Computation, address: EthAddress): seq[byte] =
else: else:
c.vmState.readOnlyStateDB.getCode(address) 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, proc newComputation*(vmState: BaseVMState, message: Message,
salt: ContractSalt = ZERO_CONTRACTSALT): Computation = salt: ContractSalt = ZERO_CONTRACTSALT): Computation =
new result new result

View File

@ -54,6 +54,7 @@ type
GasBlockhash, # Payment for BLOCKHASH operation. GasBlockhash, # Payment for BLOCKHASH operation.
GasExtCodeHash, # Payment for contract's code hashing GasExtCodeHash, # Payment for contract's code hashing
GasInitcodeWord # Payment for each word (rounded up) for initcode GasInitcodeWord # Payment for each word (rounded up) for initcode
GasWarmStorageRead # Transient storage read and write cost.
GasFeeSchedule = array[GasFeeKind, GasInt] GasFeeSchedule = array[GasFeeKind, GasInt]
@ -706,6 +707,10 @@ template gasCosts(fork: EVMFork, prefix, ResultGasCostsName: untyped) =
Log3: memExpansion `prefix gasLog3`, Log3: memExpansion `prefix gasLog3`,
Log4: memExpansion `prefix gasLog4`, Log4: memExpansion `prefix gasLog4`,
# b0s: Transient storage operations
Tload: fixed GasWarmStorageRead,
Tstore: fixed GasWarmStorageRead,
# f0s: System operations # f0s: System operations
Create: complex `prefix gasCreate`, Create: complex `prefix gasCreate`,
Call: complex `prefix gasCall`, Call: complex `prefix gasCall`,
@ -759,7 +764,8 @@ const
GasCopy: 3, GasCopy: 3,
GasBlockhash: 20, GasBlockhash: 20,
GasExtCodeHash: 400, 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 # Create the schedule for each forks

View File

@ -173,7 +173,12 @@ type
Nop0xA5, Nop0xA6, Nop0xA7, Nop0xA8, Nop0xA9, Nop0xAA, Nop0xA5, Nop0xA6, Nop0xA7, Nop0xA8, Nop0xA9, Nop0xAA,
Nop0xAB, Nop0xAC, Nop0xAD, Nop0xAE, Nop0xAF, Nop0xB0, 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, Nop0xB7, Nop0xB8, Nop0xB9, Nop0xBA, Nop0xBB, Nop0xBC,
Nop0xBD, Nop0xBE, Nop0xBF, Nop0xC0, Nop0xC1, Nop0xC2, Nop0xBD, Nop0xBE, Nop0xBF, Nop0xC0, Nop0xC1, Nop0xC2,
Nop0xC3, Nop0xC4, Nop0xC5, Nop0xC6, Nop0xC7, Nop0xC8, Nop0xC3, Nop0xC4, Nop0xC5, Nop0xC6, Nop0xC7, Nop0xC8,

View File

@ -86,6 +86,10 @@ const
Vm2OpShanghaiAndLater* = Vm2OpShanghaiAndLater* =
Vm2OpParisAndLater - {FkParis} Vm2OpParisAndLater - {FkParis}
# TODO: fix this after EIP-1153 accepted in a fork
Vm2Op1153AndLater* =
Vm2OpShanghaiAndLater - {FkShanghai}
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
# End # End
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------

View File

@ -305,6 +305,22 @@ const
## on machine state during execution. ## on machine state during execution.
discard 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 EIP-2315: temporary disabled
Reason : not included in berlin hard fork Reason : not included in berlin hard fork
@ -493,6 +509,22 @@ const
info: "Mark a valid destination for jumps", info: "Mark a valid destination for jumps",
exec: (prep: vm2OpIgnore, exec: (prep: vm2OpIgnore,
run: jumpDestOp, 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))] post: vm2OpIgnore))]
#[ #[

View File

@ -35,7 +35,6 @@ proc setupTxContext*(vmState: BaseVMState, origin: EthAddress, gasPrice: GasInt,
vmState.determineFork vmState.determineFork
vmState.gasCosts = vmState.fork.forkToSchedule vmState.gasCosts = vmState.fork.forkToSchedule
# FIXME-awkwardFactoring: the factoring out of the pre and # FIXME-awkwardFactoring: the factoring out of the pre and
# post parts feels awkward to me, but for now I'd really like # post parts feels awkward to me, but for now I'd really like
# not to have too much duplicated code between sync and async. # not to have too much duplicated code between sync and async.

View File

@ -216,5 +216,71 @@ proc stateDBMain*() =
check ac.verifySlots(0xcc, 0x01) check ac.verifySlots(0xcc, 0x01)
check ac.verifySlots(0xdd, 0x04) 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: when isMainModule:
stateDBMain() stateDBMain()