Skip to content

Commit f973b86

Browse files
authored
nrpc re-org handling (#2994)
* reorg handling * formatting fix * fix validation condition * eip-7685 compatible * more exception handling * remove strict checking
1 parent 184af02 commit f973b86

File tree

1 file changed

+72
-21
lines changed

1 file changed

+72
-21
lines changed

nrpc/nrpc.nim

Lines changed: 72 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Nimbus
2-
# Copyright (c) 2024 Status Research & Development GmbH
2+
# Copyright (c) 2024-2025 Status Research & Development GmbH
33
# Licensed under either of
44
# * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE))
55
# * MIT license ([LICENSE-MIT](LICENSE-MIT))
@@ -22,6 +22,7 @@ import
2222
beacon_chain/el/el_manager,
2323
beacon_chain/el/engine_api_conversions,
2424
beacon_chain/spec/[forks, state_transition_block],
25+
beacon_chain/spec/datatypes/bellatrix,
2526
beacon_chain/spec/eth2_apis/[rest_types, rest_beacon_calls],
2627
beacon_chain/networking/network_metadata,
2728
eth/async_utils
@@ -45,7 +46,6 @@ template getCLBlockFromBeaconChain(
4546
var blck: ForkedSignedBeaconBlock
4647
if clBlock.isSome():
4748
let blck = clBlock.get()[]
48-
4949
(blck, true)
5050
else:
5151
(blck, false)
@@ -148,7 +148,7 @@ proc syncToEngineApi(conf: NRpcConf) {.async.} =
148148
uint64(await rpcClient.eth_blockNumber())
149149
except CatchableError as exc:
150150
error "Error getting block number", error = exc.msg
151-
0'u64
151+
quit(QuitFailure)
152152

153153
# Load the EL state detials and create the beaconAPI client
154154
var
@@ -199,17 +199,23 @@ proc syncToEngineApi(conf: NRpcConf) {.async.} =
199199
elif consensusFork == ConsensusFork.Capella:
200200
Opt.none(PayloadAttributesV2)
201201
elif consensusFork == ConsensusFork.Deneb or
202-
consensusFork == ConsensusFork.Electra or
203-
consensusFork == ConsensusFork.Fulu:
202+
consensusFork == ConsensusFork.Electra or consensusFork == ConsensusFork.Fulu:
204203
Opt.none(PayloadAttributesV3)
205204
else:
206-
static: doAssert(false, "Unsupported consensus fork")
205+
static:
206+
doAssert(false, "Unsupported consensus fork")
207207
Opt.none(PayloadAttributesV3)
208208

209209
# Make the forkchoiceUpdated call based, after loading attributes based on the consensus fork
210210
let fcuResponse = await rpcClient.forkchoiceUpdated(state, payloadAttributes)
211211
debug "forkchoiceUpdated", state = state, response = fcuResponse
212-
info "forkchoiceUpdated Request sent", response = fcuResponse.payloadStatus.status
212+
if fcuResponse.payloadStatus.status == PayloadExecutionStatus.invalid or
213+
fcuResponse.payloadStatus.status == PayloadExecutionStatus.invalid_block_hash:
214+
error "Forkchoice not validated", status = fcuResponse.payloadStatus.status
215+
quit(QuitFailure)
216+
else:
217+
info "forkchoiceUpdated Request sent",
218+
response = fcuResponse.payloadStatus.status
213219

214220
while running and currentBlockNumber < headBlck.header.number:
215221
var isAvailable = false
@@ -249,19 +255,25 @@ proc syncToEngineApi(conf: NRpcConf) {.async.} =
249255
payload = payload,
250256
versionedHashes = versioned_hashes
251257
elif consensusFork == ConsensusFork.Electra or
252-
consensusFork == ConsensusFork.Fulu:
253-
# Calculate the versioned hashes from the kzg commitments
254-
let versioned_hashes = mapIt(
255-
forkyBlck.message.body.blob_kzg_commitments,
256-
engine_api.VersionedHash(kzg_commitment_to_versioned_hash(it)),
257-
)
258-
# Execution Requests for Electra
259-
let execution_requests = @[
260-
SSZ.encode(forkyBlck.message.body.execution_requests.deposits),
261-
SSZ.encode(forkyBlck.message.body.execution_requests.withdrawals),
262-
SSZ.encode(forkyBlck.message.body.execution_requests.consolidations),
263-
]
264-
# TODO: Update to `newPayload()` once nim-web3 is updated
258+
consensusFork == ConsensusFork.Fulu:
259+
let
260+
# Calculate the versioned hashes from the kzg commitments
261+
versioned_hashes = mapIt(
262+
forkyBlck.message.body.blob_kzg_commitments,
263+
engine_api.VersionedHash(kzg_commitment_to_versioned_hash(it)),
264+
)
265+
# Execution Requests for Electra
266+
execution_requests = block:
267+
var requests: seq[seq[byte]]
268+
for request_type, request_data in [
269+
SSZ.encode(forkyBlck.message.body.execution_requests.deposits),
270+
SSZ.encode(forkyBlck.message.body.execution_requests.withdrawals),
271+
SSZ.encode(forkyBlck.message.body.execution_requests.consolidations),
272+
]:
273+
if request_data.len > 0:
274+
requests.add @[request_type.byte] & request_data
275+
requests
276+
265277
payloadResponse = await rpcClient.engine_newPayloadV4(
266278
payload,
267279
versioned_hashes,
@@ -274,11 +286,18 @@ proc syncToEngineApi(conf: NRpcConf) {.async.} =
274286
versionedHashes = versioned_hashes,
275287
executionRequests = execution_requests
276288
else:
277-
static: doAssert(false, "Unsupported consensus fork")
289+
static:
290+
doAssert(false, "Unsupported consensus fork")
278291

279292
info "newPayload Request sent",
280293
blockNumber = int(payload.blockNumber), response = payloadResponse.status
281294

295+
if payloadResponse.status == PayloadExecutionStatus.invalid or
296+
payloadResponse.status == PayloadExecutionStatus.invalid_block_hash:
297+
error "Payload not validated",
298+
blockNumber = int(payload.blockNumber), status = payloadResponse.status
299+
quit(QuitFailure)
300+
282301
# Load the head hash from the execution payload, for forkchoice
283302
headHash = forkyBlck.message.body.execution_payload.block_hash
284303

@@ -303,9 +322,41 @@ proc syncToEngineApi(conf: NRpcConf) {.async.} =
303322
# Update the current block number from EL rest api
304323
# Shows that the fcu call has succeeded
305324
currentBlockNumber = elBlockNumber()
325+
let oldHeadBlockNumber = headBlck.header.number
306326
(headBlck, _) =
307327
client.getELBlockFromBeaconChain(BlockIdent.init(BlockIdentType.Head), clConfig)
308328

329+
# Check for reorg
330+
# No need to check for reorg if the EL head is behind the finalized block
331+
if currentBlockNumber > finalizedBlck.header.number and
332+
oldHeadBlockNumber > headBlck.header.number:
333+
warn "Head moved backwards : Possible reorg detected",
334+
oldHead = oldHeadBlockNumber, newHead = headBlck.header.number
335+
336+
let (headClBlck, isAvailable) =
337+
client.getCLBlockFromBeaconChain(BlockIdent.init(BlockIdentType.Head), clConfig)
338+
339+
# move back the importedSlot to the finalized block
340+
if isAvailable:
341+
withBlck(headClBlck.asTrusted()):
342+
when consensusFork >= ConsensusFork.Bellatrix:
343+
importedSlot = forkyBlck.message.slot.uint64 + 1
344+
currentBlockNumber = forkyBlck.message.body.execution_payload.block_number
345+
346+
# Load this head to the `headBlck`
347+
if not getEthBlock(forkyBlck.message, headBlck):
348+
error "Failed to get EL block from CL head"
349+
quit(QuitFailure)
350+
351+
(finalizedBlck, _) = client.getELBlockFromBeaconChain(
352+
BlockIdent.init(BlockIdentType.Finalized), clConfig
353+
)
354+
finalizedHash = finalizedBlck.header.blockHash.asEth2Digest
355+
sendFCU(headClBlck)
356+
else:
357+
error "Failed to get CL head"
358+
quit(QuitFailure)
359+
309360
# fcU call for the last remaining payloads
310361
sendFCU(curBlck)
311362

0 commit comments

Comments
 (0)