Skip to content

Commit 0a3cf9d

Browse files
proxy: txs, rxs and logs (#3457)
* txs, receipts and logs * format * add copyright * Apply suggestions from code review Co-authored-by: bhartnett <[email protected]> * review and fixes * add methods to backend * fixes * fixes * tests * review * lint and fixes * use the type directly * silly fix * review * revert * add pragma * workaround * trigger CI --------- Co-authored-by: bhartnett <[email protected]>
1 parent 5800909 commit 0a3cf9d

File tree

14 files changed

+333059
-22
lines changed

14 files changed

+333059
-22
lines changed

execution_chain/tracer.nim

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,7 @@ proc init(
6060
T.init(com, header.stateRoot)
6161

6262
# -------------------
63-
64-
proc `%`(x: addresses.Address|Bytes32|Bytes256|Hash32): JsonNode =
63+
proc `%`(x: addresses.Address | Bytes32 | Bytes256 | Hash32): JsonNode =
6564
result = %toHex(x)
6665

6766
proc toJson(receipt: Receipt): JsonNode =
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# nimbus_verified_proxy
2+
# Copyright (c) 2025 Status Research & Development GmbH
3+
# Licensed and distributed under either of
4+
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
5+
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
6+
# at your option. This file may not be copied, modified, or distributed except according to those terms.
7+
8+
{.push gcsafe, raises: [].}
9+
10+
import
11+
std/sequtils,
12+
results,
13+
eth/common/eth_types_rlp,
14+
eth/trie/[ordered_trie, trie_defs],
15+
json_rpc/[rpcserver, rpcclient],
16+
web3/[eth_api_types, eth_api],
17+
../../execution_chain/beacon/web3_eth_conv,
18+
../../execution_chain/rpc/filters,
19+
../types,
20+
./blocks
21+
22+
template toLog(lg: LogObject): Log =
23+
Log(address: lg.address, topics: lg.topics, data: lg.data)
24+
25+
func toLogs(logs: openArray[LogObject]): seq[Log] =
26+
logs.mapIt(it.toLog)
27+
28+
func toReceipt(rec: ReceiptObject): Receipt =
29+
let isHash = not rec.status.isSome()
30+
31+
let status = rec.status.isSome() and rec.status.get() == 1.Quantity
32+
33+
return Receipt(
34+
hash: rec.transactionHash,
35+
isHash: isHash,
36+
status: status,
37+
cumulativeGasUsed: rec.cumulativeGasUsed.GasInt,
38+
logs: toLogs(rec.logs),
39+
logsBloom: rec.logsBloom,
40+
receiptType: rec.`type`.get(0.Web3Quantity).ReceiptType,
41+
)
42+
43+
func toReceipts(recs: openArray[ReceiptObject]): seq[Receipt] =
44+
recs.mapIt(it.toReceipt)
45+
46+
proc getReceipts(
47+
vp: VerifiedRpcProxy, header: Header, blockTag: types.BlockTag
48+
): Future[Result[seq[ReceiptObject], string]] {.async.} =
49+
let rxs =
50+
try:
51+
await vp.rpcClient.eth_getBlockReceipts(blockTag)
52+
except CatchableError as e:
53+
return err(e.msg)
54+
55+
if rxs.isSome():
56+
if orderedTrieRoot(toReceipts(rxs.get())) != header.receiptsRoot:
57+
return
58+
err("downloaded receipts do not evaluate to the receipts root of the block")
59+
else:
60+
return err("error downloading the receipts")
61+
62+
return ok(rxs.get())
63+
64+
proc getReceipts*(
65+
vp: VerifiedRpcProxy, blockTag: types.BlockTag
66+
): Future[Result[seq[ReceiptObject], string]] {.async.} =
67+
let
68+
header = (await vp.getHeader(blockTag)).valueOr:
69+
return err(error)
70+
# all other tags are automatically resolved while getting the header
71+
numberTag = types.BlockTag(
72+
kind: BlockIdentifierKind.bidNumber, number: Quantity(header.number)
73+
)
74+
75+
await vp.getReceipts(header, numberTag)
76+
77+
proc getReceipts*(
78+
vp: VerifiedRpcProxy, blockHash: Hash32
79+
): Future[Result[seq[ReceiptObject], string]] {.async.} =
80+
let
81+
header = (await vp.getHeader(blockHash)).valueOr:
82+
return err(error)
83+
numberTag = types.BlockTag(
84+
kind: BlockIdentifierKind.bidNumber, number: Quantity(header.number)
85+
)
86+
87+
await vp.getReceipts(header, numberTag)
88+
89+
proc getLogs*(
90+
vp: VerifiedRpcProxy, filterOptions: FilterOptions
91+
): Future[Result[seq[LogObject], string]] {.async.} =
92+
let logObjs =
93+
try:
94+
await vp.rpcClient.eth_getLogs(filterOptions)
95+
except CatchableError as e:
96+
return err(e.msg)
97+
98+
# store block hashes contains the logs so that we can batch receipt requests
99+
var
100+
prevBlockHash: Hash32
101+
rxs: seq[ReceiptObject]
102+
103+
for lg in logObjs:
104+
# none only for pending logs before block is built
105+
if lg.blockHash.isSome() and lg.transactionIndex.isSome() and lg.logIndex.isSome():
106+
# exploit sequentiality of logs
107+
if prevBlockHash != lg.blockHash.get():
108+
# TODO: a cache will solve downloading the same block receipts for multiple logs
109+
rxs = (await vp.getReceipts(lg.blockHash.get())).valueOr:
110+
return err(error)
111+
prevBlockHash = lg.blockHash.get()
112+
113+
let
114+
txIdx = distinctBase(lg.transactionIndex.get())
115+
logIdx =
116+
distinctBase(lg.logIndex.get()) -
117+
distinctBase(rxs[txIdx].logs[0].logIndex.get())
118+
rxLog = rxs[txIdx].logs[logIdx]
119+
120+
if rxLog.address != lg.address or rxLog.data != lg.data or
121+
rxLog.topics != lg.topics or
122+
(not match(toLog(lg), filterOptions.address, filterOptions.topics)):
123+
return err("one of the returned logs is invalid")
124+
125+
return ok(logObjs)

nimbus_verified_proxy/rpc/rpc_eth_api.nim

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ import
1818
../header_store,
1919
./accounts,
2020
./blocks,
21-
./evm
21+
./evm,
22+
./transactions,
23+
./receipts
2224

2325
logScope:
2426
topics = "verified_proxy"
@@ -229,12 +231,54 @@ proc installEthApiHandlers*(vp: VerifiedRpcProxy) =
229231

230232
return gasEstimate.Quantity
231233

234+
vp.proxy.rpc("eth_getTransactionByHash") do(txHash: Hash32) -> TransactionObject:
235+
let tx =
236+
try:
237+
await vp.rpcClient.eth_getTransactionByHash(txHash)
238+
except CatchableError as e:
239+
raise newException(ValueError, e.msg)
240+
if tx.hash != txHash:
241+
raise newException(
242+
ValueError,
243+
"the downloaded transaction hash doesn't match the requested transaction hash",
244+
)
245+
246+
if not checkTxHash(tx, txHash):
247+
raise
248+
newException(ValueError, "the transaction doesn't hash to the provided hash")
249+
250+
return tx
251+
252+
vp.proxy.rpc("eth_getBlockReceipts") do(blockTag: BlockTag) -> Opt[seq[ReceiptObject]]:
253+
let rxs = (await vp.getReceipts(blockTag)).valueOr:
254+
raise newException(ValueError, error)
255+
return Opt.some(rxs)
256+
257+
vp.proxy.rpc("eth_getTransactionReceipt") do(txHash: Hash32) -> ReceiptObject:
258+
let
259+
rx =
260+
try:
261+
await vp.rpcClient.eth_getTransactionReceipt(txHash)
262+
except CatchableError as e:
263+
raise newException(ValueError, e.msg)
264+
rxs = (await vp.getReceipts(rx.blockHash)).valueOr:
265+
raise newException(ValueError, error)
266+
267+
for r in rxs:
268+
if r.transactionHash == txHash:
269+
return r
270+
271+
raise newException(ValueError, "receipt couldn't be verified")
272+
273+
vp.proxy.rpc("eth_getLogs") do(filterOptions: FilterOptions) -> seq[LogObject]:
274+
(await vp.getLogs(filterOptions)).valueOr:
275+
raise newException(ValueError, error)
276+
232277
# TODO:
233278
# Following methods are forwarded directly to the web3 provider and therefore
234279
# are not validated in any way.
235280
vp.proxy.registerProxyMethod("net_version")
236281
vp.proxy.registerProxyMethod("eth_sendRawTransaction")
237-
vp.proxy.registerProxyMethod("eth_getTransactionReceipt")
238282

239283
# Used to be in eth1_monitor.nim; not sure why it was deleted,
240284
# so I copied it here. --Adam

nimbus_verified_proxy/rpc_api_backend.nim

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,35 @@ proc initNetworkApiBackend*(vp: VerifiedRpcProxy): EthApiBackend =
3737
): Future[seq[byte]] {.async: (raw: true).} =
3838
vp.proxy.getClient.eth_getCode(address, blockId)
3939

40+
getTransactionByHashProc = proc(
41+
txHash: Hash32
42+
): Future[TransactionObject] {.async: (raw: true).} =
43+
vp.proxy.getClient.eth_getTransactionByHash(txHash)
44+
45+
getTransactionReceiptProc = proc(
46+
txHash: Hash32
47+
): Future[ReceiptObject] {.async: (raw: true).} =
48+
vp.proxy.getClient.eth_getTransactionReceipt(txHash)
49+
50+
getBlockReceiptsProc = proc(
51+
blockId: BlockTag
52+
): Future[Opt[seq[ReceiptObject]]] {.async: (raw: true).} =
53+
vp.proxy.getClient.eth_getBlockReceipts(blockId)
54+
55+
getLogsProc = proc(
56+
filterOptions: FilterOptions
57+
): Future[seq[LogObject]] {.async: (raw: true).} =
58+
vp.proxy.getClient.eth_getLogs(filterOptions)
59+
4060
EthApiBackend(
4161
eth_chainId: ethChainIdProc,
4262
eth_getBlockByHash: getBlockByHashProc,
4363
eth_getBlockByNumber: getBlockByNumberProc,
4464
eth_getProof: getProofProc,
4565
eth_createAccessList: createAccessListProc,
4666
eth_getCode: getCodeProc,
67+
eth_getBlockReceipts: getBlockReceiptsProc,
68+
eth_getLogs: getLogsProc,
69+
eth_getTransactionByHash: getTransactionByHashProc,
70+
eth_getTransactionReceipt: getTransactionReceiptProc,
4771
)

nimbus_verified_proxy/tests/all_proxy_tests.nim

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,9 @@
55
# * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
66
# at your option. This file may not be copied, modified, or distributed except according to those terms.
77

8-
import ./test_proof_validation, ./test_header_store, ./test_transactions, ./test_blocks
8+
import
9+
./test_proof_validation,
10+
./test_header_store,
11+
./test_transactions,
12+
./test_blocks,
13+
./test_receipts
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
curl https://eth.llamarpc.com -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0", "method": "eth_getLogs", "params": [{"fromBlock": "0xed14f2", "toBlock": "0xed14f2", "topics":["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"]}], "id": 1}' | jq '.result' > logs.json

0 commit comments

Comments
 (0)