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))
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