always use fcUV2 in shapella even for non-proposer fcUs (#4817)

* always use fcUV2 in shapella even for non-proposer fcUs

* avoid template/proc naming conflict with libp2p/signed_envelope.nim having a payload proc
This commit is contained in:
tersec 2023-04-17 14:17:52 +00:00 committed by GitHub
parent addb7bda2f
commit 75be7d267d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 136 additions and 67 deletions

View File

@ -47,11 +47,11 @@ proc initLightClient*(
withBlck(signedBlock): withBlck(signedBlock):
when consensusFork >= ConsensusFork.Bellatrix: when consensusFork >= ConsensusFork.Bellatrix:
if blck.message.is_execution_block: if blck.message.is_execution_block:
template payload(): auto = blck.message.body.execution_payload template blckPayload(): auto = blck.message.body.execution_payload
if not payload.block_hash.isZero: if not blckPayload.block_hash.isZero:
# engine_newPayloadV1 # engine_newPayloadV1
discard await node.elManager.newExecutionPayload(payload) discard await node.elManager.newExecutionPayload(blckPayload)
# Retain optimistic head for other `forkchoiceUpdated` callers. # Retain optimistic head for other `forkchoiceUpdated` callers.
# May temporarily block `forkchoiceUpdatedV1` calls, e.g., Geth: # May temporarily block `forkchoiceUpdatedV1` calls, e.g., Geth:
@ -60,15 +60,32 @@ proc initLightClient*(
# Once DAG sync catches up or as new optimistic heads are fetched # Once DAG sync catches up or as new optimistic heads are fetched
# the situation recovers # the situation recovers
node.consensusManager[].setOptimisticHead( node.consensusManager[].setOptimisticHead(
blck.toBlockId(), payload.block_hash) blck.toBlockId(), blckPayload.block_hash)
# engine_forkchoiceUpdatedV1 # engine_forkchoiceUpdatedV1 or engine_forkchoiceUpdatedV2,
# depending on pre or post-Shapella
let beaconHead = node.attestationPool[].getBeaconHead(nil) let beaconHead = node.attestationPool[].getBeaconHead(nil)
discard await node.elManager.forkchoiceUpdated(
headBlockHash = payload.block_hash, template callForkchoiceUpdated(attributes: untyped) =
safeBlockHash = beaconHead.safeExecutionPayloadHash, discard await node.elManager.forkchoiceUpdated(
finalizedBlockHash = beaconHead.finalizedExecutionPayloadHash, headBlockHash = blckPayload.block_hash,
payloadAttributes = NoPayloadAttributes) safeBlockHash = beaconHead.safeExecutionPayloadHash,
finalizedBlockHash = beaconHead.finalizedExecutionPayloadHash,
payloadAttributes = none attributes)
case node.dag.cfg.consensusForkAtEpoch(beaconHead.blck.bid.slot.epoch)
of ConsensusFork.Capella, ConsensusFork.Deneb:
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/shanghai.md#specification-1
# Consensus layer client MUST call this method instead of
# `engine_forkchoiceUpdatedV1` under any of the following
# conditions:
# `headBlockHash` references a block which `timestamp` is
# greater or equal to the Shanghai timestamp
callForkchoiceUpdated(PayloadAttributesV2)
of ConsensusFork.Bellatrix:
callForkchoiceUpdated(PayloadAttributesV1)
of ConsensusFork.Phase0, ConsensusFork.Altair:
discard
else: discard else: discard
optimisticProcessor = initOptimisticProcessor( optimisticProcessor = initOptimisticProcessor(

View File

@ -164,13 +164,25 @@ proc updateExecutionClientHead(self: ref ConsensusManager,
self.dag.markBlockVerified(self.quarantine[], newHead.blck.root) self.dag.markBlockVerified(self.quarantine[], newHead.blck.root)
return Opt[void].ok() return Opt[void].ok()
# Can't use dag.head here because it hasn't been updated yet template callForkchoiceUpdated(attributes: untyped): auto =
let (payloadExecutionStatus, latestValidHash) =
await self.elManager.forkchoiceUpdated( await self.elManager.forkchoiceUpdated(
headBlockHash = headExecutionPayloadHash, headBlockHash = headExecutionPayloadHash,
safeBlockHash = newHead.safeExecutionPayloadHash, safeBlockHash = newHead.safeExecutionPayloadHash,
finalizedBlockHash = newHead.finalizedExecutionPayloadHash, finalizedBlockHash = newHead.finalizedExecutionPayloadHash,
payloadAttributes = NoPayloadAttributes) payloadAttributes = none attributes)
# Can't use dag.head here because it hasn't been updated yet
let (payloadExecutionStatus, latestValidHash) =
case self.dag.cfg.consensusForkAtEpoch(newHead.blck.bid.slot.epoch)
of ConsensusFork.Capella, ConsensusFork.Deneb:
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/shanghai.md#specification-1
# Consensus layer client MUST call this method instead of
# `engine_forkchoiceUpdatedV1` under any of the following conditions:
# `headBlockHash` references a block which `timestamp` is greater or
# equal to the Shanghai timestamp
callForkchoiceUpdated(PayloadAttributesV2)
of ConsensusFork.Phase0, ConsensusFork.Altair, ConsensusFork.Bellatrix:
callForkchoiceUpdated(PayloadAttributesV1)
case payloadExecutionStatus case payloadExecutionStatus
of PayloadExecutionStatus.valid: of PayloadExecutionStatus.valid:
@ -360,7 +372,7 @@ proc runProposalForkchoiceUpdated*(
let (status, _) = await self.elManager.forkchoiceUpdated( let (status, _) = await self.elManager.forkchoiceUpdated(
headBlockHash, safeBlockHash, headBlockHash, safeBlockHash,
beaconHead.finalizedExecutionPayloadHash, beaconHead.finalizedExecutionPayloadHash,
payloadAttributes = fcPayloadAttributes) payloadAttributes = some fcPayloadAttributes)
debug "Fork-choice updated for proposal", status debug "Fork-choice updated for proposal", status
static: doAssert high(ConsensusFork) == ConsensusFork.Deneb static: doAssert high(ConsensusFork) == ConsensusFork.Deneb

View File

@ -133,10 +133,6 @@ type
hasConsensusViolation: bool hasConsensusViolation: bool
## The local chain contradicts the observed consensus on the network ## The local chain contradicts the observed consensus on the network
NoPayloadAttributesType = object
## A type with exactly one value, and which is not constructed via a `nil`
## value for a ref object, which which Nim 1.6 crashes with an ICE.
NextExpectedPayloadParams* = object NextExpectedPayloadParams* = object
headBlockHash*: Eth2Digest headBlockHash*: Eth2Digest
safeBlockHash*: Eth2Digest safeBlockHash*: Eth2Digest
@ -255,9 +251,6 @@ type
GetPayloadV2Response | GetPayloadV2Response |
CancunExecutionPayloadAndBlobs CancunExecutionPayloadAndBlobs
const
NoPayloadAttributes* = default(NoPayloadAttributesType)
declareCounter failed_web3_requests, declareCounter failed_web3_requests,
"Failed web3 requests" "Failed web3 requests"
@ -764,16 +757,13 @@ func areSameAs(expectedParams: Option[NextExpectedPayloadParams],
proc forkchoiceUpdated(rpcClient: RpcClient, proc forkchoiceUpdated(rpcClient: RpcClient,
state: ForkchoiceStateV1, state: ForkchoiceStateV1,
payloadAttributes: PayloadAttributesV1 | payloadAttributes: Option[PayloadAttributesV1] |
PayloadAttributesV2 | Option[PayloadAttributesV2]):
NoPayloadAttributesType):
Future[ForkchoiceUpdatedResponse] = Future[ForkchoiceUpdatedResponse] =
when payloadAttributes is NoPayloadAttributesType: when payloadAttributes is Option[PayloadAttributesV1]:
rpcClient.engine_forkchoiceUpdatedV1(state, none PayloadAttributesV1) rpcClient.engine_forkchoiceUpdatedV1(state, payloadAttributes)
elif payloadAttributes is PayloadAttributesV1: elif payloadAttributes is Option[PayloadAttributesV2]:
rpcClient.engine_forkchoiceUpdatedV1(state, some payloadAttributes) rpcClient.engine_forkchoiceUpdatedV2(state, payloadAttributes)
elif payloadAttributes is PayloadAttributesV2:
rpcClient.engine_forkchoiceUpdatedV2(state, some payloadAttributes)
else: else:
static: doAssert false static: doAssert false
@ -806,7 +796,7 @@ proc getPayloadFromSingleEL(
headBlockHash: headBlock.asBlockHash, headBlockHash: headBlock.asBlockHash,
safeBlockHash: safeBlock.asBlockHash, safeBlockHash: safeBlock.asBlockHash,
finalizedBlockHash: finalizedBlock.asBlockHash), finalizedBlockHash: finalizedBlock.asBlockHash),
PayloadAttributesV1( some PayloadAttributesV1(
timestamp: Quantity timestamp, timestamp: Quantity timestamp,
prevRandao: FixedBytes[32] randomData.data, prevRandao: FixedBytes[32] randomData.data,
suggestedFeeRecipient: suggestedFeeRecipient)) suggestedFeeRecipient: suggestedFeeRecipient))
@ -816,7 +806,7 @@ proc getPayloadFromSingleEL(
headBlockHash: headBlock.asBlockHash, headBlockHash: headBlock.asBlockHash,
safeBlockHash: safeBlock.asBlockHash, safeBlockHash: safeBlock.asBlockHash,
finalizedBlockHash: finalizedBlock.asBlockHash), finalizedBlockHash: finalizedBlock.asBlockHash),
PayloadAttributesV2( some PayloadAttributesV2(
timestamp: Quantity timestamp, timestamp: Quantity timestamp,
prevRandao: FixedBytes[32] randomData.data, prevRandao: FixedBytes[32] randomData.data,
suggestedFeeRecipient: suggestedFeeRecipient, suggestedFeeRecipient: suggestedFeeRecipient,
@ -1204,8 +1194,8 @@ proc sendNewPayload*(m: ELManager,
proc forkchoiceUpdatedForSingleEL( proc forkchoiceUpdatedForSingleEL(
connection: ELConnection, connection: ELConnection,
state: ref ForkchoiceStateV1, state: ref ForkchoiceStateV1,
payloadAttributes: PayloadAttributesV1 | PayloadAttributesV2 | payloadAttributes: Option[PayloadAttributesV1] |
NoPayloadAttributesType): Option[PayloadAttributesV2]):
Future[PayloadStatusV1] {.async.} = Future[PayloadStatusV1] {.async.} =
let let
rpcClient = await connection.connectedRpcClient() rpcClient = await connection.connectedRpcClient()
@ -1223,9 +1213,10 @@ proc forkchoiceUpdatedForSingleEL(
return response.payloadStatus return response.payloadStatus
proc forkchoiceUpdated*(m: ELManager, proc forkchoiceUpdated*(m: ELManager,
headBlockHash, safeBlockHash, finalizedBlockHash: Eth2Digest, headBlockHash, safeBlockHash,
payloadAttributes: PayloadAttributesV1 | PayloadAttributesV2 | finalizedBlockHash: Eth2Digest,
NoPayloadAttributesType): payloadAttributes: Option[PayloadAttributesV1] |
Option[PayloadAttributesV2]):
Future[(PayloadExecutionStatus, Option[BlockHash])] {.async.} = Future[(PayloadExecutionStatus, Option[BlockHash])] {.async.} =
doAssert not headBlockHash.isZero doAssert not headBlockHash.isZero
@ -1243,18 +1234,24 @@ proc forkchoiceUpdated*(m: ELManager,
if m.elConnections.len == 0: if m.elConnections.len == 0:
return (PayloadExecutionStatus.syncing, none BlockHash) return (PayloadExecutionStatus.syncing, none BlockHash)
when payloadAttributes is PayloadAttributesV2: when payloadAttributes is Option[PayloadAttributesV2]:
template payloadAttributesV2(): auto = payloadAttributes
elif payloadAttributes is PayloadAttributesV1:
template payloadAttributesV2(): auto = PayloadAttributesV2(
timestamp: payloadAttributes.timestamp,
prevRandao: payloadAttributes.prevRandao,
suggestedFeeRecipient: payloadAttributes.suggestedFeeRecipient,
withdrawals: @[])
elif payloadAttributes is NoPayloadAttributesType:
template payloadAttributesV2(): auto = template payloadAttributesV2(): auto =
# Because timestamp and prevRandao are both 0, won't false-positive match if payloadAttributes.isSome:
(static(default(PayloadAttributesV2))) payloadAttributes.get
else:
# As timestamp and prevRandao are both 0, won't false-positive match
(static(default(PayloadAttributesV2)))
elif payloadAttributes is Option[PayloadAttributesV1]:
template payloadAttributesV2(): auto =
if payloadAttributes.isSome:
PayloadAttributesV2(
timestamp: payloadAttributes.get.timestamp,
prevRandao: payloadAttributes.get.prevRandao,
suggestedFeeRecipient: payloadAttributes.get.suggestedFeeRecipient,
withdrawals: @[])
else:
# As timestamp and prevRandao are both 0, won't false-positive match
(static(default(PayloadAttributesV2)))
else: else:
static: doAssert false static: doAssert false

View File

@ -214,14 +214,14 @@ proc storeBackfillBlock(
res res
from web3/engine_api_types import
from web3/engine_api_types import PayloadExecutionStatus, PayloadStatusV1 PayloadAttributesV1, PayloadAttributesV2, PayloadExecutionStatus,
PayloadStatusV1
from ../eth1/eth1_monitor import from ../eth1/eth1_monitor import
ELManager, NoPayloadAttributes, asEngineExecutionPayload, sendNewPayload, ELManager, asEngineExecutionPayload, sendNewPayload, forkchoiceUpdated
forkchoiceUpdated
proc expectValidForkchoiceUpdated( proc expectValidForkchoiceUpdated(
elManager: ELManager, elManager: ELManager, headBlockPayloadAttributesType: typedesc,
headBlockHash, safeBlockHash, finalizedBlockHash: Eth2Digest, headBlockHash, safeBlockHash, finalizedBlockHash: Eth2Digest,
receivedBlock: ForkySignedBeaconBlock): Future[void] {.async.} = receivedBlock: ForkySignedBeaconBlock): Future[void] {.async.} =
let let
@ -229,7 +229,7 @@ proc expectValidForkchoiceUpdated(
headBlockHash = headBlockHash, headBlockHash = headBlockHash,
safeBlockHash = safeBlockHash, safeBlockHash = safeBlockHash,
finalizedBlockHash = finalizedBlockHash, finalizedBlockHash = finalizedBlockHash,
payloadAttributes = NoPayloadAttributes) payloadAttributes = none headBlockPayloadAttributesType)
receivedExecutionBlockHash = receivedExecutionBlockHash =
when typeof(receivedBlock).toFork >= ConsensusFork.Bellatrix: when typeof(receivedBlock).toFork >= ConsensusFork.Bellatrix:
receivedBlock.message.body.execution_payload.block_hash receivedBlock.message.body.execution_payload.block_hash
@ -525,7 +525,7 @@ proc storeBlock*(
# > Client software MAY skip an update of the forkchoice state and MUST # > Client software MAY skip an update of the forkchoice state and MUST
# NOT begin a payload build process if `forkchoiceState.headBlockHash` # NOT begin a payload build process if `forkchoiceState.headBlockHash`
# references an ancestor of the head of canonical chain. # references an ancestor of the head of canonical chain.
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.2/src/engine/paris.md#specification-1 # https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/paris.md#specification-1
# #
# However, in practice, an EL client may not have completed importing all # However, in practice, an EL client may not have completed importing all
# block headers, so may be unaware of a block's ancestor status. # block headers, so may be unaware of a block's ancestor status.
@ -534,11 +534,27 @@ proc storeBlock*(
# - "Beacon chain gapped" from DAG head to optimistic head, # - "Beacon chain gapped" from DAG head to optimistic head,
# - followed by "Beacon chain reorged" from optimistic head back to DAG. # - followed by "Beacon chain reorged" from optimistic head back to DAG.
self.consensusManager[].updateHead(newHead.get.blck) self.consensusManager[].updateHead(newHead.get.blck)
discard await elManager.forkchoiceUpdated(
headBlockHash = self.consensusManager[].optimisticExecutionPayloadHash, template callForkchoiceUpdated(attributes: untyped) =
safeBlockHash = newHead.get.safeExecutionPayloadHash, discard await elManager.forkchoiceUpdated(
finalizedBlockHash = newHead.get.finalizedExecutionPayloadHash, headBlockHash = self.consensusManager[].optimisticExecutionPayloadHash,
payloadAttributes = NoPayloadAttributes) safeBlockHash = newHead.get.safeExecutionPayloadHash,
finalizedBlockHash = newHead.get.finalizedExecutionPayloadHash,
payloadAttributes = none attributes)
case self.consensusManager.dag.cfg.consensusForkAtEpoch(
newHead.get.blck.bid.slot.epoch)
of ConsensusFork.Capella, ConsensusFork.Deneb:
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/shanghai.md#specification-1
# Consensus layer client MUST call this method instead of
# `engine_forkchoiceUpdatedV1` under any of the following conditions:
# `headBlockHash` references a block which `timestamp` is greater or
# equal to the Shanghai timestamp
callForkchoiceUpdated(PayloadAttributesV2)
of ConsensusFork.Bellatrix:
callForkchoiceUpdated(PayloadAttributesV1)
of ConsensusFork.Phase0, ConsensusFork.Altair:
discard
else: else:
let let
headExecutionPayloadHash = headExecutionPayloadHash =
@ -557,11 +573,22 @@ proc storeBlock*(
if self.consensusManager.checkNextProposer(wallSlot).isNone: if self.consensusManager.checkNextProposer(wallSlot).isNone:
# No attached validator is next proposer, so use non-proposal fcU # No attached validator is next proposer, so use non-proposal fcU
await elManager.expectValidForkchoiceUpdated(
headBlockHash = headExecutionPayloadHash, template callForkchoiceUpdated(payloadAttributeType: untyped): auto =
safeBlockHash = newHead.get.safeExecutionPayloadHash, await elManager.expectValidForkchoiceUpdated(
finalizedBlockHash = newHead.get.finalizedExecutionPayloadHash, headBlockPayloadAttributesType = payloadAttributeType,
receivedBlock = signedBlock) headBlockHash = headExecutionPayloadHash,
safeBlockHash = newHead.get.safeExecutionPayloadHash,
finalizedBlockHash = newHead.get.finalizedExecutionPayloadHash,
receivedBlock = signedBlock)
case self.consensusManager.dag.cfg.consensusForkAtEpoch(
newHead.get.blck.bid.slot.epoch)
of ConsensusFork.Capella, ConsensusFork.Deneb:
callForkchoiceUpdated(payloadAttributeType = PayloadAttributesV2)
of ConsensusFork.Phase0, ConsensusFork.Altair,
ConsensusFork.Bellatrix:
callForkchoiceUpdated(payloadAttributeType = PayloadAttributesV1)
else: else:
# Some attached validator is next proposer, so prepare payload. As # Some attached validator is next proposer, so prepare payload. As
# updateHead() updated the DAG head, runProposalForkchoiceUpdated, # updateHead() updated the DAG head, runProposalForkchoiceUpdated,

View File

@ -105,7 +105,12 @@ programMain:
opt = signedBlock.toBlockId(), opt = signedBlock.toBlockId(),
wallSlot = getBeaconTime().slotOrZero wallSlot = getBeaconTime().slotOrZero
withBlck(signedBlock): withBlck(signedBlock):
when consensusFork >= ConsensusFork.Bellatrix: when consensusFork >= ConsensusFork.Capella:
# https://github.com/ethereum/execution-apis/blob/v1.0.0-beta.3/src/engine/shanghai.md#specification-1
# Consensus layer client MUST call this method instead of
# `engine_forkchoiceUpdatedV1` under any of the following conditions:
# `headBlockHash` references a block which `timestamp` is greater or
# equal to the Shanghai timestamp
if blck.message.is_execution_block: if blck.message.is_execution_block:
template payload(): auto = blck.message.body.execution_payload template payload(): auto = blck.message.body.execution_payload
@ -115,7 +120,18 @@ programMain:
headBlockHash = payload.block_hash, headBlockHash = payload.block_hash,
safeBlockHash = payload.block_hash, # stub value safeBlockHash = payload.block_hash, # stub value
finalizedBlockHash = ZERO_HASH, finalizedBlockHash = ZERO_HASH,
payloadAttributes = NoPayloadAttributes) payloadAttributes = none PayloadAttributesV2)
elif consensusFork >= ConsensusFork.Bellatrix:
if blck.message.is_execution_block:
template payload(): auto = blck.message.body.execution_payload
if elManager != nil and not payload.block_hash.isZero:
discard await elManager.newExecutionPayload(payload)
discard await elManager.forkchoiceUpdated(
headBlockHash = payload.block_hash,
safeBlockHash = payload.block_hash, # stub value
finalizedBlockHash = ZERO_HASH,
payloadAttributes = none PayloadAttributesV1)
else: discard else: discard
optimisticProcessor = initOptimisticProcessor( optimisticProcessor = initOptimisticProcessor(
getBeaconTime, optimisticHandler) getBeaconTime, optimisticHandler)