Skip to content

Commit 8923504

Browse files
proxy: add blocks support (#3338)
* add blocks support * add rpc handlers * reviews * format * catch only error exceptions * remove unused imports * review * add basics tests * fix
1 parent 9a4fd90 commit 8923504

File tree

10 files changed

+4269
-80
lines changed

10 files changed

+4269
-80
lines changed

nimbus_verified_proxy/header_store.nim

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ func finalized*(self: HeaderStore): Opt[Header] =
116116
func finalizedHash*(self: HeaderStore): Opt[Hash32] =
117117
self.finalizedHash
118118

119+
func contains*(self: HeaderStore, hash: Hash32): bool =
120+
self.headers.contains(hash)
121+
122+
func contains*(self: HeaderStore, number: base.BlockNumber): bool =
123+
self.hashes.contains(number)
124+
119125
proc updateFinalized*(
120126
self: HeaderStore, header: ForkedLightClientHeader
121127
): Result[bool, string] =

nimbus_verified_proxy/nimbus_verified_proxy.nim

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ proc run*(
8484
# header cache contains headers downloaded from p2p
8585
headerStore = HeaderStore.new(config.cacheLen)
8686

87-
let verifiedProxy = VerifiedRpcProxy.init(rpcProxy, headerStore, chainId)
87+
# TODO: add config object to verified proxy for future config options
88+
let verifiedProxy =
89+
VerifiedRpcProxy.init(rpcProxy, headerStore, chainId, config.maxBlockWalk)
8890

8991
# add handlers that verify RPC calls /rpc/rpc_eth_api.nim
9092
verifiedProxy.installEthApiHandlers()

nimbus_verified_proxy/nimbus_verified_proxy_conf.nim

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ type VerifiedProxyConf* = object # Config
141141
name: "max-peers"
142142
.}: int
143143

144+
maxBlockWalk* {.
145+
hidden,
146+
desc: "Maximum number of blocks that will be queried to serve a request",
147+
defaultValue: 1000,
148+
defaultValueDesc: "1000",
149+
name: "debug-max-walk"
150+
.}: uint64
151+
144152
hardMaxPeers* {.
145153
desc: "The maximum number of peers to connect to. Defaults to maxPeers * 1.5",
146154
name: "hard-max-peers"

nimbus_verified_proxy/rpc/blocks.nim

Lines changed: 232 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -9,59 +9,244 @@
99

1010
import
1111
std/strutils,
12-
stint,
13-
chronos,
1412
results,
13+
chronicles,
14+
web3/[eth_api_types, eth_api],
15+
json_rpc/[rpcproxy, rpcserver, rpcclient],
1516
eth/common/eth_types_rlp,
16-
web3/eth_api_types,
17+
eth/rlp,
18+
eth/trie/[ordered_trie, trie_defs],
19+
../../execution_chain/beacon/web3_eth_conv,
20+
../types,
1721
../header_store,
18-
../types
22+
./transactions
1923

20-
type
21-
QuantityTagKind = enum
22-
LatestBlock
23-
BlockNumber
24-
25-
QuantityTag = object
26-
case kind: QuantityTagKind
27-
of LatestBlock:
28-
discard
29-
of BlockNumber:
30-
blockNumber: Quantity
31-
32-
func parseQuantityTag(blockTag: BlockTag): Result[QuantityTag, string] =
24+
proc resolveBlockTag*(
25+
vp: VerifiedRpcProxy, blockTag: BlockTag
26+
): Result[base.BlockNumber, string] =
3327
if blockTag.kind == bidAlias:
34-
let tag = blockTag.alias.toLowerAscii
28+
let tag = blockTag.alias.toLowerAscii()
3529
case tag
3630
of "latest":
37-
return ok(QuantityTag(kind: LatestBlock))
31+
let hLatest = vp.headerStore.latest.valueOr:
32+
return err("Couldn't get the latest block number from header store")
33+
ok(hLatest.number)
3834
else:
39-
return err("Unsupported blockTag: " & tag)
35+
err("No support for block tag " & $blockTag)
4036
else:
41-
let quantity = blockTag.number
42-
return ok(QuantityTag(kind: BlockNumber, blockNumber: quantity))
43-
44-
template checkPreconditions(proxy: VerifiedRpcProxy) =
45-
if proxy.headerStore.isEmpty():
46-
raise newException(ValueError, "Syncing")
47-
48-
proc getHeaderByTag(
49-
proxy: VerifiedRpcProxy, quantityTag: BlockTag
50-
): results.Opt[Header] {.raises: [ValueError].} =
51-
checkPreconditions(proxy)
52-
53-
let tag = parseQuantityTag(quantityTag).valueOr:
54-
raise newException(ValueError, error)
55-
56-
case tag.kind
57-
of LatestBlock:
58-
# this will always return some block, as we always checkPreconditions
59-
proxy.headerStore.latest
60-
of BlockNumber:
61-
proxy.headerStore.get(base.BlockNumber(distinctBase(tag.blockNumber)))
62-
63-
proc getHeaderByTagOrThrow*(
64-
proxy: VerifiedRpcProxy, quantityTag: BlockTag
65-
): Header {.raises: [ValueError].} =
66-
getHeaderByTag(proxy, quantityTag).valueOr:
67-
raise newException(ValueError, "No block stored for given tag " & $quantityTag)
37+
ok(base.BlockNumber(distinctBase(blockTag.number)))
38+
39+
func convHeader(blk: eth_api_types.BlockObject): Header =
40+
let nonce = blk.nonce.valueOr:
41+
default(Bytes8)
42+
43+
return Header(
44+
parentHash: blk.parentHash,
45+
ommersHash: blk.sha3Uncles,
46+
coinbase: blk.miner,
47+
stateRoot: blk.stateRoot,
48+
transactionsRoot: blk.transactionsRoot,
49+
receiptsRoot: blk.receiptsRoot,
50+
logsBloom: blk.logsBloom,
51+
difficulty: blk.difficulty,
52+
number: base.BlockNumber(distinctBase(blk.number)),
53+
gasLimit: GasInt(blk.gasLimit.uint64),
54+
gasUsed: GasInt(blk.gasUsed.uint64),
55+
timestamp: ethTime(blk.timestamp),
56+
extraData: seq[byte](blk.extraData),
57+
mixHash: Bytes32(distinctBase(blk.mixHash)),
58+
nonce: nonce,
59+
baseFeePerGas: blk.baseFeePerGas,
60+
withdrawalsRoot: blk.withdrawalsRoot,
61+
blobGasUsed: blk.blobGasUsed.u64,
62+
excessBlobGas: blk.excessBlobGas.u64,
63+
parentBeaconBlockRoot: blk.parentBeaconBlockRoot,
64+
requestsHash: blk.requestsHash,
65+
)
66+
67+
proc walkBlocks(
68+
vp: VerifiedRpcProxy,
69+
sourceNum: base.BlockNumber,
70+
targetNum: base.BlockNumber,
71+
sourceHash: Hash32,
72+
targetHash: Hash32,
73+
): Future[Result[void, string]] {.async: (raises: []).} =
74+
var nextHash = sourceHash
75+
info "Starting block walk to verify requested block", blockHash = targetHash
76+
77+
let numBlocks = sourceNum - targetNum
78+
if numBlocks > vp.maxBlockWalk:
79+
return err(
80+
"Cannot query more than " & $vp.maxBlockWalk &
81+
" to verify the chain for the requested block"
82+
)
83+
84+
for i in 0 ..< numBlocks:
85+
let nextHeader =
86+
if vp.headerStore.contains(nextHash):
87+
vp.headerStore.get(nextHash).get()
88+
else:
89+
let blk =
90+
try:
91+
await vp.rpcClient.eth_getBlockByHash(nextHash, false)
92+
except CatchableError as e:
93+
return err(
94+
"Couldn't get block " & $nextHash & " during the chain traversal: " & e.msg
95+
)
96+
97+
trace "getting next block",
98+
hash = nextHash,
99+
number = blk.number,
100+
remaining = distinctBase(blk.number) - targetNum
101+
102+
let header = convHeader(blk)
103+
104+
if header.computeBlockHash != nextHash:
105+
return err("Encountered an invalid block header while walking the chain")
106+
107+
header
108+
109+
if nextHeader.parentHash == targetHash:
110+
return ok()
111+
112+
nextHash = nextHeader.parentHash
113+
114+
err("the requested block is not part of the canonical chain")
115+
116+
proc verifyHeader(
117+
vp: VerifiedRpcProxy, header: Header, hash: Hash32
118+
): Future[Result[void, string]] {.async.} =
119+
# verify calculated hash with the requested hash
120+
if header.computeBlockHash != hash:
121+
return err("hashed block header doesn't match with blk.hash(downloaded)")
122+
123+
if not vp.headerStore.contains(hash):
124+
let latestHeader = vp.headerStore.latest.valueOr:
125+
return err("Couldn't get the latest header, syncing in progress")
126+
127+
# walk blocks backwards(time) from source to target
128+
?(
129+
await vp.walkBlocks(
130+
latestHeader.number, header.number, latestHeader.parentHash, hash
131+
)
132+
)
133+
134+
ok()
135+
136+
proc verifyBlock(
137+
vp: VerifiedRpcProxy, blk: BlockObject, fullTransactions: bool
138+
): Future[Result[void, string]] {.async.} =
139+
let header = convHeader(blk)
140+
141+
?(await vp.verifyHeader(header, blk.hash))
142+
143+
# verify transactions
144+
if fullTransactions:
145+
?verifyTransactions(header.transactionsRoot, blk.transactions)
146+
147+
# verify withdrawals
148+
if blk.withdrawalsRoot.isSome():
149+
if blk.withdrawalsRoot.get() != orderedTrieRoot(blk.withdrawals.get(@[])):
150+
return err("Withdrawals within the block do not yield the same withdrawals root")
151+
else:
152+
if blk.withdrawals.isSome():
153+
return err("Block contains withdrawals but no withdrawalsRoot")
154+
155+
ok()
156+
157+
proc getBlock*(
158+
vp: VerifiedRpcProxy, blockHash: Hash32, fullTransactions: bool
159+
): Future[Result[BlockObject, string]] {.async.} =
160+
# get the target block
161+
let blk =
162+
try:
163+
await vp.rpcClient.eth_getBlockByHash(blockHash, fullTransactions)
164+
except CatchableError as e:
165+
return err(e.msg)
166+
167+
# verify requested hash with the downloaded hash
168+
if blockHash != blk.hash:
169+
return err("the downloaded block hash doesn't match with the requested hash")
170+
171+
# verify the block
172+
?(await vp.verifyBlock(blk, fullTransactions))
173+
174+
ok(blk)
175+
176+
proc getBlock*(
177+
vp: VerifiedRpcProxy, blockTag: BlockTag, fullTransactions: bool
178+
): Future[Result[BlockObject, string]] {.async.} =
179+
let n = vp.resolveBlockTag(blockTag).valueOr:
180+
return err(error)
181+
182+
# get the target block
183+
let blk =
184+
try:
185+
await vp.rpcClient.eth_getBlockByNumber(blockTag, fullTransactions)
186+
except CatchableError as e:
187+
return err(e.msg)
188+
189+
if n != distinctBase(blk.number):
190+
return
191+
err("the downloaded block number doesn't match with the requested block number")
192+
193+
# verify the block
194+
?(await vp.verifyBlock(blk, fullTransactions))
195+
196+
ok(blk)
197+
198+
proc getHeader*(
199+
vp: VerifiedRpcProxy, blockHash: Hash32
200+
): Future[Result[Header, string]] {.async.} =
201+
let cachedHeader = vp.headerStore.get(blockHash)
202+
203+
if cachedHeader.isNone():
204+
debug "did not find the header in the cache", blockHash = blockHash
205+
else:
206+
return ok(cachedHeader.get())
207+
208+
# get the target block
209+
let blk =
210+
try:
211+
await vp.rpcClient.eth_getBlockByHash(blockHash, false)
212+
except CatchableError as e:
213+
return err(e.msg)
214+
215+
let header = convHeader(blk)
216+
217+
if blockHash != blk.hash:
218+
return err("the blk.hash(downloaded) doesn't match with the provided hash")
219+
220+
?(await vp.verifyHeader(header, blockHash))
221+
222+
ok(header)
223+
224+
proc getHeader*(
225+
vp: VerifiedRpcProxy, blockTag: BlockTag
226+
): Future[Result[Header, string]] {.async.} =
227+
let
228+
n = vp.resolveBlockTag(blockTag).valueOr:
229+
return err(error)
230+
cachedHeader = vp.headerStore.get(n)
231+
232+
if cachedHeader.isNone():
233+
debug "did not find the header in the cache", blockTag = blockTag
234+
else:
235+
return ok(cachedHeader.get())
236+
237+
# get the target block
238+
let blk =
239+
try:
240+
await vp.rpcClient.eth_getBlockByNumber(blockTag, false)
241+
except CatchableError as e:
242+
return err(e.msg)
243+
244+
let header = convHeader(blk)
245+
246+
if n != header.number:
247+
return
248+
err("the downloaded block number doesn't match with the requested block number")
249+
250+
?(await vp.verifyHeader(header, blk.hash))
251+
252+
ok(header)

0 commit comments

Comments
 (0)