handle INVALIDATED forkchoiceUpdated better (#4081)

This commit is contained in:
tersec 2022-09-07 20:54:37 +00:00 committed by GitHub
parent 0191225896
commit cd46af17e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 37 additions and 9 deletions

View File

@ -211,17 +211,21 @@ proc runForkchoiceUpdatedDiscardResult*(
discard await eth1Monitor.runForkchoiceUpdated( discard await eth1Monitor.runForkchoiceUpdated(
headBlockRoot, safeBlockRoot, finalizedBlockRoot) headBlockRoot, safeBlockRoot, finalizedBlockRoot)
proc updateExecutionClientHead(self: ref ConsensusManager, newHead: BeaconHead) from ../beacon_clock import GetBeaconTimeFn
{.async.} = from ../fork_choice/fork_choice import mark_root_invalid
proc updateExecutionClientHead(
self: ref ConsensusManager, newHead: BeaconHead):
Future[Opt[void]] {.async.} =
if self.eth1Monitor.isNil: if self.eth1Monitor.isNil:
return return Opt[void].ok()
let headExecutionPayloadHash = self.dag.loadExecutionBlockRoot(newHead.blck) let headExecutionPayloadHash = self.dag.loadExecutionBlockRoot(newHead.blck)
if headExecutionPayloadHash.isZero: if headExecutionPayloadHash.isZero:
# Blocks without execution payloads can't be optimistic. # Blocks without execution payloads can't be optimistic.
self.dag.markBlockVerified(self.quarantine[], newHead.blck.root) self.dag.markBlockVerified(self.quarantine[], newHead.blck.root)
return return Opt[void].ok()
# Can't use dag.head here because it hasn't been updated yet # Can't use dag.head here because it hasn't been updated yet
let payloadExecutionStatus = await self.eth1Monitor.runForkchoiceUpdated( let payloadExecutionStatus = await self.eth1Monitor.runForkchoiceUpdated(
@ -235,9 +239,12 @@ proc updateExecutionClientHead(self: ref ConsensusManager, newHead: BeaconHead)
of PayloadExecutionStatus.invalid, PayloadExecutionStatus.invalid_block_hash: of PayloadExecutionStatus.invalid, PayloadExecutionStatus.invalid_block_hash:
self.dag.markBlockInvalid(newHead.blck.root) self.dag.markBlockInvalid(newHead.blck.root)
self.quarantine[].addUnviable(newHead.blck.root) self.quarantine[].addUnviable(newHead.blck.root)
return Opt.none(void)
of PayloadExecutionStatus.accepted, PayloadExecutionStatus.syncing: of PayloadExecutionStatus.accepted, PayloadExecutionStatus.syncing:
self.dag.optimisticRoots.incl newHead.blck.root self.dag.optimisticRoots.incl newHead.blck.root
return Opt[void].ok()
proc updateHead*(self: var ConsensusManager, newHead: BlockRef) = proc updateHead*(self: var ConsensusManager, newHead: BlockRef) =
## Trigger fork choice and update the DAG with the new head block ## Trigger fork choice and update the DAG with the new head block
## This does not automatically prune the DAG after finalization ## This does not automatically prune the DAG after finalization
@ -352,8 +359,8 @@ proc runProposalForkchoiceUpdated*(
error "Engine API fork-choice update failed", err = err.msg error "Engine API fork-choice update failed", err = err.msg
proc updateHeadWithExecution*( proc updateHeadWithExecution*(
self: ref ConsensusManager, newHead: BeaconHead, wallSlot: Slot) self: ref ConsensusManager, initialNewHead: BeaconHead,
{.async.} = getBeaconTimeFn: GetBeaconTimeFn) {.async.} =
## Trigger fork choice and update the DAG with the new head block ## Trigger fork choice and update the DAG with the new head block
## This does not automatically prune the DAG after finalization ## This does not automatically prune the DAG after finalization
## `pruneFinalized` must be called for pruning. ## `pruneFinalized` must be called for pruning.
@ -361,7 +368,28 @@ proc updateHeadWithExecution*(
# Grab the new head according to our latest attestation data # Grab the new head according to our latest attestation data
try: try:
# Ensure dag.updateHead has most current information # Ensure dag.updateHead has most current information
await self.updateExecutionClientHead(newHead) var
attempts = 0
newHead = initialNewHead
while (await self.updateExecutionClientHead(newHead)).isErr:
# This proc is called on every new block; guarantee timely return
inc attempts
const maxAttempts = 3
if attempts >= maxAttempts:
warn "updateHeadWithExecution: too many attempts to recover from invalid payload",
attempts, maxAttempts, newHead, initialNewHead
break
# Select new head for next attempt
let
wallTime = getBeaconTimeFn()
nextHead = self.attestationPool[].selectOptimisticHead(wallTime).valueOr:
warn "Head selection failed after invalid block, using previous head",
newHead, wallSlot = wallTime.slotOrZero
break
warn "updateHeadWithExecution: attempting to recover from invalid payload",
attempts, maxAttempts, newHead, initialNewHead, nextHead
newHead = nextHead
# Store the new head in the chain DAG - this may cause epochs to be # Store the new head in the chain DAG - this may cause epochs to be
# justified and finalized # justified and finalized
@ -373,7 +401,7 @@ proc updateHeadWithExecution*(
# needs while runProposalForkchoiceUpdated requires RANDAO information # needs while runProposalForkchoiceUpdated requires RANDAO information
# from the head state corresponding to the `newHead` block, which only # from the head state corresponding to the `newHead` block, which only
# self.dag.updateHead(...) sets up. # self.dag.updateHead(...) sets up.
await self.runProposalForkchoiceUpdated(wallSlot) await self.runProposalForkchoiceUpdated(getBeaconTimeFn().slotOrZero)
self[].checkExpectedBlock() self[].checkExpectedBlock()
except CatchableError as exc: except CatchableError as exc:

View File

@ -346,7 +346,7 @@ proc storeBlock*(
wallSlot) wallSlot)
else: else:
asyncSpawn self.consensusManager.updateHeadWithExecution( asyncSpawn self.consensusManager.updateHeadWithExecution(
newHead.get, wallSlot) newHead.get, self.getBeaconTime)
else: else:
warn "Head selection failed, using previous head", warn "Head selection failed, using previous head",
head = shortLog(self.consensusManager.dag.head), wallSlot head = shortLog(self.consensusManager.dag.head), wallSlot