# beacon_chain # Copyright (c) 2018-2024 Status Research & Development GmbH # Licensed and distributed under either of # * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT). # * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0). # at your option. This file may not be copied, modified, or distributed except according to those terms. {.push raises: [].} import std/[tables, json, streams, sequtils, uri, sets], chronos, chronicles, metrics, json_serialization/std/net, presto/client, ../spec/[keystore, signatures, helpers, crypto], ../spec/datatypes/[phase0, altair], ../spec/eth2_apis/[rest_types, eth2_rest_serialization, rest_remote_signer_calls], ../filepath, ../conf, ./slashing_protection export streams, keystore, phase0, altair, tables, uri, crypto, signatures.voluntary_exit_signature_fork, rest_types, eth2_rest_serialization, rest_remote_signer_calls, slashing_protection const WEB3_SIGNER_DELAY_TOLERANCE = 3.seconds WEB3_SIGNER_ATTEMPTS_COUNT = 4 declareGauge validators, "Number of validators attached to the beacon node" logScope: topics = "val_pool" type ValidatorKind* {.pure.} = enum Local, Remote ValidatorAndIndex* = object index*: ValidatorIndex validator*: Validator AttachedValidator* = ref object data*: KeystoreData case kind*: ValidatorKind of ValidatorKind.Local: discard of ValidatorKind.Remote: clients*: seq[(RestClientRef, RemoteSignerInfo)] threshold*: uint32 updated*: bool index*: Opt[ValidatorIndex] ## Validator index which is assigned after the eth1 deposit has been ## processed - this index is valid across all eth2 forks for fork depths ## up to ETH1_FOLLOW_DISTANCE - we don't support changing indices. activationEpoch*: Epoch ## Epoch when validator activated - this happens at the time or some time ## after the validator index has been assigned depending on how many ## validators are in the activation queue - this is the first epoch that ## the validator starts performing duties # Cache the latest slot signature - the slot signature is used to determine # 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] doppelCheck*: Opt[Epoch] ## The epoch where doppelganger detection last performed a check doppelActivity*: Opt[Epoch] ## The last time we attempted to perform a duty with this validator validator*: Opt[Validator] ## Copy of validator's entry from head state. Used by validator client, ## to calculate feeRecipient address. lastWarning*: Opt[Slot] SignResponse* = Web3SignerDataResponse SignatureResult* = Result[ValidatorSig, string] SyncCommitteeMessageResult* = Result[SyncCommitteeMessage, string] ValidatorPool* = object validators*: Table[ValidatorPubKey, AttachedValidator] indexSet*: HashSet[ValidatorIndex] slashingProtection*: SlashingProtectionDB doppelgangerDetectionEnabled*: bool AddValidatorProc* = proc(keystore: KeystoreData) {.gcsafe, raises: [].} template pubkey*(v: AttachedValidator): ValidatorPubKey = v.data.pubkey func shortLog*(v: AttachedValidator): string = case v.kind of ValidatorKind.Local: shortLog(v.pubkey) of ValidatorKind.Remote: shortLog(v.pubkey) func init*(T: type ValidatorPool, slashingProtectionDB: SlashingProtectionDB, doppelgangerDetectionEnabled: bool): T = ## Initialize the validator pool and the slashing protection service ## `genesis_validators_root` is used as an unique ID for the ## blockchain ## `backend` is the KeyValue Store backend T( slashingProtection: slashingProtectionDB, doppelgangerDetectionEnabled: doppelgangerDetectionEnabled) template count*(pool: ValidatorPool): int = len(pool.validators) proc addLocalValidator( pool: var ValidatorPool, keystore: KeystoreData, feeRecipient: Eth1Address, gasLimit: uint64): AttachedValidator = doAssert keystore.kind == KeystoreKind.Local let v = AttachedValidator( kind: ValidatorKind.Local, data: keystore, externalBuilderRegistration: Opt.none SignedValidatorRegistrationV1, activationEpoch: FAR_FUTURE_EPOCH ) pool.validators[v.pubkey] = v # Fee recipient may change after startup, but we log the initial value here notice "Local validator attached", pubkey = v.pubkey, validator = shortLog(v), initial_fee_recipient = feeRecipient.toHex(), initial_gas_limit = gasLimit validators.set(pool.count().int64) v proc addRemoteValidator(pool: var ValidatorPool, keystore: KeystoreData, clients: seq[(RestClientRef, RemoteSignerInfo)], feeRecipient: Eth1Address, gasLimit: uint64): AttachedValidator = doAssert keystore.kind == KeystoreKind.Remote let v = AttachedValidator( kind: ValidatorKind.Remote, data: keystore, clients: clients, externalBuilderRegistration: Opt.none SignedValidatorRegistrationV1, activationEpoch: FAR_FUTURE_EPOCH, ) pool.validators[v.pubkey] = v if RemoteKeystoreFlag.DynamicKeystore in keystore.flags: notice "Dynamic remote validator attached", pubkey = v.pubkey, validator = shortLog(v), remote_signer = $keystore.remotes, initial_fee_recipient = feeRecipient.toHex(), initial_gas_limit = gasLimit else: notice "Remote validator attached", pubkey = v.pubkey, validator = shortLog(v), remote_signer = $keystore.remotes, initial_fee_recipient = feeRecipient.toHex(), initial_gas_limit = gasLimit validators.set(pool.count().int64) v proc addRemoteValidator(pool: var ValidatorPool, keystore: KeystoreData, feeRecipient: Eth1Address, gasLimit: uint64): AttachedValidator = let httpFlags = if RemoteKeystoreFlag.IgnoreSSLVerification in keystore.flags: {HttpClientFlag.NoVerifyHost, HttpClientFlag.NoVerifyServerName} else: {} prestoFlags = {RestClientFlag.CommaSeparatedArray, RestClientFlag.ResolveAlways} socketFlags = {SocketFlags.TcpNoDelay} clients = block: var res: seq[(RestClientRef, RemoteSignerInfo)] for remote in keystore.remotes: let client = RestClientRef.new( $remote.url, prestoFlags, httpFlags, socketFlags = socketFlags) if client.isErr(): # TODO keep trying in case of temporary network failure warn "Unable to resolve distributed signer address", remote_url = $remote.url, validator = $remote.pubkey else: res.add((client.get(), remote)) res pool.addRemoteValidator(keystore, clients, feeRecipient, gasLimit) proc addValidator*(pool: var ValidatorPool, keystore: KeystoreData, feeRecipient: Eth1Address, gasLimit: uint64): AttachedValidator = pool.validators.withValue(keystore.pubkey, v): notice "Adding already-known validator", validator = shortLog(v[]) return v[] case keystore.kind of KeystoreKind.Local: pool.addLocalValidator(keystore, feeRecipient, gasLimit) of KeystoreKind.Remote: pool.addRemoteValidator(keystore, feeRecipient, gasLimit) func getValidator*(pool: ValidatorPool, validatorKey: ValidatorPubKey): Opt[AttachedValidator] = let v = pool.validators.getOrDefault(validatorKey) if v == nil: Opt.none(AttachedValidator) else: Opt.some(v) func contains*(pool: ValidatorPool, pubkey: ValidatorPubKey): bool = ## Returns ``true`` if validator with key ``pubkey`` present in ``pool``. pool.validators.contains(pubkey) proc contains*(pool: ValidatorPool, index: ValidatorIndex): bool = ## Returns ``true`` if validator with index ``index`` present in ``pool``. pool.indexSet.contains(index) proc setValidatorIndex*(pool: var ValidatorPool, validator: AttachedValidator, index: ValidatorIndex) = pool.indexSet.incl(index) validator.index = Opt.some(index) proc removeValidatorIndex(pool: var ValidatorPool, index: ValidatorIndex) = pool.indexSet.excl(index) proc removeValidator*(pool: var ValidatorPool, pubkey: ValidatorPubKey) = ## Delete validator with public key ``pubkey`` from ``pool``. let validator = pool.validators.getOrDefault(pubkey) if not(isNil(validator)): if validator.index.isSome(): pool.removeValidatorIndex(validator.index.get) pool.validators.del(pubkey) case validator.kind of ValidatorKind.Local: notice "Local validator detached", pubkey, validator = shortLog(validator) of ValidatorKind.Remote: if RemoteKeystoreFlag.DynamicKeystore in validator.data.flags: notice "Dynamic remote validator detached", pubkey, validator = shortLog(validator) else: notice "Remote validator detached", pubkey, validator = shortLog(validator) validators.set(pool.count().int64) func needsUpdate*(validator: AttachedValidator): bool = validator.index.isNone() or validator.activationEpoch == FAR_FUTURE_EPOCH proc updateValidator*(pool: var ValidatorPool, validator: AttachedValidator, validatorData: Opt[ValidatorAndIndex]) = defer: validator.updated = true let data = validatorData.valueOr: if not validator.updated: notice "Validator deposit not yet processed, monitoring", pubkey = validator.pubkey return index = data.index activationEpoch = data.validator.activation_epoch ## Update activation information for a validator if validator.index != Opt.some data.index: pool.setValidatorIndex(validator, data.index) validator.index = Opt.some data.index validator.validator = Opt.some data.validator if validator.activationEpoch != data.validator.activation_epoch: # In theory, activation epoch could change but that's rare enough that it # shouldn't practically matter for the current uses info "Validator activation updated", validator = shortLog(validator), pubkey = validator.pubkey, index, activationEpoch validator.activationEpoch = activationEpoch func invalidateValidatorRegistration*( pool: var ValidatorPool, pubkey: ValidatorPubKey) = # When the per-validator fee recipient changes via keymanager, the builder # API validator registration needs to be recomputed. This will happen when # next the registrations are sent, but ensure here that will happen rather # than relying on a now-outdated, cached, validator registration. pool.getValidator(pubkey).isErrOr: value.externalBuilderRegistration.reset() proc close*(pool: var ValidatorPool) = ## Unlock and close all validator keystore's files managed by ``pool``. for validator in pool.validators.values(): let res = validator.data.handle.closeLockedFile() if res.isErr(): notice "Could not unlock validator's keystore file", pubkey = validator.pubkey, validator = shortLog(validator) pool.validators.clear() iterator indices*(pool: ValidatorPool): ValidatorIndex = for item in pool.validators.values(): if item.index.isSome(): yield item.index.get() iterator items*(pool: ValidatorPool): AttachedValidator = for item in pool.validators.values(): yield item proc doppelgangerChecked*(validator: AttachedValidator, epoch: Epoch) = ## Call when the validator was checked for activity in the given epoch if validator.doppelCheck.isNone(): debug "Doppelganger first check", validator = shortLog(validator), epoch else: let check = validator.doppelCheck.get() if check > epoch: # Shouldn't happen but due to `await`, it may - consider turning into # assert debug "Doppelganger reordered check", validator = shortLog(validator), check, epoch return if check - epoch > 1: debug "Doppelganger stale check", validator = shortLog(validator), check, epoch validator.doppelCheck = Opt.some epoch proc doppelgangerActivity*(validator: AttachedValidator, epoch: Epoch) = ## Call when we performed a doppelganger-monitored activity in the epoch if validator.doppelActivity.isNone(): debug "Doppelganger first activity", validator = shortLog(validator), epoch else: let activity = validator.doppelActivity.get() if activity > epoch: # Shouldn't happen but due to `await`, it may - consider turning into # assert debug "Doppelganger reordered activity", validator = shortLog(validator), activity, epoch return if epoch - activity > 1: # We missed work in some epoch debug "Doppelganger stale activity", validator = shortLog(validator), activity, epoch validator.doppelActivity = Opt.some epoch func triggersDoppelganger*(v: AttachedValidator, epoch: Epoch): bool = ## Returns true iff we have proof that an activity in the given epoch ## triggers doppelganger detection: this means the network was active for this ## validator during the given epoch (via doppelgangerChecked) but the activity ## did not originate from this instance. if v.doppelActivity.isSome() and v.doppelActivity.get() >= epoch: false # This was our own activity elif v.doppelCheck.isNone(): false # Can't prove that the activity triggers the check else: v.doppelCheck.get() == epoch func doppelgangerReady*(validator: AttachedValidator, slot: Slot): bool = ## Returns true iff the validator has passed doppelganger detection by being ## monitored in the previous epoch (or the given epoch is the activation ## epoch, in which case we always consider it ready) ## ## If we checked doppelganger, we allow the check to lag by one slot to avoid ## a race condition where the check for epoch N is ongoing and block ## block production for slot_start(N+1) is about to happen let epoch = slot.epoch epoch == validator.activationEpoch or (validator.doppelCheck.isSome and (((validator.doppelCheck.get() + 1) == epoch) or (((validator.doppelCheck.get() + 2).start_slot) == slot))) proc getValidatorForDuties*( pool: ValidatorPool, key: ValidatorPubKey, slot: Slot, slashingSafe: bool): Opt[AttachedValidator] = ## Return validator only if it is ready for duties (has index and has passed ## doppelganger check where applicable) let validator = ? pool.getValidator(key) if validator.index.isNone(): return Opt.none(AttachedValidator) # Sync committee duties are not slashable, so we perform them even during # doppelganger detection if pool.doppelgangerDetectionEnabled and not validator.doppelgangerReady(slot) and not slashingSafe: notice "Doppelganger detection active - " & "skipping validator duties while observing the network", validator = shortLog(validator), slot, doppelCheck = validator.doppelCheck, activationEpoch = shortLog(validator.activationEpoch) return Opt.none(AttachedValidator) return Opt.some(validator) func triggersDoppelganger*( pool: ValidatorPool, pubkey: ValidatorPubKey, epoch: Epoch): bool = let v = pool.getValidator(pubkey) v.isSome() and v[].triggersDoppelganger(epoch) proc updateDynamicValidators*(pool: ref ValidatorPool, web3signerUrl: Web3SignerUrl, keystores: openArray[KeystoreData], addProc: AddValidatorProc) = var keystoresTable: Table[ValidatorPubKey, Opt[KeystoreData]] deleteValidators: seq[ValidatorPubKey] for keystore in keystores: keystoresTable[keystore.pubkey] = Opt.some(keystore) # We preserve `Local` and `Remote` keystores which are not from dynamic set, # and also we removing all the dynamic keystores which are not part of new # dynamic set. for validator in pool[].items(): if validator.kind == ValidatorKind.Remote: if RemoteKeystoreFlag.DynamicKeystore in validator.data.flags: let keystore = keystoresTable.getOrDefault(validator.pubkey) if keystore.isSome(): # Just update validator's `data` field with new data from keystore. validator.data = keystore.get() elif validator.data.remotes[0].url == HttpHostUri(web3signerUrl.url): # The "dynamic" keystores are guaratneed to not be distributed # so they have a single remote. This code ensures that we are # deleting all previous dynamically obtained keystores which # were associated with a particular Web3Signer when the same # signer no longer serves them. deleteValidators.add(validator.pubkey) for pubkey in deleteValidators: pool[].removeValidator(pubkey) # Adding new dynamic keystores. for keystore in keystores.items(): let res = pool[].getValidator(keystore.pubkey) if res.isSome(): let validator = res.get() if validator.kind != ValidatorKind.Remote or RemoteKeystoreFlag.DynamicKeystore notin validator.data.flags: warn "Attempt to replace local validator with dynamic remote validator", pubkey = validator.pubkey, validator = shortLog(validator), remote_signer = $keystore.remotes, local_validator_kind = validator.kind else: addProc(keystore) proc signWithDistributedKey(v: AttachedValidator, request: Web3SignerRequest): Future[SignatureResult] {.async: (raises: [CancelledError]).} = doAssert v.data.threshold <= uint32(v.clients.len) let deadline = sleepAsync(WEB3_SIGNER_DELAY_TOLERANCE) signatureReqs = mapIt(v.clients, it[0].signData(it[1].pubkey, deadline, WEB3_SIGNER_ATTEMPTS_COUNT, request)) await allFutures(signatureReqs) if not(deadline.finished()): await cancelAndWait(deadline) var shares: seq[SignatureShare] var neededShares = v.data.threshold for i, req in signatureReqs: template shareInfo: untyped = v.clients[i][1] if req.completed() and req.value().isOk: shares.add req.value.get.toSignatureShare(shareInfo.id) neededShares = neededShares - 1 else: warn "Failed to obtain signature from remote signer", pubkey = shareInfo.pubkey, signerUrl = $(v.clients[i][0].address), reason = req.read.error.message, kind = req.read.error.kind if neededShares == 0: let recovered = shares.recoverSignature() return SignatureResult.ok recovered.toValidatorSig SignatureResult.err "Not enough shares to recover the signature" proc signWithSingleKey(v: AttachedValidator, request: Web3SignerRequest): Future[SignatureResult] {.async: (raises: [CancelledError]).} = doAssert v.clients.len == 1 let deadline = sleepAsync(WEB3_SIGNER_DELAY_TOLERANCE) (client, info) = v.clients[0] res = await client.signData( info.pubkey, deadline, WEB3_SIGNER_ATTEMPTS_COUNT, request) if not(deadline.finished()): await cancelAndWait(deadline) if res.isErr(): SignatureResult.err(res.error.message) else: SignatureResult.ok(res.get().toValidatorSig()) proc signData(v: AttachedValidator, request: Web3SignerRequest): Future[SignatureResult] {.async: (raises: [CancelledError], raw: true).} = doAssert v.kind == ValidatorKind.Remote debug "Signing request with remote signer", validator = shortLog(v), kind = request.kind if v.clients.len == 1: v.signWithSingleKey(request) else: v.signWithDistributedKey(request) # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/phase0/validator.md#signature proc getBlockSignature*(v: AttachedValidator, fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot, block_root: Eth2Digest, blck: ForkedBeaconBlock | ForkedBlindedBeaconBlock | ForkedMaybeBlindedBeaconBlock | deneb_mev.BlindedBeaconBlock | electra_mev.BlindedBeaconBlock ): Future[SignatureResult] {.async: (raises: [CancelledError]).} = type SomeBlockBody = capella.BeaconBlockBody | deneb.BeaconBlockBody | deneb_mev.BlindedBeaconBlockBody | electra.BeaconBlockBody | electra_mev.BlindedBeaconBlockBody template blockPropertiesProofs(blockBody: SomeBlockBody, forkIndexField: untyped): seq[Web3SignerMerkleProof] = var proofs: seq[Web3SignerMerkleProof] for prop in v.data.provenBlockProperties: if prop.forkIndexField.isSome: let idx = prop.forkIndexField.get proofRes = build_proof(blockBody, idx) if proofRes.isErr: return err proofRes.error proofs.add Web3SignerMerkleProof( index: idx, proof: proofRes.get) proofs case v.kind of ValidatorKind.Local: SignatureResult.ok( get_block_signature( fork, genesis_validators_root, slot, block_root, v.data.privateKey).toValidatorSig()) of ValidatorKind.Remote: let web3signerRequest = when blck is ForkedBlindedBeaconBlock: case blck.kind of ConsensusFork.Phase0 .. ConsensusFork.Capella: return SignatureResult.err("Invalid blinded beacon block fork") of ConsensusFork.Deneb: case v.data.remoteType of RemoteSignerType.Web3Signer: Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Deneb, data: blck.denebData.toBeaconBlockHeader)) of RemoteSignerType.VerifyingWeb3Signer: let proofs = blockPropertiesProofs( blck.denebData.body, denebIndex) Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Deneb, data: blck.denebData.toBeaconBlockHeader), proofs) of ConsensusFork.Electra: case v.data.remoteType of RemoteSignerType.Web3Signer: Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Electra, data: blck.electraData.toBeaconBlockHeader)) of RemoteSignerType.VerifyingWeb3Signer: let proofs = blockPropertiesProofs( blck.electraData.body, electraIndex) Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Electra, data: blck.electraData.toBeaconBlockHeader), proofs) elif blck is deneb_mev.BlindedBeaconBlock: case v.data.remoteType of RemoteSignerType.Web3Signer: Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Deneb, data: blck.toBeaconBlockHeader)) of RemoteSignerType.VerifyingWeb3Signer: let proofs = blockPropertiesProofs( blck.body, denebIndex) Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Deneb, data: blck.toBeaconBlockHeader), proofs) elif blck is electra_mev.BlindedBeaconBlock: case v.data.remoteType of RemoteSignerType.Web3Signer: Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Electra, data: blck.toBeaconBlockHeader)) of RemoteSignerType.VerifyingWeb3Signer: let proofs = blockPropertiesProofs( blck.body, electraIndex) Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Electra, data: blck.toBeaconBlockHeader), proofs) elif blck is ForkedMaybeBlindedBeaconBlock: withForkyMaybeBlindedBlck(blck): # TODO why isn't this a case statement when consensusFork < ConsensusFork.Capella: return SignatureResult.err("Invalid beacon block fork") elif consensusFork == ConsensusFork.Capella: when isBlinded: return SignatureResult.err("Invalid blinded beacon block fork") else: case v.data.remoteType of RemoteSignerType.Web3Signer: Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Capella, data: forkyMaybeBlindedBlck.toBeaconBlockHeader)) of RemoteSignerType.VerifyingWeb3Signer: let proofs = blockPropertiesProofs(forkyMaybeBlindedBlck.body, capellaIndex) Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Capella, data: forkyMaybeBlindedBlck.toBeaconBlockHeader), proofs) elif consensusFork == ConsensusFork.Deneb: when isBlinded: case v.data.remoteType of RemoteSignerType.Web3Signer: Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Deneb, data: forkyMaybeBlindedBlck.toBeaconBlockHeader)) of RemoteSignerType.VerifyingWeb3Signer: let proofs = blockPropertiesProofs(forkyMaybeBlindedBlck.body, denebIndex) Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Deneb, data: forkyMaybeBlindedBlck.toBeaconBlockHeader), proofs) else: case v.data.remoteType of RemoteSignerType.Web3Signer: Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Deneb, data: forkyMaybeBlindedBlck.`block`.toBeaconBlockHeader)) of RemoteSignerType.VerifyingWeb3Signer: let proofs = blockPropertiesProofs(forkyMaybeBlindedBlck.`block`.body, denebIndex) Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Deneb, data: forkyMaybeBlindedBlck.`block`.toBeaconBlockHeader), proofs) elif consensusFork == ConsensusFork.Electra: when isBlinded: case v.data.remoteType of RemoteSignerType.Web3Signer: Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Electra, data: forkyMaybeBlindedBlck.toBeaconBlockHeader)) of RemoteSignerType.VerifyingWeb3Signer: let proofs = blockPropertiesProofs(forkyMaybeBlindedBlck.body, electraIndex) Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Electra, data: forkyMaybeBlindedBlck.toBeaconBlockHeader), proofs) else: case v.data.remoteType of RemoteSignerType.Web3Signer: Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Electra, data: forkyMaybeBlindedBlck.`block`.toBeaconBlockHeader)) of RemoteSignerType.VerifyingWeb3Signer: let proofs = blockPropertiesProofs(forkyMaybeBlindedBlck.`block`.body, electraIndex) Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Electra, data: forkyMaybeBlindedBlck.`block`.toBeaconBlockHeader), proofs) else: case blck.kind of ConsensusFork.Phase0 .. ConsensusFork.Bellatrix: return SignatureResult.err("Invalid beacon block fork") of ConsensusFork.Capella: case v.data.remoteType of RemoteSignerType.Web3Signer: Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Capella, data: blck.capellaData.toBeaconBlockHeader)) of RemoteSignerType.VerifyingWeb3Signer: let proofs = blockPropertiesProofs( blck.capellaData.body, capellaIndex) Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Capella, data: blck.capellaData.toBeaconBlockHeader), proofs) of ConsensusFork.Deneb: case v.data.remoteType of RemoteSignerType.Web3Signer: Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Deneb, data: blck.denebData.toBeaconBlockHeader)) of RemoteSignerType.VerifyingWeb3Signer: let proofs = blockPropertiesProofs( blck.denebData.body, denebIndex) Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Deneb, data: blck.denebData.toBeaconBlockHeader), proofs) of ConsensusFork.Electra: case v.data.remoteType of RemoteSignerType.Web3Signer: Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Electra, data: blck.electraData.toBeaconBlockHeader)) of RemoteSignerType.VerifyingWeb3Signer: let proofs = blockPropertiesProofs( blck.electraData.body, electraIndex) Web3SignerRequest.init(fork, genesis_validators_root, Web3SignerForkedBeaconBlock(kind: ConsensusFork.Electra, data: blck.electraData.toBeaconBlockHeader), proofs) await v.signData(web3signerRequest) # https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/validator.md#aggregate-signature proc getAttestationSignature*(v: AttachedValidator, fork: Fork, genesis_validators_root: Eth2Digest, data: AttestationData ): Future[SignatureResult] {.async: (raises: [CancelledError]).} = case v.kind of ValidatorKind.Local: SignatureResult.ok( get_attestation_signature( fork, genesis_validators_root, data, v.data.privateKey).toValidatorSig()) of ValidatorKind.Remote: let request = Web3SignerRequest.init(fork, genesis_validators_root, data) await v.signData(request) # https://github.com/ethereum/consensus-specs/blob/v1.4.0/specs/phase0/validator.md#broadcast-aggregate proc getAggregateAndProofSignature*(v: AttachedValidator, fork: Fork, genesis_validators_root: Eth2Digest, aggregate_and_proof: phase0.AggregateAndProof | electra.AggregateAndProof, ): Future[SignatureResult] {.async: (raises: [CancelledError]).} = case v.kind of ValidatorKind.Local: SignatureResult.ok( get_aggregate_and_proof_signature( fork, genesis_validators_root, aggregate_and_proof, v.data.privateKey).toValidatorSig() ) of ValidatorKind.Remote: let request = Web3SignerRequest.init( fork, genesis_validators_root, aggregate_and_proof) await v.signData(request) # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/altair/validator.md#prepare-sync-committee-message proc getSyncCommitteeMessage*(v: AttachedValidator, fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot, beacon_block_root: Eth2Digest ): Future[SyncCommitteeMessageResult] {.async: (raises: [CancelledError]).} = let signature = case v.kind of ValidatorKind.Local: SignatureResult.ok(get_sync_committee_message_signature( fork, genesis_validators_root, slot, beacon_block_root, v.data.privateKey).toValidatorSig()) of ValidatorKind.Remote: let request = Web3SignerRequest.init( fork, genesis_validators_root, beacon_block_root, slot) await v.signData(request) if signature.isErr: return err("Failed to obtain signature") ok( SyncCommitteeMessage( slot: slot, beacon_block_root: beacon_block_root, validator_index: uint64(v.index.get()), signature: signature.get() ) ) # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/altair/validator.md#aggregation-selection proc getSyncCommitteeSelectionProof*(v: AttachedValidator, fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot, subcommittee_index: SyncSubcommitteeIndex ): Future[SignatureResult] {.async: (raises: [CancelledError]).} = case v.kind of ValidatorKind.Local: SignatureResult.ok(get_sync_committee_selection_proof( fork, genesis_validators_root, slot, subcommittee_index, v.data.privateKey).toValidatorSig()) of ValidatorKind.Remote: let request = Web3SignerRequest.init( fork, genesis_validators_root, SyncAggregatorSelectionData( slot: slot, subcommittee_index: uint64 subcommittee_index) ) await v.signData(request) # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/altair/validator.md#broadcast-sync-committee-contribution proc getContributionAndProofSignature*(v: AttachedValidator, fork: Fork, genesis_validators_root: Eth2Digest, contribution_and_proof: ContributionAndProof ): Future[SignatureResult] {.async: (raises: [CancelledError]).} = case v.kind of ValidatorKind.Local: SignatureResult.ok(get_contribution_and_proof_signature( fork, genesis_validators_root, contribution_and_proof, v.data.privateKey).toValidatorSig()) of ValidatorKind.Remote: let request = Web3SignerRequest.init( fork, genesis_validators_root, contribution_and_proof) await v.signData(request) # https://github.com/ethereum/consensus-specs/blob/v1.5.0-alpha.8/specs/phase0/validator.md#randao-reveal proc getEpochSignature*(v: AttachedValidator, fork: Fork, genesis_validators_root: Eth2Digest, epoch: Epoch ): Future[SignatureResult] {.async: (raises: [CancelledError]).} = 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( fork, genesis_validators_root, epoch, v.data.privateKey).toValidatorSig()) of ValidatorKind.Remote: let request = Web3SignerRequest.init( 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/specs/phase0/validator.md#aggregation-selection proc getSlotSignature*(v: AttachedValidator, fork: Fork, genesis_validators_root: Eth2Digest, slot: Slot ): Future[SignatureResult] {.async: (raises: [CancelledError]).} = if v.slotSignature.isSome and v.slotSignature.get.slot == slot: return SignatureResult.ok(v.slotSignature.get.signature) let signature = case v.kind of ValidatorKind.Local: SignatureResult.ok(get_slot_signature( fork, genesis_validators_root, slot, v.data.privateKey).toValidatorSig()) of ValidatorKind.Remote: let request = Web3SignerRequest.init(fork, genesis_validators_root, slot) await v.signData(request) if signature.isErr: return signature v.slotSignature = Opt.some((slot, signature.get)) return signature proc getValidatorExitSignature*(v: AttachedValidator, fork: Fork, genesis_validators_root: Eth2Digest, voluntary_exit: VoluntaryExit ): Future[SignatureResult] {.async: (raises: [CancelledError]).} = case v.kind of ValidatorKind.Local: SignatureResult.ok(get_voluntary_exit_signature( fork, genesis_validators_root, voluntary_exit, v.data.privateKey).toValidatorSig()) of ValidatorKind.Remote: let request = Web3SignerRequest.init(fork, genesis_validators_root, voluntary_exit) await v.signData(request) proc getDepositMessageSignature*(v: AttachedValidator, version: Version, deposit_message: DepositMessage ): Future[SignatureResult] {.async: (raises: [CancelledError]).} = case v.kind of ValidatorKind.Local: SignatureResult.ok(get_deposit_signature( deposit_message, version, v.data.privateKey).toValidatorSig()) of ValidatorKind.Remote: let request = Web3SignerRequest.init(version, deposit_message) await v.signData(request) # https://github.com/ethereum/builder-specs/blob/v0.4.0/specs/bellatrix/builder.md#signing proc getBuilderSignature*(v: AttachedValidator, fork: Fork, validatorRegistration: ValidatorRegistrationV1): Future[SignatureResult] {.async: (raises: [CancelledError]).} = case v.kind of ValidatorKind.Local: SignatureResult.ok(get_builder_signature( fork, validatorRegistration, v.data.privateKey).toValidatorSig()) of ValidatorKind.Remote: let request = Web3SignerRequest.init( fork, ZERO_HASH, validatorRegistration) await v.signData(request)