Skip to content

Commit 408406a

Browse files
authored
Fluffy: Implement AsyncEvm createAccessList and eth_createAccessList RPC endpoint (#3201)
1 parent a9cd91c commit 408406a

File tree

5 files changed

+204
-21
lines changed

5 files changed

+204
-21
lines changed

fluffy/evm/async_evm.nim

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@ import
1414
chronicles,
1515
stint,
1616
results,
17-
eth/common/[hashes, addresses, accounts, headers],
18-
../../execution_chain/db/ledger,
17+
eth/common/[base, hashes, addresses, accounts, headers, transactions],
18+
../../execution_chain/db/[ledger, access_list],
1919
../../execution_chain/common/common,
2020
../../execution_chain/transaction/call_evm,
2121
../../execution_chain/evm/[types, state, evm_errors]
2222

2323
from web3/eth_api_types import TransactionArgs
2424

2525
export
26-
results, chronos, hashes, addresses, accounts, headers, TransactionArgs, CallResult
26+
results, chronos, hashes, addresses, accounts, headers, TransactionArgs, CallResult,
27+
transactions.AccessList, GasInt
2728

2829
logScope:
2930
topics = "async_evm"
@@ -132,9 +133,15 @@ proc init*(
132133

133134
AsyncEvm(com: com, backend: backend)
134135

135-
proc call*(
136-
evm: AsyncEvm, header: Header, tx: TransactionArgs, optimisticStateFetch = true
137-
): Future[Result[CallResult, string]] {.async: (raises: [CancelledError]).} =
136+
proc call(
137+
evm: AsyncEvm,
138+
header: Header,
139+
tx: TransactionArgs,
140+
optimisticStateFetch: bool,
141+
collectAccessList: bool,
142+
): Future[Result[(CallResult, transactions.AccessList), string]] {.
143+
async: (raises: [CancelledError])
144+
.} =
138145
let
139146
to = tx.to.valueOr:
140147
return err("to address is required")
@@ -179,7 +186,7 @@ proc call*(
179186
var
180187
lastWitnessKeys: WitnessTable
181188
witnessKeys = vmState.ledger.getWitnessKeys()
182-
callResult: EvmResult[CallResult]
189+
evmResult: EvmResult[CallResult]
183190
evmCallCount = 0
184191

185192
# Limit the max number of calls to prevent infinite loops and/or DOS in the
@@ -188,7 +195,7 @@ proc call*(
188195
debug "Starting AsyncEvm execution", evmCallCount
189196

190197
let sp = vmState.ledger.beginSavepoint()
191-
callResult = rpcCallEvm(tx, header, vmState, EVM_CALL_GAS_CAP)
198+
evmResult = rpcCallEvm(tx, header, vmState, EVM_CALL_GAS_CAP)
192199
inc evmCallCount
193200
vmState.ledger.rollback(sp) # all state changes from the call are reverted
194201

@@ -213,6 +220,8 @@ proc call*(
213220
var stateFetchDone = false
214221
for k, v in witnessKeys:
215222
let (adr, _) = k
223+
if adr == default(Address):
224+
continue
216225

217226
if v.storageMode:
218227
let slotIdx = (adr, v.storageSlot)
@@ -224,7 +233,7 @@ proc call*(
224233
storageQueries.add(StorageQuery.init(adr, v.storageSlot, storageFut))
225234
if not optimisticStateFetch:
226235
stateFetchDone = true
227-
elif adr != default(Address):
236+
else:
228237
doAssert(adr == v.address)
229238

230239
if adr notin fetchedAccounts:
@@ -280,7 +289,57 @@ proc call*(
280289
# TODO: why do the above futures throw a CatchableError and not CancelledError?
281290
raiseAssert(e.msg)
282291

283-
callResult.mapErr(
284-
proc(e: EvmErrorObj): string =
285-
"EVM execution failed: " & $e.code
286-
)
292+
# If collectAccessList is enabled then build the access list from the
293+
# witness keys and then execute the transaction one last time using the final
294+
# access list which will impact the gas used value returned in the callResult.
295+
var tx = tx
296+
if evmResult.isOk() and collectAccessList:
297+
let fromAdr = tx.`from`.get(default(Address))
298+
299+
var al = access_list.AccessList.init()
300+
for lookupKey, witnessKey in witnessKeys:
301+
let (adr, _) = lookupKey
302+
if adr == fromAdr:
303+
continue
304+
305+
if witnessKey.storageMode:
306+
al.add(adr, witnessKey.storageSlot)
307+
else:
308+
al.add(adr)
309+
310+
tx.accessList = Opt.some(al.getAccessList()) # converts to transactions.AccessList
311+
312+
let sp = vmState.ledger.beginSavepoint()
313+
evmResult = rpcCallEvm(tx, header, vmState, EVM_CALL_GAS_CAP)
314+
inc evmCallCount
315+
vmState.ledger.rollback(sp) # all state changes from the call are reverted
316+
317+
let callResult =
318+
?evmResult.mapErr(
319+
proc(e: EvmErrorObj): string =
320+
"EVM execution failed: " & $e.code
321+
)
322+
323+
if callResult.error.len() > 0:
324+
err("EVM execution failed: " & callResult.error)
325+
else:
326+
ok((callResult, tx.accessList.get(@[])))
327+
328+
proc call*(
329+
evm: AsyncEvm, header: Header, tx: TransactionArgs, optimisticStateFetch = true
330+
): Future[Result[CallResult, string]] {.async: (raises: [CancelledError]).} =
331+
let (callResult, _) =
332+
?(await evm.call(header, tx, optimisticStateFetch, collectAccessList = false))
333+
ok(callResult)
334+
335+
proc createAccessList*(
336+
evm: AsyncEvm, header: Header, tx: TransactionArgs, optimisticStateFetch = true
337+
): Future[Result[(transactions.AccessList, GasInt), string]] {.
338+
async: (raises: [CancelledError])
339+
.} =
340+
let (callResult, accessList) =
341+
?(await evm.call(header, tx, optimisticStateFetch, collectAccessList = true))
342+
343+
# TODO: Do we need to use the estimate gas calculation here to get a more acturate
344+
# estimation of the gas requirement?
345+
ok((accessList, callResult.gasUsed))

fluffy/rpc/rpc_calls/rpc_eth_calls.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,6 @@ createRpcSigsFromNim(RpcClient):
4949
): ProofResponse
5050

5151
proc eth_call(tx: TransactionArgs, blockId: BlockIdentifier): seq[byte]
52+
proc eth_createAccessList(
53+
tx: TransactionArgs, blockId: BlockIdentifier
54+
): AccessListResult

fluffy/rpc/rpc_debug_api.nim

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,4 @@ proc installDebugApiHandlers*(rpcServer: RpcServer, stateNetwork: Opt[StateNetwo
174174
let callResult = (await evm.call(header, tx, optimisticStateFetch)).valueOr:
175175
raise newException(ValueError, error)
176176

177-
if callResult.error.len() > 0:
178-
raise newException(ValueError, callResult.error)
179-
180177
callResult.output

fluffy/rpc/rpc_eth_api.nim

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,43 @@ proc installEthApiHandlers*(
468468
let callResult = (await evm.call(header, tx, optimisticStateFetch)).valueOr:
469469
raise newException(ValueError, error)
470470

471-
if callResult.error.len() > 0:
472-
raise newException(ValueError, callResult.error)
471+
return callResult.output
473472

474-
callResult.output
473+
rpcServer.rpc("eth_createAccessList") do(
474+
tx: TransactionArgs, quantityTag: RtBlockIdentifier, optimisticStateFetch: Opt[bool]
475+
) -> AccessListResult:
476+
## Creates an EIP-2930 access list that you can include in a transaction.
477+
##
478+
## tx: the transaction call object which contains
479+
## from: (optional) The address the transaction is sent from.
480+
## to: The address the transaction is directed to.
481+
## gas: (optional) Integer of the gas provided for the transaction execution.
482+
## eth_call consumes zero gas, but this parameter may be needed by some executions.
483+
## gasPrice: (optional) Integer of the gasPrice used for each paid gas.
484+
## value: (optional) Integer of the value sent with this transaction.
485+
## input: (optional) Hash of the method signature and encoded parameters.
486+
## quantityTag: integer block number, or the string "latest", "earliest" or "pending",
487+
## see the default block parameter.
488+
## Returns: the access list object which contains the addresses and storage keys which
489+
## are read by the transaction.
490+
491+
if tx.to.isNone():
492+
raise newException(ValueError, "to address is required")
493+
494+
if quantityTag.kind == bidAlias:
495+
raise newException(ValueError, "tag not yet implemented")
496+
497+
let
498+
hn = historyNetwork.getOrRaise()
499+
evm = asyncEvm.getOrRaise()
500+
header = (await hn.getVerifiedBlockHeader(quantityTag.number.uint64)).valueOr:
501+
raise newException(ValueError, "Unable to get block header")
502+
optimisticStateFetch = optimisticStateFetch.valueOr:
503+
true
504+
505+
let (accessList, gasUsed) = (
506+
await evm.createAccessList(header, tx, optimisticStateFetch)
507+
).valueOr:
508+
raise newException(ValueError, error)
509+
510+
return AccessListResult(accessList: accessList, gasUsed: gasUsed.Quantity)

fluffy/tests/evm/test_async_evm.nim

Lines changed: 90 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,104 @@ procSuite "Async EVM":
6666
callData = "0xa888c2cd0000000000000000000000000000000000000000000000000000000000000007".hexToSeqByte()
6767
tx = TransactionArgs(to: Opt.some(address), input: Opt.some(callData))
6868

69-
asyncTest "Test basic call - optimistic state fetch enabled":
69+
asyncTest "Basic call - optimistic state fetch enabled":
7070
let callResult = (await evm.call(header, tx, optimisticStateFetch = true)).expect(
7171
"successful call"
7272
)
7373
check callResult.output ==
7474
"0x000000000000000000000000fb7bc66a002762e28545ea0a7fc970d381863c420000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000c5df000000000000000000000000000000000000000000000000000000000000002d536174697366792056616c756573207468726f75676820467269656e647368697020616e6420506f6e6965732100000000000000000000000000000000000000".hexToSeqByte()
7575

76-
asyncTest "Test basic call - optimistic state fetch disabled":
76+
asyncTest "Basic call - optimistic state fetch disabled":
7777
let callResult = (await evm.call(header, tx, optimisticStateFetch = false)).expect(
7878
"successful call"
7979
)
8080
check callResult.output ==
8181
"0x000000000000000000000000fb7bc66a002762e28545ea0a7fc970d381863c420000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000c5df000000000000000000000000000000000000000000000000000000000000002d536174697366792056616c756573207468726f75676820467269656e647368697020616e6420506f6e6965732100000000000000000000000000000000000000".hexToSeqByte()
82+
83+
asyncTest "Create access list - optimistic state fetch enabled":
84+
let (accessList, gasUsed) = (
85+
await evm.createAccessList(header, tx, optimisticStateFetch = true)
86+
).expect("successful call")
87+
88+
check:
89+
accessList.len() == 1
90+
gasUsed > 0
91+
accessList[0].address == address
92+
93+
let storageKeys = accessList[0].storageKeys
94+
check:
95+
storageKeys.len() == 6
96+
storageKeys.contains(
97+
Bytes32.fromHex(
98+
"0x80f0598597d7a1012e2e0a89cab2b766e02a3a5e30768662751fe258f5389667"
99+
)
100+
)
101+
storageKeys.contains(
102+
Bytes32.fromHex(
103+
"0x80f0598597d7a1012e2e0a89cab2b766e02a3a5e30768662751fe258f5389668"
104+
)
105+
)
106+
storageKeys.contains(
107+
Bytes32.fromHex(
108+
"0x0000000000000000000000000000000000000000000000000000000000000000"
109+
)
110+
)
111+
storageKeys.contains(
112+
Bytes32.fromHex(
113+
"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e578"
114+
)
115+
)
116+
storageKeys.contains(
117+
Bytes32.fromHex(
118+
"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e57a"
119+
)
120+
)
121+
storageKeys.contains(
122+
Bytes32.fromHex(
123+
"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e579"
124+
)
125+
)
126+
127+
asyncTest "Create access list - optimistic state fetch disabled":
128+
let (accessList, gasUsed) = (
129+
await evm.createAccessList(header, tx, optimisticStateFetch = false)
130+
).expect("successful call")
131+
132+
check:
133+
accessList.len() == 1
134+
gasUsed > 0
135+
accessList[0].address == address
136+
137+
let storageKeys = accessList[0].storageKeys
138+
check:
139+
storageKeys.len() == 6
140+
storageKeys.contains(
141+
Bytes32.fromHex(
142+
"0x80f0598597d7a1012e2e0a89cab2b766e02a3a5e30768662751fe258f5389667"
143+
)
144+
)
145+
storageKeys.contains(
146+
Bytes32.fromHex(
147+
"0x80f0598597d7a1012e2e0a89cab2b766e02a3a5e30768662751fe258f5389668"
148+
)
149+
)
150+
storageKeys.contains(
151+
Bytes32.fromHex(
152+
"0x0000000000000000000000000000000000000000000000000000000000000000"
153+
)
154+
)
155+
storageKeys.contains(
156+
Bytes32.fromHex(
157+
"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e578"
158+
)
159+
)
160+
storageKeys.contains(
161+
Bytes32.fromHex(
162+
"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e57a"
163+
)
164+
)
165+
storageKeys.contains(
166+
Bytes32.fromHex(
167+
"0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e579"
168+
)
169+
)

0 commit comments

Comments
 (0)