diff --git a/beacon_chain/consensus_object_pools/block_clearance.nim b/beacon_chain/consensus_object_pools/block_clearance.nim index adb859922b..5ca1506f52 100644 --- a/beacon_chain/consensus_object_pools/block_clearance.nim +++ b/beacon_chain/consensus_object_pools/block_clearance.nim @@ -532,6 +532,78 @@ proc addHeadExecutionPayload*( ok(blck) +proc addBackfillExecutionPayload*( + dag: ChainDAGRef, + signedEnvelope: gloas.SignedExecutionPayloadEnvelope | + gloas.TrustedSignedExecutionPayloadEnvelope, +): Result[void, VerifierError] = + template blockRoot(): auto = signedEnvelope.message.beacon_block_root + template envelope(): auto = signedEnvelope.message + + logScope: + blockRoot = shortLog(blockRoot) + slot = envelope.slot + signature = shortLog(signedEnvelope.signature) + backfill = shortLog(dag.backfill) + + let startTick = Moment.now() + + # When a valid block is backfilled, dag.backfill has already moved to next + # parent. So we need to check with finalizedHead and database. + if envelope.slot > dag.finalizedHead.slot: + return err(VerifierError.Invalid) + + # Check root and slot of the block + let bsi = dag.getBlockIdAtSlot(envelope.slot).valueOr: + # This should not be happening as we backfill envelope after the block is + # backfilled successfully. + return err(VerifierError.Invalid) + if blockRoot != bsi.bid.root: + return err(VerifierError.Invalid) + if dag.db.containsExecutionPayloadEnvelope(blockRoot): + return err(VerifierError.Duplicate) + + # Check builder index is matched with the block + block: + let blck = dag.getForkedBlock(bsi.bid).valueOr: + # The block should exist as we have checked above. Database may be + # corrupted. + debug "Backfill envelope cannot find forked block, database corrupt?" + return err(VerifierError.Invalid) + withBlck(blck): + when consensusFork >= ConsensusFork.Gloas: + template bid(): auto = + forkyBlck.message.body.signed_execution_payload_bid + if bid.message.builder_index != envelope.builder_index: + return err(VerifierError.Invalid) + else: + return err(VerifierError.UnviableFork) + + # Verify signature + when signedEnvelope.signature isnot TrustedSig: + let builderKey = dag.validatorKey(envelope.builder_index).valueOr: + fatal "Invalid builder in backfill envelope - checkpoint state corrupt?", + head = shortLog(dag.head), tail = shortLog(dag.tail) + quit 1 + if not verify_execution_payload_envelope_signature( + dag.forkAtEpoch(envelope.slot.epoch), + dag.genesis_validators_root, + envelope.slot.epoch, + envelope, + builderKey, + signedEnvelope.signature): + return err(VerifierError.Invalid) + let sigVerifyTick = Moment.now + + dag.db.putExecutionPayloadEnvelope(signedEnvelope) + let putBlockTick = Moment.now + + debug "Envelope backfilled", + sigVerifyDur = sigVerifyTick - startTick, + putBlockDur = putBlockTick - sigVerifyTick + + ok() + proc verifyBlockSignatures*( verifier: var BatchVerifier, fork: Fork, diff --git a/beacon_chain/consensus_object_pools/envelope_quarantine.nim b/beacon_chain/consensus_object_pools/envelope_quarantine.nim index a8f7aa6ac2..9c30510c91 100644 --- a/beacon_chain/consensus_object_pools/envelope_quarantine.nim +++ b/beacon_chain/consensus_object_pools/envelope_quarantine.nim @@ -61,6 +61,10 @@ func popOrphan*( func delOrphan*(self: var EnvelopeQuarantine, blck: gloas.SignedBeaconBlock) = self.orphans.del(blck.root) +func remove*(self: var EnvelopeQuarantine, root: Eth2Digest) = + self.orphans.del(root) + self.missing.excl(root) + func cleanupOrphans*(self: var EnvelopeQuarantine, finalizedSlot: Slot) = var toDel: seq[Eth2Digest] diff --git a/beacon_chain/gossip_processing/block_processor.nim b/beacon_chain/gossip_processing/block_processor.nim index db456aa36a..cc56a24d0d 100644 --- a/beacon_chain/gossip_processing/block_processor.nim +++ b/beacon_chain/gossip_processing/block_processor.nim @@ -31,7 +31,7 @@ from ../consensus_object_pools/block_quarantine import from ../consensus_object_pools/blob_quarantine import BlobQuarantine, ColumnQuarantine, GloasColumnQuarantine, popSidecars, put from ../consensus_object_pools/envelope_quarantine import - EnvelopeQuarantine, addMissing, addOrphan, delOrphan, popOrphan + EnvelopeQuarantine, addMissing, addOrphan, delOrphan, popOrphan, remove from ../validators/validator_monitor import MsgSource, ValidatorMonitor, registerAttestationInBlock, registerBeaconBlock, registerSyncAggregateInBlock @@ -186,10 +186,12 @@ proc dumpBlock( discard from ../consensus_object_pools/block_clearance import - addBackfillBlock, addHeadBlockWithParent, checkHeadBlock, verifyBlockProposer + addBackfillBlock, addBackfillExecutionPayload, + addHeadBlockWithParent, checkHeadBlock, verifyBlockProposer proc verifySidecars( signedBlock: ForkySignedBeaconBlock, + envelope: NoEnvelope | gloas.SignedExecutionPayloadEnvelope, sidecarsOpt: SomeOptSidecars, ): Result[void, VerifierError] = const consensusFork = typeof(signedBlock).kind @@ -262,7 +264,10 @@ proc storeBackfillBlock( # In case the block was added to any part of the quarantine.. quarantine[].remove(signedBlock) - ?verifySidecars(signedBlock, sidecarsOpt) + const consensusFork = typeof(signedBlock).kind + + when consensusFork <= ConsensusFork.Fulu: + ?verifySidecars(signedBlock, noEnvelope, sidecarsOpt) let res = self.consensusManager.dag.addBackfillBlock(signedBlock) @@ -490,7 +495,9 @@ proc onBlockAdded*( ) proc verifyPayload( - self: ref BlockProcessor, signedBlock: ForkySignedBeaconBlock + self: ref BlockProcessor, + signedBlock: ForkySignedBeaconBlock, + signedEnvelope: NoEnvelope | gloas.SignedExecutionPayloadEnvelope, ): Result[OptimisticStatus, VerifierError] = const consensusFork = typeof(signedBlock).kind # When the execution layer is not available to verify the payload, we do the @@ -647,14 +654,22 @@ proc storeBlock( else: Opt.some(OptimisticStatus.valid) # vacuously - let optimisticStatus = ?(optimisticStatusRes or verifyPayload(self, signedBlock)) + let optimisticStatus = + when consensusFork >= ConsensusFork.Gloas: + # The execution payload validity is not known yet at block time as an + # envelope will be processed after its valid block. So always return + # `notValidated` and skip verifying payload. + OptimisticStatus.notValidated + else: + ?(optimisticStatusRes or verifyPayload(self, signedBlock, noEnvelope)) if OptimisticStatus.invalidated == optimisticStatus: return err(VerifierError.Invalid) let newPayloadTick = Moment.now() - ?verifySidecars(signedBlock, sidecarsOpt) + when consensusFork <= ConsensusFork.Fulu: + ?verifySidecars(signedBlock, noEnvelope, sidecarsOpt) let blck = ?dag.addHeadBlockWithParent( @@ -873,6 +888,22 @@ proc addBlock*( of VerifierError.Duplicate: err(res.error()) +proc storeBackfillPayload( + self: var BlockProcessor, + signedBlock: gloas.SignedBeaconBlock, + signedEnvelope: gloas.SignedExecutionPayloadEnvelope, + sidecarsOpt: Opt[gloas.DataColumnSidecars], +): Result[void, VerifierError] = + self.envelopeQuarantine[].remove(signedEnvelope.message.beacon_block_root) + + ?verifySidecars(signedBlock, signedEnvelope, sidecarsOpt) + + self.consensusManager.dag.addBackfillExecutionPayload(signedEnvelope).isOkOr: + return err(error) + + self.storeSidecars(sidecarsOpt) + ok() + proc storePayload( self: ref BlockProcessor, signedBlock: gloas.SignedBeaconBlock, @@ -903,16 +934,24 @@ proc storePayload( ok() -proc enqueuePayload*( +proc addPayload*( self: ref BlockProcessor, blck: gloas.SignedBeaconBlock, envelope: gloas.SignedExecutionPayloadEnvelope, sidecarsOpt: Opt[gloas.DataColumnSidecars], -) = +): Future[Result[void, VerifierError]] {.async: (raises: [CancelledError]).} = if blck.message.slot <= self.consensusManager.dag.finalizedHead.slot: - debugGloasComment("backfilling") + return self[].storeBackfillPayload(blck, envelope, sidecarsOpt) + + await self.storePayload(blck, envelope, sidecarsOpt) - discard self.storePayload(blck, envelope, sidecarsOpt) +proc enqueuePayload*( + self: ref BlockProcessor, + blck: gloas.SignedBeaconBlock, + envelope: gloas.SignedExecutionPayloadEnvelope, + sidecarsOpt: Opt[gloas.DataColumnSidecars], +) = + discard self.addPayload(blck, envelope, sidecarsOpt) proc enqueuePayload*(self: ref BlockProcessor, blck: gloas.SignedBeaconBlock) = ## Enqueue payload processing by block that is a valid block. diff --git a/beacon_chain/spec/datatypes/gloas.nim b/beacon_chain/spec/datatypes/gloas.nim index a358586850..e250f434af 100644 --- a/beacon_chain/spec/datatypes/gloas.nim +++ b/beacon_chain/spec/datatypes/gloas.nim @@ -121,7 +121,7 @@ type TrustedSignedExecutionPayloadEnvelope* = object message*: TrustedExecutionPayloadEnvelope - signature*: ValidatorSig + signature*: TrustedSig # https://github.com/ethereum/consensus-specs/blob/v1.6.0-alpha.6/specs/gloas/beacon-chain.md#payloadattestationdata PayloadAttestationData* = object diff --git a/beacon_chain/spec/signatures.nim b/beacon_chain/spec/signatures.nim index 307d16ea86..7934a537b2 100644 --- a/beacon_chain/spec/signatures.nim +++ b/beacon_chain/spec/signatures.nim @@ -476,9 +476,10 @@ proc verify_execution_payload_envelope_signature*( fork: Fork, genesis_validators_root: Eth2Digest, epoch: Epoch, msg: ExecutionPayloadEnvelope | TrustedExecutionPayloadEnvelope, pubkey: ValidatorPubKey | CookedPubKey, signature: SomeSig): bool = - let signing_root = compute_execution_payload_envelope_signing_root( - fork, genesis_validators_root, epoch, msg) - blsVerify(pubkey, signing_root.data, signature) + withTrust(signature): + let signing_root = compute_execution_payload_envelope_signing_root( + fork, genesis_validators_root, epoch, msg) + blsVerify(pubkey, signing_root.data, signature) # https://github.com/ethereum/consensus-specs/blob/v1.6.1/specs/gloas/validator.md#constructing-a-payload-attestation func compute_payload_attestation_message_signing_root*(