diff --git a/nimbus/core/executor/process_transaction.nim b/nimbus/core/executor/process_transaction.nim index 5a4d7ffd5..79d09002d 100644 --- a/nimbus/core/executor/process_transaction.nim +++ b/nimbus/core/executor/process_transaction.nim @@ -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 diff --git a/nimbus/core/tx_pool/tx_tasks/tx_packer.nim b/nimbus/core/tx_pool/tx_tasks/tx_packer.nim index 78aefa31b..2dd16493a 100644 --- a/nimbus/core/tx_pool/tx_tasks/tx_packer.nim +++ b/nimbus/core/tx_pool/tx_tasks/tx_packer.nim @@ -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 diff --git a/nimbus/db/access_list.nim b/nimbus/db/access_list.nim index 82ebaa18f..54c9a8b8b 100644 --- a/nimbus/db/access_list.nim +++ b/nimbus/db/access_list.nim @@ -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, diff --git a/nimbus/db/accounts_cache.nim b/nimbus/db/accounts_cache.nim index ff7ca0b10..cf3749a0b 100644 --- a/nimbus/db/accounts_cache.nim +++ b/nimbus/db/accounts_cache.nim @@ -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 @@ -53,6 +64,7 @@ type selfDestruct: HashSet[EthAddress] logEntries: seq[Log] accessList: ac_access_list.AccessList + transientStorage: TransientStorage state: TransactionState when debugAccountsCache: depth: int @@ -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 @@ -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 @@ -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.} @@ -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.} diff --git a/nimbus/db/storage_types.nim b/nimbus/db/storage_types.nim index 4c4e10469..667e401cd 100644 --- a/nimbus/db/storage_types.nim +++ b/nimbus/db/storage_types.nim @@ -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 diff --git a/nimbus/db/transient_storage.nim b/nimbus/db/transient_storage.nim new file mode 100644 index 000000000..8183946dd --- /dev/null +++ b/nimbus/db/transient_storage.nim @@ -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() diff --git a/nimbus/evm/computation.nim b/nimbus/evm/computation.nim index 5b3e948b1..da3d56d6e 100644 --- a/nimbus/evm/computation.nim +++ b/nimbus/evm/computation.nim @@ -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 diff --git a/nimbus/evm/interpreter/gas_costs.nim b/nimbus/evm/interpreter/gas_costs.nim index 065b30793..5a86d0cb3 100644 --- a/nimbus/evm/interpreter/gas_costs.nim +++ b/nimbus/evm/interpreter/gas_costs.nim @@ -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] @@ -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`, @@ -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 diff --git a/nimbus/evm/interpreter/op_codes.nim b/nimbus/evm/interpreter/op_codes.nim index a95252f42..e12fb8dcc 100644 --- a/nimbus/evm/interpreter/op_codes.nim +++ b/nimbus/evm/interpreter/op_codes.nim @@ -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, diff --git a/nimbus/evm/interpreter/op_handlers/oph_defs.nim b/nimbus/evm/interpreter/op_handlers/oph_defs.nim index 7af3cf089..c3da024ce 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_defs.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_defs.nim @@ -86,6 +86,10 @@ const Vm2OpShanghaiAndLater* = Vm2OpParisAndLater - {FkParis} + # TODO: fix this after EIP-1153 accepted in a fork + Vm2Op1153AndLater* = + Vm2OpShanghaiAndLater - {FkShanghai} + # ------------------------------------------------------------------------------ # End # ------------------------------------------------------------------------------ diff --git a/nimbus/evm/interpreter/op_handlers/oph_memory.nim b/nimbus/evm/interpreter/op_handlers/oph_memory.nim index 97dcdb66e..613a76d85 100644 --- a/nimbus/evm/interpreter/op_handlers/oph_memory.nim +++ b/nimbus/evm/interpreter/op_handlers/oph_memory.nim @@ -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 @@ -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))] #[ diff --git a/nimbus/evm/state_transactions.nim b/nimbus/evm/state_transactions.nim index e60c5e5d3..577249ddb 100644 --- a/nimbus/evm/state_transactions.nim +++ b/nimbus/evm/state_transactions.nim @@ -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. diff --git a/tests/test_state_db.nim b/tests/test_state_db.nim index 97cd17e75..4a473db84 100644 --- a/tests/test_state_db.nim +++ b/tests/test_state_db.nim @@ -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()