mirror of
https://github.com/status-im/nimbus-eth1.git
synced 2025-01-12 13:24:21 +00:00
Implement RPC method eth_getAccessList (#2091)
* Implement RPC method eth_getAccessList * Fix comment
This commit is contained in:
parent
8ed40c78e0
commit
7ea6d719d9
@ -208,9 +208,6 @@ proc exchangeCapabilities*(client: RpcClient,
|
||||
wrapTrySimpleRes:
|
||||
client.engine_exchangeCapabilities(methods)
|
||||
|
||||
proc toBlockNumber(n: Quantity): common.BlockNumber =
|
||||
n.uint64.toBlockNumber
|
||||
|
||||
proc toBlockNonce(n: Option[FixedBytes[8]]): common.BlockNonce =
|
||||
if n.isNone:
|
||||
return default(BlockNonce)
|
||||
@ -265,21 +262,6 @@ proc toBlockHeader*(bc: BlockObject): common.BlockHeader =
|
||||
parentBeaconBlockRoot: ethHash bc.parentBeaconBlockRoot,
|
||||
)
|
||||
|
||||
func storageKeys(list: seq[FixedBytes[32]]): seq[StorageKey] =
|
||||
for x in list:
|
||||
result.add StorageKey(x)
|
||||
|
||||
func accessList(list: openArray[AccessTuple]): AccessList =
|
||||
for x in list:
|
||||
result.add AccessPair(
|
||||
address : ethAddr x.address,
|
||||
storageKeys: storageKeys x.storageKeys,
|
||||
)
|
||||
|
||||
func accessList(x: Option[seq[AccessTuple]]): AccessList =
|
||||
if x.isNone: return
|
||||
else: accessList(x.get)
|
||||
|
||||
func vHashes(x: Option[seq[Web3Hash]]): seq[common.Hash256] =
|
||||
if x.isNone: return
|
||||
else: ethHashes(x.get)
|
||||
@ -296,7 +278,7 @@ proc toTransaction(tx: TransactionObject): Transaction =
|
||||
to : ethAddr tx.to,
|
||||
value : tx.value,
|
||||
payload : tx.input,
|
||||
accessList : accessList(tx.accessList),
|
||||
accessList : ethAccessList(tx.accessList),
|
||||
maxFeePerBlobGas: tx.maxFeePerBlobGas.get(0.u256),
|
||||
versionedHashes : vHashes(tx.blobVersionedHashes),
|
||||
V : tx.v.int64,
|
||||
|
@ -161,6 +161,21 @@ func ethTxs*(list: openArray[Web3Tx], removeBlobs = false):
|
||||
for x in list:
|
||||
result.add ethTx(x)
|
||||
|
||||
func storageKeys(list: seq[FixedBytes[32]]): seq[StorageKey] =
|
||||
for x in list:
|
||||
result.add StorageKey(x)
|
||||
|
||||
func ethAccessList*(list: openArray[AccessTuple]): common.AccessList =
|
||||
for x in list:
|
||||
result.add common.AccessPair(
|
||||
address : ethAddr x.address,
|
||||
storageKeys: storageKeys x.storageKeys,
|
||||
)
|
||||
|
||||
func ethAccessList*(x: Option[seq[AccessTuple]]): common.AccessList =
|
||||
if x.isSome:
|
||||
return ethAccessList(x.get)
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Eth types to Web3 types
|
||||
# ------------------------------------------------------------------------------
|
||||
@ -188,6 +203,11 @@ func w3Hash*(x: Option[common.Hash256]): Option[BlockHash] =
|
||||
func w3Hash*(x: common.BlockHeader): BlockHash =
|
||||
BlockHash rlpHash(x).data
|
||||
|
||||
func w3Hash*(list: openArray[StorageKey]): seq[Web3Hash] =
|
||||
result = newSeqOfCap[Web3Hash](list.len)
|
||||
for x in list:
|
||||
result.add Web3Hash x
|
||||
|
||||
func w3Addr*(x: common.EthAddress): Web3Address =
|
||||
Web3Address x
|
||||
|
||||
@ -266,3 +286,14 @@ func w3Txs*(list: openArray[common.Transaction]): seq[Web3Tx] =
|
||||
result = newSeqOfCap[Web3Tx](list.len)
|
||||
for tx in list:
|
||||
result.add w3Tx(tx)
|
||||
|
||||
proc w3AccessTuple*(ac: AccessPair): AccessTuple =
|
||||
AccessTuple(
|
||||
address: w3Addr ac.address,
|
||||
storageKeys: w3Hash(ac.storageKeys)
|
||||
)
|
||||
|
||||
proc w3AccessList*(list: openArray[AccessPair]): seq[AccessTuple] =
|
||||
result = newSeqOfCap[AccessTuple](list.len)
|
||||
for x in list:
|
||||
result.add w3AccessTuple(x)
|
||||
|
@ -75,3 +75,20 @@ func getAccessList*(ac: AccessList): common.AccessList =
|
||||
address : address,
|
||||
storageKeys: slots.toStorageKeys,
|
||||
)
|
||||
|
||||
func equal*(ac: AccessList, other: var AccessList): bool =
|
||||
if ac.slots.len != other.slots.len:
|
||||
return false
|
||||
|
||||
for address, slots in ac.slots:
|
||||
other.slots.withValue(address, otherSlots):
|
||||
if slots.len != otherSlots[].len:
|
||||
return false
|
||||
|
||||
for slot in slots:
|
||||
if slot notin otherSlots[]:
|
||||
return false
|
||||
do:
|
||||
return false
|
||||
|
||||
true
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Nimbus
|
||||
# Copyright (c) 2018 Status Research & Development GmbH
|
||||
# Copyright (c) 2018-2024 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)
|
||||
@ -75,6 +75,10 @@ iterator activePrecompiles*(fork: EVMFork): EthAddress =
|
||||
res[^1] = c.byte
|
||||
yield res
|
||||
|
||||
func activePrecompilesList*(fork: EVMFork): seq[EthAddress] =
|
||||
for address in activePrecompiles(fork):
|
||||
result.add address
|
||||
|
||||
proc getSignature(c: Computation): (array[32, byte], Signature) =
|
||||
# input is Hash, V, R, S
|
||||
template data: untyped = c.msg.data
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Nimbus
|
||||
# Copyright (c) 2018 Status Research & Development GmbH
|
||||
# Copyright (c) 2018-2024 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)
|
||||
@ -132,5 +132,9 @@ proc peekInt*(stack: Stack): UInt256 =
|
||||
ensurePop(stack, 1)
|
||||
fromStackElement(stack.values[^1], result)
|
||||
|
||||
proc peekAddress*(stack: Stack): EthAddress =
|
||||
ensurePop(stack, 1)
|
||||
fromStackElement(stack.values[^1], result)
|
||||
|
||||
proc top*(stack: Stack, value: uint | int | GasInt | UInt256 | EthAddress | Hash256) {.inline.} =
|
||||
toStackElement(value, stack.values[^1])
|
||||
|
77
nimbus/evm/tracer/access_list_tracer.nim
Normal file
77
nimbus/evm/tracer/access_list_tracer.nim
Normal file
@ -0,0 +1,77 @@
|
||||
# Nimbus
|
||||
# Copyright (c) 2023-2024 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/[sets],
|
||||
eth/common/eth_types as common,
|
||||
".."/[types, stack],
|
||||
../interpreter/op_codes,
|
||||
../../db/access_list,
|
||||
../../errors
|
||||
|
||||
type
|
||||
AccessListTracer* = ref object of TracerRef
|
||||
list: access_list.AccessList
|
||||
excl: HashSet[EthAddress]
|
||||
|
||||
proc new*(T: type AccessListTracer,
|
||||
acl: common.AccessList,
|
||||
sender: EthAddress,
|
||||
to: EthAddress,
|
||||
precompiles: openArray[EthAddress]): T =
|
||||
let act = T()
|
||||
act.excl.incl sender
|
||||
act.excl.incl to
|
||||
|
||||
for address in precompiles:
|
||||
act.excl.incl address
|
||||
|
||||
for acp in acl:
|
||||
if acp.address notin act.excl:
|
||||
act.list.add acp.address
|
||||
for slot in acp.storageKeys:
|
||||
act.list.add(acp.address, UInt256.fromBytesBE(slot))
|
||||
|
||||
act
|
||||
|
||||
# Opcode level
|
||||
method captureOpStart*(act: AccessListTracer, c: Computation,
|
||||
fixed: bool, pc: int, op: Op, gas: GasInt,
|
||||
depth: int): int {.gcsafe.} =
|
||||
let stackLen = c.stack.len
|
||||
try:
|
||||
if (op in [Sload, Sstore]) and (stackLen >= 1):
|
||||
let slot = c.stack.peekInt()
|
||||
act.list.add(c.msg.contractAddress, slot)
|
||||
|
||||
if (op in [ExtCodeCopy, ExtCodeHash, ExtCodeSize, Balance, SelfDestruct]) and (stackLen >= 1):
|
||||
let address = c.stack.peekAddress()
|
||||
if address notin act.excl:
|
||||
act.list.add address
|
||||
|
||||
if (op in [DelegateCall, Call, StaticCall, CallCode]) and (stackLen >= 5):
|
||||
let address = c.stack[^2, EthAddress]
|
||||
if address notin act.excl:
|
||||
act.list.add address
|
||||
except InsufficientStack as exc:
|
||||
# should not raise, because we already check the stack len
|
||||
# this try..except block is to prevent unlisted exception error
|
||||
discard exc
|
||||
except ValueError as exc:
|
||||
discard exc
|
||||
|
||||
# AccessListTracer is not using captureOpEnd
|
||||
# no need to return op index
|
||||
|
||||
func equal*(ac: AccessListTracer, other: AccessListTracer): bool =
|
||||
ac.list.equal(other.list)
|
||||
|
||||
func accessList*(ac: AccessListTracer): common.AccessList =
|
||||
ac.list.getAccessList()
|
@ -575,6 +575,19 @@ proc setupEthRpc*(
|
||||
return Opt.some(recs)
|
||||
except CatchableError:
|
||||
return Opt.none(seq[ReceiptObject])
|
||||
|
||||
server.rpc("eth_createAccessList") do(args: TransactionArgs, quantityTag: BlockTag) -> AccessListResult:
|
||||
try:
|
||||
let
|
||||
header = chainDB.headerFromTag(quantityTag)
|
||||
args = callData(args)
|
||||
|
||||
return createAccessList(header, com, args)
|
||||
except CatchableError as exc:
|
||||
return AccessListResult(
|
||||
error: some("createAccessList error: " & exc.msg),
|
||||
)
|
||||
|
||||
#[
|
||||
server.rpc("eth_newFilter") do(filterOptions: FilterOptions) -> int:
|
||||
## Creates a filter object, based on filter options, to notify when the state changes (logs).
|
||||
|
@ -12,14 +12,21 @@
|
||||
import
|
||||
std/[strutils, algorithm, options],
|
||||
./rpc_types,
|
||||
eth/[common, keys],
|
||||
eth/keys,
|
||||
../common/common,
|
||||
../db/core_db,
|
||||
../db/ledger,
|
||||
../constants, stint,
|
||||
../utils/utils,
|
||||
../transaction,
|
||||
../transaction/call_evm,
|
||||
../core/eip4844,
|
||||
../beacon/web3_eth_conv
|
||||
../beacon/web3_eth_conv,
|
||||
../vm_types,
|
||||
../vm_state,
|
||||
../evm/precompiles,
|
||||
../evm/tracer/access_list_tracer
|
||||
|
||||
|
||||
const
|
||||
defaultTag = blockId("latest")
|
||||
@ -108,19 +115,18 @@ template optionalU256(src, dst: untyped) =
|
||||
if src.isSome:
|
||||
dst = some(src.get)
|
||||
|
||||
template optionalBytes(src, dst: untyped) =
|
||||
if src.isSome:
|
||||
dst = src.get
|
||||
|
||||
proc callData*(call: TransactionArgs): RpcCallData {.gcsafe, raises: [].} =
|
||||
optionalAddress(call.source, result.source)
|
||||
optionalAddress(call.to, result.to)
|
||||
optionalGas(call.gas, result.gasLimit)
|
||||
optionalGas(call.gasPrice, result.gasPrice)
|
||||
optionalGas(call.maxFeePerGas, result.maxFee)
|
||||
optionalGas(call.maxPriorityFeePerGas, result.maxPriorityFee)
|
||||
optionalU256(call.value, result.value)
|
||||
optionalBytes(call.data, result.data)
|
||||
proc callData*(args: TransactionArgs): RpcCallData {.gcsafe, raises: [].} =
|
||||
optionalAddress(args.source, result.source)
|
||||
optionalAddress(args.to, result.to)
|
||||
optionalGas(args.gas, result.gasLimit)
|
||||
optionalGas(args.gasPrice, result.gasPrice)
|
||||
optionalGas(args.maxFeePerGas, result.maxFee)
|
||||
optionalGas(args.maxPriorityFeePerGas, result.maxPriorityFee)
|
||||
optionalU256(args.value, result.value)
|
||||
result.data = args.payload()
|
||||
if args.blobVersionedHashes.isSome:
|
||||
result.versionedHashes = ethHashes args.blobVersionedHashes.get
|
||||
result.accessList = ethAccessList args.accessList
|
||||
|
||||
proc toWd(wd: Withdrawal): WithdrawalObject =
|
||||
WithdrawalObject(
|
||||
@ -135,22 +141,6 @@ proc toWdList(list: openArray[Withdrawal]): seq[WithdrawalObject] =
|
||||
for x in list:
|
||||
result.add toWd(x)
|
||||
|
||||
proc toHashList(list: openArray[StorageKey]): seq[Web3Hash] =
|
||||
result = newSeqOfCap[Web3Hash](list.len)
|
||||
for x in list:
|
||||
result.add Web3Hash x
|
||||
|
||||
proc toAccessTuple(ac: AccessPair): AccessTuple =
|
||||
AccessTuple(
|
||||
address: w3Addr ac.address,
|
||||
storageKeys: toHashList(ac.storageKeys)
|
||||
)
|
||||
|
||||
proc toAccessTupleList(list: openArray[AccessPair]): seq[AccessTuple] =
|
||||
result = newSeqOfCap[AccessTuple](list.len)
|
||||
for x in list:
|
||||
result.add toAccessTuple(x)
|
||||
|
||||
proc populateTransactionObject*(tx: Transaction,
|
||||
optionalHeader: Option[BlockHeader] = none(BlockHeader),
|
||||
txIndex: Option[int] = none(int)): TransactionObject
|
||||
@ -180,7 +170,7 @@ proc populateTransactionObject*(tx: Transaction,
|
||||
|
||||
if tx.txType >= TxEip2930:
|
||||
result.chainId = some(Web3Quantity(tx.chainId))
|
||||
result.accessList = some(toAccessTupleList(tx.accessList))
|
||||
result.accessList = some(w3AccessList(tx.accessList))
|
||||
|
||||
if tx.txType >= TxEIP4844:
|
||||
result.maxFeePerBlobGas = some(tx.maxFeePerBlobGas)
|
||||
@ -301,3 +291,55 @@ proc populateReceipt*(receipt: Receipt, gasUsed: GasInt, tx: Transaction,
|
||||
if tx.txType == TxEip4844:
|
||||
result.blobGasUsed = some(w3Qty(tx.versionedHashes.len.uint64 * GAS_PER_BLOB.uint64))
|
||||
result.blobGasPrice = some(getBlobBaseFee(header.excessBlobGas.get(0'u64)))
|
||||
|
||||
proc createAccessList*(header: BlockHeader,
|
||||
com: CommonRef,
|
||||
args: RpcCallData): AccessListResult {.gcsafe, raises:[CatchableError].} =
|
||||
var args = args
|
||||
|
||||
# If the gas amount is not set, default to RPC gas cap.
|
||||
if args.gasLimit.isNone:
|
||||
args.gasLimit = some(DEFAULT_RPC_GAS_CAP)
|
||||
|
||||
let
|
||||
vmState = BaseVMState.new(header, com)
|
||||
fork = com.toEVMFork(forkDeterminationInfo(header.blockNumber, header.timestamp))
|
||||
sender = args.source.get(ZERO_ADDRESS)
|
||||
# TODO: nonce should be retrieved from txPool
|
||||
nonce = vmState.stateDB.getNonce(sender)
|
||||
to = if args.to.isSome: args.to.get
|
||||
else: generateAddress(sender, nonce)
|
||||
precompiles = activePrecompilesList(fork)
|
||||
|
||||
var prevTracer = AccessListTracer.new(
|
||||
args.accessList,
|
||||
sender,
|
||||
to,
|
||||
precompiles)
|
||||
|
||||
while true:
|
||||
# Retrieve the current access list to expand
|
||||
let accessList = prevTracer.accessList()
|
||||
|
||||
# Set the accesslist to the last accessList
|
||||
# generated by prevTracer
|
||||
args.accessList = accessList
|
||||
|
||||
# Apply the transaction with the access list tracer
|
||||
let
|
||||
tracer = AccessListTracer.new(accessList, sender, to, precompiles)
|
||||
vmState = BaseVMState.new(header, com, tracer)
|
||||
res = rpcCallEvm(args, header, com, vmState)
|
||||
|
||||
if res.isError:
|
||||
return AccessListResult(
|
||||
error: some("failed to apply transaction: " & res.error),
|
||||
)
|
||||
|
||||
if tracer.equal(prevTracer):
|
||||
return AccessListResult(
|
||||
accessList: w3AccessList accessList,
|
||||
gasUsed: w3Qty res.gasUsed,
|
||||
)
|
||||
|
||||
prevTracer = tracer
|
||||
|
@ -97,6 +97,19 @@ proc rpcCallEvm*(call: RpcCallData, header: BlockHeader, com: CommonRef): CallRe
|
||||
|
||||
runComputation(params)
|
||||
|
||||
proc rpcCallEvm*(call: RpcCallData,
|
||||
header: BlockHeader,
|
||||
com: CommonRef,
|
||||
vmState: BaseVMState): CallResult
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
const globalGasCap = 0 # TODO: globalGasCap should configurable by user
|
||||
let params = toCallParams(vmState, call, globalGasCap, header.fee)
|
||||
|
||||
var dbTx = com.db.beginTransaction()
|
||||
defer: dbTx.dispose() # always dispose state changes
|
||||
|
||||
runComputation(params)
|
||||
|
||||
proc rpcEstimateGas*(cd: RpcCallData, header: BlockHeader, com: CommonRef, gasCap: GasInt): GasInt
|
||||
{.gcsafe, raises: [CatchableError].} =
|
||||
# Binary search the gas requirement, as it may be higher than the amount used
|
||||
|
Loading…
x
Reference in New Issue
Block a user