Skip to content

Commit a30ff4a

Browse files
authored
Support BLOCKHASH opcode in Async EVM (#3512)
* Update async evm to support blockhash opcode. * Add blockHashProc AsyncEvmStateBackend to the verified proxy. Get block hashes from the header store. Increase the default cache size to 256.
1 parent 6615373 commit a30ff4a

File tree

8 files changed

+120
-17
lines changed

8 files changed

+120
-17
lines changed

execution_chain/evm/async_evm.nim

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import
1414
chronicles,
1515
stint,
1616
results,
17+
minilru,
1718
eth/common/[base, addresses, accounts, headers, transactions],
1819
../db/[ledger, access_list],
1920
../common/common,
@@ -56,12 +57,6 @@ logScope:
5657
# default approach where the state lookups are wired directly into the EVM gives
5758
# the worst case performance because all state accesses inside the EVM are
5859
# completely sequential.
59-
#
60-
# Note: The BLOCKHASH opt code is not yet supported by this implementation and so
61-
# transactions which use this opt code will simply get the empty/default hash
62-
# for any requested block. After the Pectra hard fork this opt code will be
63-
# implemented using a system contract with the data stored in the Ethereum state
64-
# trie/s and at that point it should just work without changes to the async evm here.
6560

6661
const
6762
EVM_CALL_LIMIT = 10_000
@@ -81,6 +76,10 @@ type
8176
address: Address
8277
codeFut: Future[Opt[seq[byte]]]
8378

79+
BlockHashQuery = object
80+
number: BlockNumber
81+
blockHashFut: Future[Opt[Hash32]]
82+
8483
AsyncEvm* = ref object
8584
com: CommonRef
8685
backend: AsyncEvmStateBackend
@@ -96,6 +95,9 @@ func init(
9695
func init(T: type CodeQuery, adr: Address, fut: Future[Opt[seq[byte]]]): T =
9796
T(address: adr, codeFut: fut)
9897

98+
func init(T: type BlockHashQuery, number: BlockNumber, fut: Future[Opt[Hash32]]): T =
99+
T(number: number, blockHashFut: fut)
100+
99101
proc init*(
100102
T: type AsyncEvm, backend: AsyncEvmStateBackend, networkId: NetworkId = MainNet
101103
): T =
@@ -138,6 +140,7 @@ proc callFetchingState(
138140
fetchedAccounts = initHashSet[Address]()
139141
fetchedStorage = initHashSet[(Address, UInt256)]()
140142
fetchedCode = initHashSet[Address]()
143+
fetchedBlockHashes = initHashSet[BlockNumber]()
141144

142145
# Set code of the 'to' address in the EVM so that we can execute the transaction
143146
let code = (await evm.backend.getCode(header, to)).valueOr:
@@ -158,6 +161,8 @@ proc callFetchingState(
158161
debug "Starting AsyncEvm execution", evmCallCount
159162

160163
vmState.ledger.clearWitnessKeys()
164+
vmState.ledger.clearBlockHashesCache()
165+
161166
let sp = vmState.ledger.beginSavepoint()
162167
evmResult = rpcCallEvm(tx, header, vmState, EVM_CALL_GAS_CAP)
163168
inc evmCallCount
@@ -172,6 +177,7 @@ proc callFetchingState(
172177
accountQueries = newSeq[AccountQuery]()
173178
storageQueries = newSeq[StorageQuery]()
174179
codeQueries = newSeq[CodeQuery]()
180+
blockHashQueries = newSeq[BlockHashQuery]()
175181

176182
# Loop through the collected keys and fetch the state concurrently.
177183
# If optimisticStateFetch is enabled then we fetch state for all the witness
@@ -212,6 +218,13 @@ proc callFetchingState(
212218
if not optimisticStateFetch:
213219
stateFetchDone = true
214220

221+
for number, blockHash in vmState.ledger.getBlockHashesCache():
222+
if number notin fetchedBlockHashes:
223+
debug "Fetching block hash", blockNumber = number
224+
let blockHashFut = evm.backend.getBlockHash(header, number)
225+
blockHashQueries.add(BlockHashQuery.init(number, blockHashFut))
226+
227+
215228
if optimisticStateFetch:
216229
# If the witness keys did not change after the last execution then we can
217230
# stop the execution loop because we have already executed the transaction
@@ -245,6 +258,13 @@ proc callFetchingState(
245258
return err("Unable to get code")
246259
vmState.ledger.setCode(q.address, code)
247260
fetchedCode.incl(q.address)
261+
262+
for q in blockHashQueries:
263+
let blockHash = (await q.blockHashFut).valueOr:
264+
return err("Unable to get block hash")
265+
vmState.ledger.txFrame.addBlockNumberToHashLookup(q.number, blockHash)
266+
fetchedBlockHashes.incl(q.number)
267+
248268
except CancelledError as e:
249269
raise e
250270
except CatchableError as e:

execution_chain/evm/async_evm_backend.nim

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,25 @@ type
2424
async: (raises: [CancelledError])
2525
.}
2626

27+
GetBlockHashProc* = proc(header: Header, number: BlockNumber): Future[Opt[Hash32]] {.
28+
async: (raises: [CancelledError])
29+
.}
30+
2731
AsyncEvmStateBackend* = ref object
2832
getAccount*: GetAccountProc
2933
getStorage*: GetStorageProc
3034
getCode*: GetCodeProc
35+
getBlockHash*: GetBlockHashProc
3136

3237
proc init*(
3338
T: type AsyncEvmStateBackend,
3439
accProc: GetAccountProc,
3540
storageProc: GetStorageProc,
3641
codeProc: GetCodeProc,
42+
blockHashProc: GetBlockHashProc,
3743
): T =
38-
AsyncEvmStateBackend(getAccount: accProc, getStorage: storageProc, getCode: codeProc)
44+
AsyncEvmStateBackend(
45+
getAccount: accProc,
46+
getStorage: storageProc,
47+
getCode: codeProc,
48+
getBlockHash: blockHashProc)

nimbus_verified_proxy/header_store.nim

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func convLCHeader*(lcHeader: ForkedLightClientHeader): Result[Header, string] =
4848
parentBeaconBlockRoot = Opt.none(Hash32)
4949

5050
when lcDataFork >= LightClientDataFork.Electra:
51-
# INFO: there is no visibility of the execution requests hash in light client header
51+
# INFO: there is no visibility of the execution requests hash in light client header
5252
let requestsHash = Opt.none(Hash32)
5353
else:
5454
const requestsHash = Opt.none(Hash32)
@@ -189,6 +189,9 @@ func latestHash*(self: HeaderStore): Opt[Hash32] =
189189

190190
Opt.none(Hash32)
191191

192+
func getHash*(self: HeaderStore, number: base.BlockNumber): Opt[Hash32] =
193+
self.hashes.peek(number)
194+
192195
func get*(self: HeaderStore, number: base.BlockNumber): Opt[Header] =
193196
let hash = self.hashes.peek(number).valueOr:
194197
return Opt.none(Header)

nimbus_verified_proxy/nimbus_verified_proxy_conf.nim

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,13 @@ type VerifiedProxyConf* = object # Config
6565
.}: OutDir
6666

6767
# In-Memory Cache Size
68+
# In order to support the BLOCKHASH opcode for eth_call we need at least
69+
# MAX_PREV_HEADER_DEPTH headers in the header cache.
6870
cacheLen* {.
6971
hidden,
7072
desc: "Length of the header cache maintained in memory",
71-
defaultValue: 64,
72-
defaultValueDesc: "64",
73+
defaultValue: MAX_PREV_HEADER_DEPTH,
74+
defaultValueDesc: "256",
7375
name: "debug-cache-len"
7476
.}: int
7577

nimbus_verified_proxy/rpc/evm.nim

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import
1212
../../execution_chain/evm/async_evm_backend,
1313
../../execution_chain/evm/async_evm,
1414
./accounts,
15+
../header_store,
1516
../types
1617

1718
logScope:
@@ -66,4 +67,9 @@ proc toAsyncEvmStateBackend*(vp: VerifiedRpcProxy): AsyncEvmStateBackend =
6667

6768
Opt.none(seq[byte])
6869

69-
AsyncEvmStateBackend.init(accProc, storageProc, codeProc)
70+
blockHashProc = proc(
71+
header: Header, number: BlockNumber
72+
): Future[Opt[Hash32]] {.async: (raises: [CancelledError]).} =
73+
vp.headerStore.getHash(number)
74+
75+
AsyncEvmStateBackend.init(accProc, storageProc, codeProc, blockHashProc)

nimbus_verified_proxy/rpc/rpc_eth_api.nim

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,6 @@ proc installEthApiHandlers*(vp: VerifiedRpcProxy) =
233233
# Following methods are forwarded directly to the web3 provider and therefore
234234
# are not validated in any way.
235235
vp.proxy.registerProxyMethod("net_version")
236-
vp.proxy.registerProxyMethod("eth_call")
237236
vp.proxy.registerProxyMethod("eth_sendRawTransaction")
238237
vp.proxy.registerProxyMethod("eth_getTransactionReceipt")
239238

tests/async_evm_test_backend.nim

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type TestEvmState* = ref object
1313
accounts: Table[Address, Account]
1414
storage: Table[Address, Table[UInt256, UInt256]]
1515
code: Table[Address, seq[byte]]
16+
blockHashes: Table[BlockNumber, Hash32]
1617

1718
proc init*(T: type TestEvmState): T =
1819
TestEvmState()
@@ -30,6 +31,9 @@ proc setStorage*(
3031
proc setCode*(backend: TestEvmState, address: Address, code: seq[byte]) =
3132
backend.code[address] = code
3233

34+
proc setBlockHash*(backend: TestEvmState, number: BlockNumber, blockHash: Hash32) =
35+
backend.blockHashes[number] = blockHash
36+
3337
proc getAccount*(backend: TestEvmState, address: Address): Account =
3438
backend.accounts.getOrDefault(address)
3539

@@ -39,8 +43,11 @@ proc getStorage*(backend: TestEvmState, address: Address, slotKey: UInt256): UIn
3943
proc getCode*(backend: TestEvmState, address: Address): seq[byte] =
4044
backend.code.getOrDefault(address)
4145

46+
proc getBlockHash*(backend: TestEvmState, number: BlockNumber): Hash32 =
47+
backend.blockHashes.getOrDefault(number)
48+
4249
proc toAsyncEvmStateBackend*(testState: TestEvmState): AsyncEvmStateBackend =
43-
# State root is ignored because TestEvmState only stores a single state
50+
# header is ignored because TestEvmState only stores a single state
4451
let
4552
accProc = proc(
4653
header: Header, address: Address
@@ -54,5 +61,9 @@ proc toAsyncEvmStateBackend*(testState: TestEvmState): AsyncEvmStateBackend =
5461
header: Header, address: Address
5562
): Future[Opt[seq[byte]]] {.async: (raises: [CancelledError]).} =
5663
Opt.some(testState.getCode(address))
64+
blockHashProc = proc(
65+
header: Header, number: BlockNumber
66+
): Future[Opt[Hash32]] {.async: (raises: [CancelledError]).} =
67+
Opt.some(testState.getBlockHash(number))
5768

58-
AsyncEvmStateBackend.init(accProc, storageProc, codeProc)
69+
AsyncEvmStateBackend.init(accProc, storageProc, codeProc, blockHashProc)

tests/test_async_evm.nim

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import
1111
std/[tables, sets],
1212
stew/byteutils,
1313
unittest2,
14+
eth/common/times,
1415
../execution_chain/evm/async_evm,
1516
./async_evm_test_backend
1617

@@ -25,24 +26,63 @@ type
2526
TestAccount =
2627
tuple[balance: string, nonce: string, code: string, storage: TestStorage]
2728

29+
TestBlockNumber = string
30+
TestBlockHash = string
31+
TestBlockHashes = Table[TestBlockNumber, TestBlockHash]
32+
2833
TestTxArgs = tuple[to: string, input: string]
2934

30-
TestResult = tuple[output: string, accessList: Table[TestAddress, TestStorageKeys]]
35+
TestAccessList = Table[TestAddress, TestStorageKeys]
36+
TestResult = tuple[output: string, accessList: TestAccessList]
3137

3238
TestCase =
3339
tuple[
3440
preState: Table[TestAddress, TestAccount],
41+
blockHashes: TestBlockHashes,
3542
blockNumber: string,
3643
txArgs: TestTxArgs,
3744
expected: TestResult,
3845
]
3946

4047
const
41-
emptyStorage = default(Table[string, string])
42-
emptyStorageKeys = default(HashSet[string])
48+
emptyStorage = default(TestStorage)
49+
emptyStorageKeys = default(TestStorageKeys)
50+
emptyAccessList = default(TestAccessList)
51+
emptyBlockHashes = default(TestBlockHashes)
4352

4453
const testCases: seq[TestCase] =
4554
@[
55+
# Block hash test which executes bytecode compiled from the following solidity:
56+
#
57+
# pragma solidity >=0.8.30 <0.9.0;
58+
#
59+
# contract BlockHashContract {
60+
# function getBlockHash() public view returns(bytes32) {
61+
# return blockhash(block.number - 10);
62+
# }
63+
# }
64+
(
65+
preState: {
66+
"0x358aa13c52544eccef6b0add0f801012adad5ee3": (
67+
balance: "0x",
68+
nonce: "0x",
69+
code:
70+
"0x6080604052348015600e575f5ffd5b50600436106026575f3560e01c80639663f88f14602a575b5f5ffd5b60306044565b604051603b9190606c565b60405180910390f35b5f600a436050919060b9565b40905090565b5f819050919050565b6066816056565b82525050565b5f602082019050607d5f830184605f565b92915050565b5f819050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f60c1826083565b915060ca836083565b925082820390508181111560df5760de608c565b5b9291505056fea26469706673582212205d02238b0582fce570b72434d696e873371bf590e00d5deed50555a523df0e8764736f6c634300081e0033",
71+
storage: emptyStorage,
72+
),
73+
}.toTable,
74+
blockHashes: {
75+
"0x015EE5A0": "0x536174697366792056616c756573207468726f75676820467269656e64736869",
76+
}.toTable,
77+
blockNumber: "0x015EE5A9",
78+
txArgs: (to: "0x358aa13c52544eccef6b0add0f801012adad5ee3", input: "0x9663f88f"),
79+
expected: (
80+
output: "0x536174697366792056616c756573207468726f75676820467269656e64736869",
81+
accessList: {
82+
"0x358aa13c52544eccef6b0add0f801012adad5ee3": emptyStorageKeys,
83+
}.toTable,
84+
)
85+
),
4686
# Test state from mainnet contract 0x1a875Da9e86506999A2931400475C34c27185Dd1 at block 700_000
4787
(
4888
preState: {
@@ -61,6 +101,7 @@ const testCases: seq[TestCase] =
61101
storage: emptyStorage,
62102
),
63103
}.toTable,
104+
blockHashes: emptyBlockHashes,
64105
blockNumber: "0x0AAE60",
65106
txArgs: (to: "0x1a875da9e86506999a2931400475c34c27185dd1", input: "0x"),
66107
expected: (
@@ -95,6 +136,7 @@ const testCases: seq[TestCase] =
95136
}.toTable,
96137
)
97138
}.toTable,
139+
blockHashes: emptyBlockHashes,
98140
blockNumber: "0x0F4240",
99141
txArgs: (
100142
to: "0x6e38a457c722c6011b2dfa06d49240e797844d66",
@@ -133,6 +175,9 @@ proc setupTestEvmState(testCase: TestCase): TestEvmState =
133175
for k, v in acc.storage:
134176
testState.setStorage(address, k.hexToUInt256(), v.hexToUInt256())
135177

178+
for number, blockHash in testCase.blockHashes:
179+
testState.setBlockHash(number.hexToUInt256().truncate(BlockNumber), Hash32.fromHex(blockHash))
180+
136181
return testState
137182

138183
suite "Async EVM":
@@ -143,6 +188,7 @@ suite "Async EVM":
143188
evm = AsyncEvm.init(testState.toAsyncEvmStateBackend())
144189
header = Header(
145190
number: testCase.blockNumber.hexToUInt256().truncate(uint64),
191+
timestamp: 1_746_612_312.EthTime, # pragueTime: Opt.some(1_746_612_311.EthTime)
146192
gasLimit: EVM_CALL_GAS_CAP,
147193
)
148194
tx = TransactionArgs(
@@ -161,6 +207,7 @@ suite "Async EVM":
161207
evm = AsyncEvm.init(testState.toAsyncEvmStateBackend())
162208
header = Header(
163209
number: testCase.blockNumber.hexToUInt256().truncate(uint64),
210+
timestamp: 1_746_612_312.EthTime, # pragueTime: Opt.some(1_746_612_311.EthTime)
164211
gasLimit: EVM_CALL_GAS_CAP,
165212
)
166213
tx = TransactionArgs(
@@ -179,6 +226,7 @@ suite "Async EVM":
179226
evm = AsyncEvm.init(testState.toAsyncEvmStateBackend())
180227
header = Header(
181228
number: testCase.blockNumber.hexToUInt256().truncate(uint64),
229+
timestamp: 1_746_612_312.EthTime, # pragueTime: Opt.some(1_746_612_311.EthTime)
182230
gasLimit: EVM_CALL_GAS_CAP,
183231
)
184232
tx = TransactionArgs(
@@ -214,6 +262,7 @@ suite "Async EVM":
214262
evm = AsyncEvm.init(testState.toAsyncEvmStateBackend())
215263
header = Header(
216264
number: testCase.blockNumber.hexToUInt256().truncate(uint64),
265+
timestamp: 1_746_612_312.EthTime, # pragueTime: Opt.some(1_746_612_311.EthTime)
217266
gasLimit: EVM_CALL_GAS_CAP,
218267
)
219268
tx = TransactionArgs(
@@ -249,6 +298,7 @@ suite "Async EVM":
249298
evm = AsyncEvm.init(testState.toAsyncEvmStateBackend())
250299
header = Header(
251300
number: testCase.blockNumber.hexToUInt256().truncate(uint64),
301+
timestamp: 1_746_612_312.EthTime, # pragueTime: Opt.some(1_746_612_311.EthTime)
252302
gasLimit: EVM_CALL_GAS_CAP,
253303
)
254304
tx = TransactionArgs(
@@ -268,6 +318,7 @@ suite "Async EVM":
268318
evm = AsyncEvm.init(testState.toAsyncEvmStateBackend())
269319
header = Header(
270320
number: testCase.blockNumber.hexToUInt256().truncate(uint64),
321+
timestamp: 1_746_612_312.EthTime, # pragueTime: Opt.some(1_746_612_311.EthTime)
271322
gasLimit: EVM_CALL_GAS_CAP,
272323
)
273324
tx = TransactionArgs(
@@ -289,6 +340,7 @@ suite "Async EVM":
289340
evm = AsyncEvm.init(testState.toAsyncEvmStateBackend())
290341
header = Header(
291342
number: testCase.blockNumber.hexToUInt256().truncate(uint64),
343+
timestamp: 1_746_612_312.EthTime, # pragueTime: Opt.some(1_746_612_311.EthTime)
292344
gasLimit: EVM_CALL_GAS_CAP,
293345
)
294346
tx = TransactionArgs(

0 commit comments

Comments
 (0)