Implement RPC method eth_getAccessList (#2091)

* Implement RPC method eth_getAccessList

* Fix comment
This commit is contained in:
andri lim 2024-03-21 18:24:32 +07:00 committed by GitHub
parent 8ed40c78e0
commit 7ea6d719d9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 236 additions and 53 deletions

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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