From 8cec3af61c8847675235c69c3e716567bc3951e3 Mon Sep 17 00:00:00 2001 From: Eugene Kabanov Date: Sat, 4 Nov 2023 09:14:14 +0200 Subject: [PATCH] VC: Obtain randao signature before slot proposal. (#5490) * Randao calculation caching for VC implementation. * Add time monitoring for randao signatures process. * Add delay calculation. * Address review comments. * Address review comments. --- .../validator_client/block_service.nim | 103 +++++++++++++----- beacon_chain/validator_client/common.nim | 3 +- beacon_chain/validators/validator_pool.nim | 15 ++- 3 files changed, 94 insertions(+), 27 deletions(-) diff --git a/beacon_chain/validator_client/block_service.nim b/beacon_chain/validator_client/block_service.nim index ad4cad057..995d3c377 100644 --- a/beacon_chain/validator_client/block_service.nim +++ b/beacon_chain/validator_client/block_service.nim @@ -32,6 +32,9 @@ type blockRoot*: Eth2Digest data*: ForkedBlindedBeaconBlock +proc proposeBlock(vc: ValidatorClientRef, slot: Slot, + proposerKey: ValidatorPubKey) {.async.} + proc produceBlock( vc: ValidatorClientRef, currentSlot, slot: Slot, @@ -86,7 +89,6 @@ proc produceBlock( data: ForkedBeaconBlock.init(blck), blobsOpt: Opt.some(blobs))) - proc produceBlindedBlock( vc: ValidatorClientRef, currentSlot, slot: Slot, @@ -125,6 +127,58 @@ proc lazyWait[T](fut: Future[T]) {.async.} = except CatchableError: discard +proc prepareRandao(vc: ValidatorClientRef, slot: Slot, + proposerKey: ValidatorPubKey) {.async.} = + if slot == GENESIS_SLOT: + return + + let + destSlot = slot - 1'u64 + destOffset = TimeDiff(nanoseconds: NANOSECONDS_PER_SLOT.int64 div 2) + deadline = destSlot.start_beacon_time() + destOffset + epoch = slot.epoch() + # We going to wait to T - (T / 4 * 2), where T is proposer's + # duty slot. + currentSlot = (await vc.checkedWaitForSlot(destSlot, destOffset, + false)).valueOr: + debug "Unable to perform RANDAO signature preparation because of " & + "system time failure" + return + validator = + vc.getValidatorForDuties(proposerKey, slot, true).valueOr: return + + if currentSlot <= destSlot: + # We do not need result, because we want it to be cached. + let + start = Moment.now() + genesisRoot = vc.beaconGenesis.genesis_validators_root + fork = vc.forkAtEpoch(epoch) + rsig = await validator.getEpochSignature(fork, genesisRoot, epoch) + timeElapsed = Moment.now() - start + if rsig.isErr(): + debug "Unable to prepare RANDAO signature", epoch = epoch, + validator = shortLog(validator), elapsed_time = timeElapsed, + current_slot = currentSlot, destination_slot = destSlot, + delay = vc.getDelay(deadline) + else: + debug "RANDAO signature has been prepared", epoch = epoch, + validator = shortLog(validator), elapsed_time = timeElapsed, + current_slot = currentSlot, destination_slot = destSlot, + delay = vc.getDelay(deadline) + else: + debug "RANDAO signature preparation timed out", epoch = epoch, + validator = shortLog(validator), + current_slot = currentSlot, destination_slot = destSlot, + delay = vc.getDelay(deadline) + +proc spawnProposalTask(vc: ValidatorClientRef, + duty: RestProposerDuty): ProposerTask = + ProposerTask( + randaoFut: prepareRandao(vc, duty.slot, duty.pubkey), + proposeFut: proposeBlock(vc, duty.slot, duty.pubkey), + duty: duty + ) + proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot, validator: AttachedValidator) {.async.} = let @@ -146,21 +200,22 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot, debug "Publishing block", delay = vc.getDelay(slot.block_deadline()), genesis_root = genesisRoot, graffiti = graffiti, fork = fork - let randaoReveal = - try: - let res = await validator.getEpochSignature(fork, genesisRoot, slot.epoch) - if res.isErr(): - warn "Unable to generate randao reveal using remote signer", - reason = res.error() + + let + randaoReveal = + try: + (await validator.getEpochSignature(fork, genesisRoot, + slot.epoch())).valueOr: + warn "Unable to generate RANDAO reveal using remote signer", + reason = error + return + except CancelledError as exc: + debug "RANDAO reveal production has been interrupted" + raise exc + except CatchableError as exc: + error "An unexpected error occurred while receiving RANDAO data", + error_name = exc.name, error_msg = exc.msg return - res.get() - except CancelledError as exc: - debug "Randao reveal production has been interrupted" - raise exc - except CatchableError as exc: - error "An unexpected error occurred while receiving randao data", - error_name = exc.name, error_msg = exc.msg - return var beaconBlocks = block: @@ -408,11 +463,6 @@ proc proposeBlock(vc: ValidatorClientRef, slot: Slot, error "Unexpected error encountered while proposing block", slot = slot, validator = shortLog(validator) -proc spawnProposalTask(vc: ValidatorClientRef, - duty: RestProposerDuty): ProposerTask = - let future = proposeBlock(vc, duty.slot, duty.pubkey) - ProposerTask(future: future, duty: duty) - proc contains(data: openArray[RestProposerDuty], task: ProposerTask): bool = for item in data: if (item.pubkey == task.duty.pubkey) and (item.slot == task.duty.slot): @@ -462,13 +512,14 @@ proc addOrReplaceProposers*(vc: ValidatorClientRef, epoch: Epoch, for task in epochDuties.duties: if task notin duties: # Task is no more relevant, so cancel it. - debug "Cancelling running proposal duty task", + debug "Cancelling running proposal duty tasks", slot = task.duty.slot, validator = shortLog(task.duty.pubkey) - task.future.cancelSoon() + task.proposeFut.cancelSoon() + task.randaoFut.cancelSoon() else: # If task is already running for proper slot, we keep it alive. - debug "Keep running previous proposal duty task", + debug "Keep running previous proposal duty tasks", slot = task.duty.slot, validator = shortLog(task.duty.pubkey) res.add(task) @@ -783,8 +834,10 @@ proc mainLoop(service: BlockServiceRef) {.async.} = var res: seq[FutureBase] for epoch, data in vc.proposers.pairs(): for duty in data.duties.items(): - if not(duty.future.finished()): - res.add(duty.future.cancelAndWait()) + if not(duty.proposeFut.finished()): + res.add(duty.proposeFut.cancelAndWait()) + if not(duty.randaoFut.finished()): + res.add(duty.randaoFut.cancelAndWait()) await noCancel allFutures(res) proc init*(t: typedesc[BlockServiceRef], diff --git a/beacon_chain/validator_client/common.nim b/beacon_chain/validator_client/common.nim index 7202bc9aa..eb4a50557 100644 --- a/beacon_chain/validator_client/common.nim +++ b/beacon_chain/validator_client/common.nim @@ -95,7 +95,8 @@ type ProposerTask* = object duty*: RestProposerDuty - future*: Future[void] + proposeFut*: Future[void] + randaoFut*: Future[void] ProposedData* = object epoch*: Epoch diff --git a/beacon_chain/validators/validator_pool.nim b/beacon_chain/validators/validator_pool.nim index 345712e47..6ad6e4416 100644 --- a/beacon_chain/validators/validator_pool.nim +++ b/beacon_chain/validators/validator_pool.nim @@ -66,6 +66,10 @@ type # if the validator will be aggregating (in the near future) slotSignature*: Opt[tuple[slot: Slot, signature: ValidatorSig]] + # Cache the latest epoch signature - the epoch signature is used for block + # proposing. + epochSignature*: Opt[tuple[epoch: Epoch, signature: ValidatorSig]] + # For the external payload builder; each epoch, the external payload # builder should be informed of current validators externalBuilderRegistration*: Opt[SignedValidatorRegistrationV1] @@ -746,7 +750,10 @@ proc getContributionAndProofSignature*(v: AttachedValidator, fork: Fork, proc getEpochSignature*(v: AttachedValidator, fork: Fork, genesis_validators_root: Eth2Digest, epoch: Epoch ): Future[SignatureResult] {.async.} = - return + if v.epochSignature.isSome and v.epochSignature.get.epoch == epoch: + return SignatureResult.ok(v.epochSignature.get.signature) + + let signature = case v.kind of ValidatorKind.Local: SignatureResult.ok(get_epoch_signature( @@ -757,6 +764,12 @@ proc getEpochSignature*(v: AttachedValidator, fork: Fork, fork, genesis_validators_root, epoch) await v.signData(request) + if signature.isErr: + return signature + + v.epochSignature = Opt.some((epoch, signature.get)) + signature + # https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.1/specs/phase0/validator.md#aggregation-selection proc getSlotSignature*(v: AttachedValidator, fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot