Skip to content

Commit 9697b6b

Browse files
committed
transactions support
1 parent d9975e6 commit 9697b6b

File tree

3 files changed

+279
-15
lines changed

3 files changed

+279
-15
lines changed

nimbus_verified_proxy/rpc/blocks.nim

Lines changed: 81 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,11 @@ import
1515
json_rpc/[rpcproxy, rpcserver, rpcclient],
1616
eth/common/addresses,
1717
eth/common/eth_types_rlp,
18+
eth/trie/[hexary, ordered_trie, db, trie_defs],
1819
../../execution_chain/beacon/web3_eth_conv,
1920
../types,
20-
../header_store
21+
../header_store,
22+
./transactions
2123

2224
type BlockTag* = eth_api_types.RtBlockIdentifier
2325

@@ -93,6 +95,83 @@ proc walkBlocks(
9395

9496
return false
9597

98+
proc getBlockByHash*(
99+
self: VerifiedRpcProxy, blockHash: Hash32, fullTransactions: bool
100+
): Future[BlockObject] {.async: (raises: [ValueError, CatchableError]).} =
101+
# get the target block
102+
let blk = await self.rpcClient.eth_getBlockByHash(blockHash, fullTransactions)
103+
let header = convHeader(blk)
104+
105+
# verify header hash
106+
if header.rlpHash != blockHash:
107+
raise newException(ValueError, "hashed block header doesn't match with blk.hash(downloaded)")
108+
109+
if blockHash != blk.hash:
110+
raise newException(ValueError, "the downloaded block hash doesn't match with the requested hash")
111+
112+
let earliestHeader = self.headerStore.earliest.valueOr:
113+
raise newException(ValueError, "Syncing")
114+
115+
# walk blocks backwards(time) from source to target
116+
let isLinked = await self.walkBlocks(earliestHeader.number, header.number, earliestHeader.parentHash, blockHash)
117+
118+
if not isLinked:
119+
raise newException(ValueError, "the requested block is not part of the canonical chain")
120+
121+
# verify transactions
122+
if fullTransactions:
123+
let verified = verifyTransactions(header.transactionsRoot, blk.transactions).valueOr:
124+
raise newException(ValueError, "error while verifying transactions root")
125+
if not verified:
126+
raise newException(ValueError, "transactions within the block do not yield the same transaction root")
127+
128+
# verify withdrawals
129+
if blk.withdrawals.isSome():
130+
if blk.withdrawalsRoot.get() != orderedTrieRoot(blk.withdrawals.get()):
131+
raise newException(ValueError, "withdrawals within the block do not yield the same withdrawals root")
132+
133+
return blk
134+
135+
proc getBlockByTag*(
136+
self: VerifiedRpcProxy, blockTag: BlockTag, fullTransactions: bool
137+
): Future[BlockObject] {.async: (raises: [ValueError, CatchableError]).} =
138+
let n = self.resolveTag(blockTag)
139+
140+
# get the target block
141+
let blk = await self.rpcClient.eth_getBlockByNumber(blockTag, false)
142+
let header = convHeader(blk)
143+
144+
# verify header hash
145+
if header.rlpHash != blk.hash:
146+
raise newException(ValueError, "hashed block header doesn't match with blk.hash(downloaded)")
147+
148+
if n != header.number:
149+
raise newException(ValueError, "the downloaded block number doesn't match with the requested block number")
150+
151+
# get the source block
152+
let earliestHeader = self.headerStore.earliest.valueOr:
153+
raise newException(ValueError, "Syncing")
154+
155+
# walk blocks backwards(time) from source to target
156+
let isLinked = await self.walkBlocks(earliestHeader.number, header.number, earliestHeader.parentHash, blk.hash)
157+
158+
if not isLinked:
159+
raise newException(ValueError, "the requested block is not part of the canonical chain")
160+
161+
# verify transactions
162+
if fullTransactions:
163+
let verified = verifyTransactions(header.transactionsRoot, blk.transactions).valueOr:
164+
raise newException(ValueError, "error while verifying transactions root")
165+
if not verified:
166+
raise newException(ValueError, "transactions within the block do not yield the same transaction root")
167+
168+
# verify withdrawals
169+
if blk.withdrawals.isSome():
170+
if blk.withdrawalsRoot.get() != orderedTrieRoot(blk.withdrawals.get()):
171+
raise newException(ValueError, "withdrawals within the block do not yield the same withdrawals root")
172+
173+
return blk
174+
96175
proc getHeaderByHash*(
97176
self: VerifiedRpcProxy, blockHash: Hash32
98177
): Future[Header] {.async: (raises: [ValueError, CatchableError]).} =
@@ -129,7 +208,7 @@ proc getHeaderByHash*(
129208
proc getHeaderByTag*(
130209
self: VerifiedRpcProxy, blockTag: BlockTag
131210
): Future[Header] {.async: (raises: [ValueError, CatchableError]).} =
132-
let
211+
let
133212
n = self.resolveTag(blockTag)
134213
cachedHeader = self.headerStore.get(n)
135214

nimbus_verified_proxy/rpc/rpc_eth_api.nim

Lines changed: 80 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ import
2424
../header_store,
2525
../types,
2626
./blocks,
27-
./accounts
27+
./accounts,
28+
./transactions
2829

2930
logScope:
3031
topics = "verified_proxy"
@@ -36,6 +37,84 @@ proc installEthApiHandlers*(vp: VerifiedRpcProxy) =
3637
vp.proxy.rpc("eth_chainId") do() -> Quantity:
3738
vp.chainId
3839

40+
vp.proxy.rpc("eth_getBlockByNumber") do(
41+
blockTag: BlockTag, fullTransactions: bool
42+
) -> Opt[BlockObject]:
43+
try:
44+
let blk = await vp.getBlockByTag(blockTag, fullTransactions)
45+
return Opt.some(blk)
46+
except ValueError as e:
47+
# should return Opt.none but we also want to transmit error related info
48+
# return Opt.none(BlockObject)
49+
raise newException(ValueError, e.msg) # raising an exception will return the error message
50+
51+
vp.proxy.rpc("eth_getBlockByHash") do(
52+
blockHash: Hash32, fullTransactions: bool
53+
) -> Opt[BlockObject]:
54+
try:
55+
let blk = await vp.getBlockByHash(blockHash, fullTransactions)
56+
return Opt.some(blk)
57+
except ValueError as e:
58+
# should return Opt.none but we also want to transmit error related info
59+
# return Opt.none(BlockObject)
60+
raise newException(ValueError, e.msg) # raising an exception will return the error message
61+
62+
vp.proxy.rpc("eth_getUncleCountByBlockNumber") do(
63+
blockTag: BlockTag
64+
) -> Quantity:
65+
let blk = await vp.getBlockByTag(blockTag, false)
66+
return Quantity(blk.uncles.len())
67+
68+
vp.proxy.rpc("eth_getUncleCountByBlockHash") do(
69+
blockHash: Hash32
70+
) -> Quantity:
71+
let blk = await vp.getBlockByHash(blockHash, false)
72+
return Quantity(blk.uncles.len())
73+
74+
vp.proxy.rpc("eth_getBlockTransactionCountByNumber") do(
75+
blockTag: BlockTag
76+
) -> Quantity:
77+
let blk = await vp.getBlockByTag(blockTag, true)
78+
return Quantity(blk.transactions.len)
79+
80+
vp.proxy.rpc("eth_getBlockTransactionCountByHash") do(
81+
blockHash: Hash32
82+
) -> Quantity:
83+
let blk = await vp.getBlockByHash(blockHash, true)
84+
return Quantity(blk.transactions.len)
85+
86+
vp.proxy.rpc("eth_getTransactionByBlockNumberAndIndex") do(
87+
blockTag: BlockTag, index: Quantity
88+
) -> TransactionObject:
89+
let blk = await vp.getBlockByTag(blockTag, true)
90+
if distinctBase(index) >= uint64(blk.transactions.len):
91+
raise newException(ValueError, "provided transaction index is outside bounds")
92+
let x = blk.transactions[distinctBase(index)]
93+
doAssert x.kind == tohTx
94+
return x.tx
95+
96+
vp.proxy.rpc("eth_getTransactionByBlockHashAndIndex") do(
97+
blockHash: Hash32, index: Quantity
98+
) -> TransactionObject:
99+
let blk = await vp.getBlockByHash(blockHash, true)
100+
if distinctBase(index) >= uint64(blk.transactions.len):
101+
raise newException(ValueError, "provided transaction index is outside bounds")
102+
let x = blk.transactions[distinctBase(index)]
103+
doAssert x.kind == tohTx
104+
return x.tx
105+
106+
vp.proxy.rpc("eth_getTransactionByHash") do(
107+
txHash: Hash32
108+
) -> TransactionObject:
109+
let tx = await vp.rpcClient.eth_getTransactionByHash(txHash)
110+
if tx.hash != txHash:
111+
raise newException(ValueError, "the downloaded transaction hash doesn't match the requested transaction hash")
112+
113+
if not checkTxHash(tx, txHash):
114+
raise newException(ValueError, "the transaction doesn't hash to the provided hash")
115+
116+
return tx
117+
39118
# eth_blockNumber - get latest tag from header store
40119
vp.proxy.rpc("eth_blockNumber") do() -> Quantity:
41120
# Returns the number of the most recent block seen by the light client.
@@ -135,18 +214,6 @@ proc installEthApiHandlers*(vp: VerifiedRpcProxy) =
135214
vp.proxy.registerProxyMethod("eth_sendRawTransaction")
136215
vp.proxy.registerProxyMethod("eth_getTransactionReceipt")
137216

138-
# TODO currently we do not handle fullTransactions flag. It require updates on
139-
# nim-web3 side
140-
# vp.proxy.rpc("eth_getBlockByNumber") do(
141-
# blockTag: BlockTag, fullTransactions: bool
142-
# ) -> Opt[BlockObject]:
143-
# vp.getBlockByTag(blockTag)
144-
#
145-
# vp.proxy.rpc("eth_getBlockByHash") do(
146-
# blockHash: Hash32, fullTransactions: bool
147-
# ) -> Opt[BlockObject]:
148-
# vp.blockCache.getPayloadByHash(blockHash)
149-
150217
# Used to be in eth1_monitor.nim; not sure why it was deleted,
151218
# so I copied it here. --Adam
152219
template awaitWithRetries*[T](
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# nimbus_verified_proxy
2+
# Copyright (c) 2022-2024 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 raises: [].}
9+
10+
import
11+
std/sequtils,
12+
stint,
13+
results,
14+
chronicles,
15+
eth/common/[base_rlp, transactions_rlp, receipts_rlp, hashes_rlp],
16+
../../execution_chain/beacon/web3_eth_conv,
17+
eth/common/addresses,
18+
eth/common/eth_types_rlp,
19+
eth/trie/[hexary, ordered_trie, db, trie_defs],
20+
json_rpc/[rpcproxy, rpcserver, rpcclient],
21+
web3/[primitives, eth_api_types, eth_api],
22+
../types,
23+
../header_store
24+
25+
export results, stint, hashes_rlp, accounts_rlp, eth_api_types
26+
27+
template rpcClient(vp: VerifiedRpcProxy): RpcClient =
28+
vp.proxy.getClient()
29+
30+
template calcWithdrawalsRoot*(withdrawals: openArray[Withdrawal]): Root =
31+
orderedTrieRoot(withdrawals)
32+
33+
func vHashes(x: Opt[seq[Hash32]]): seq[VersionedHash] =
34+
if x.isNone: return
35+
else: x.get
36+
37+
func authList(x: Opt[seq[Authorization]]): seq[Authorization] =
38+
if x.isNone: return
39+
else: x.get
40+
41+
proc toTransaction(tx: TransactionObject): Transaction =
42+
Transaction(
43+
txType : tx.`type`.get(0.Web3Quantity).TxType,
44+
chainId : tx.chainId.get(0.Web3Quantity).ChainId,
45+
nonce : tx.nonce.AccountNonce,
46+
gasPrice : tx.gasPrice.GasInt,
47+
maxPriorityFeePerGas: tx.maxPriorityFeePerGas.get(0.Web3Quantity).GasInt,
48+
maxFeePerGas : tx.maxFeePerGas.get(0.Web3Quantity).GasInt,
49+
gasLimit : tx.gas.GasInt,
50+
to : tx.to,
51+
value : tx.value,
52+
payload : tx.input,
53+
accessList : tx.accessList.get(@[]),
54+
maxFeePerBlobGas: tx.maxFeePerBlobGas.get(0.u256),
55+
versionedHashes : vHashes(tx.blobVersionedHashes),
56+
V : tx.v.uint64,
57+
R : tx.r,
58+
S : tx.s,
59+
authorizationList: authList(tx.authorizationList),
60+
)
61+
62+
proc toTransactions(txs: openArray[TxOrHash]): seq[Transaction] {.raises: [ValueError].} =
63+
for x in txs:
64+
if x.kind == tohTx:
65+
result.add toTransaction(x.tx)
66+
else:
67+
raise newException(ValueError, "cannot construct a transaction trie using only txhashes")
68+
69+
proc checkTxHash*(txObj: TransactionObject, txHash: Hash32): bool =
70+
let tx = toTransaction(txObj)
71+
if tx.rlpHash != txHash:
72+
return false
73+
74+
return true
75+
76+
template toLog(lg: LogObject): Log =
77+
Log(
78+
address: lg.address,
79+
topics: lg.topics,
80+
data: lg.data
81+
)
82+
83+
proc toLogs(logs: openArray[LogObject]): seq[Log] =
84+
result = map(logs, proc(x: LogObject): Log = toLog(x))
85+
86+
proc toReceipt(rec: ReceiptObject): Receipt =
87+
let isHash = if rec.status.isSome: false
88+
else: true
89+
90+
var status = false
91+
if rec.status.isSome:
92+
if rec.status.get() == 1.Quantity:
93+
status = true
94+
95+
return Receipt(
96+
hash: rec.transactionHash,
97+
isHash: isHash,
98+
status: status,
99+
cumulativeGasUsed: rec.cumulativeGasUsed.GasInt,
100+
logs: toLogs(rec.logs),
101+
logsBloom: rec.logsBloom,
102+
receiptType: rec.`type`.get(0.Web3Quantity).ReceiptType
103+
)
104+
105+
proc verifyTransactions*(
106+
txRoot: Hash32,
107+
transactions: seq[TxOrHash],
108+
): Result[bool, string] =
109+
110+
try:
111+
let txs = toTransactions(transactions)
112+
let rootHash = orderedTrieRoot(txs)
113+
if rootHash == txRoot:
114+
return ok(true)
115+
except ValueError as e:
116+
return err(e.msg)
117+
118+
ok(false)

0 commit comments

Comments
 (0)