diff --git a/beacon_chain/beacon_node.nim b/beacon_chain/beacon_node.nim index 34f23eb004..43912490fe 100644 --- a/beacon_chain/beacon_node.nim +++ b/beacon_chain/beacon_node.nim @@ -10,7 +10,7 @@ # Everything needed to run a full Beacon Node import - std/osproc, + std/[osproc, sets], # Nimble packages chronos, presto, bearssl/rand, @@ -32,7 +32,7 @@ import ./rpc/state_ttl_cache export - osproc, chronos, presto, action_tracker, + osproc, sets, chronos, presto, action_tracker, beacon_clock, beacon_chain_db, conf, light_client, attestation_pool, sync_committee_msg_pool, validator_change_pool, eth2_network, el_manager, branch_discovery, request_manager, sync_manager, @@ -80,6 +80,7 @@ type keymanagerHost*: ref KeymanagerHost keymanagerServer*: RestServerRef keystoreCache*: KeystoreCacheRef + keysFilter*: HashSet[ValidatorPubKey] eventBus*: EventBus vcProcess*: Process requestManager*: RequestManager diff --git a/beacon_chain/conf.nim b/beacon_chain/conf.nim index 3aa3d0dffa..c5c9d8775c 100644 --- a/beacon_chain/conf.nim +++ b/beacon_chain/conf.nim @@ -187,6 +187,10 @@ type name: "web3-signer-update-interval" defaultValue: 3600 .}: Natural + web3SignersKeyFilter* {. + desc: "Validator keys which will be used with remote Web3Signer" + name: "web3-signer-key" .}: seq[ValidatorPubKey] + secretsDirFlag* {. desc: "A directory containing validator keystore passwords" name: "secrets-dir" .}: Option[InputDir] @@ -938,6 +942,10 @@ type desc: "Remote Web3Signer URL that will be used as a source of validators" name: "web3-signer-url" .}: seq[Uri] + web3SignersKeyFilter* {. + desc: "Validator keys which will be used with remote Web3Signer" + name: "web3-signer-key" .}: seq[ValidatorPubKey] + secretsDirFlag* {. desc: "A directory containing validator keystore passwords" name: "secrets-dir" .}: Option[InputDir] diff --git a/beacon_chain/nimbus_beacon_node.nim b/beacon_chain/nimbus_beacon_node.nim index e6c6ad21bd..3f90297148 100644 --- a/beacon_chain/nimbus_beacon_node.nim +++ b/beacon_chain/nimbus_beacon_node.nim @@ -890,7 +890,8 @@ proc init*(T: type BeaconNode, beaconClock: beaconClock, validatorMonitor: validatorMonitor, stateTtlCache: stateTtlCache, - dynamicFeeRecipientsStore: newClone(DynamicFeeRecipientsStore.init())) + dynamicFeeRecipientsStore: newClone(DynamicFeeRecipientsStore.init()), + keysFilter: toHashSet(config.web3SignersKeyFilter)) node.initLightClient( rng, cfg, dag.forkDigests, getBeaconTime, dag.genesis_validators_root) diff --git a/beacon_chain/nimbus_validator_client.nim b/beacon_chain/nimbus_validator_client.nim index a81cd054ef..e3542051f8 100644 --- a/beacon_chain/nimbus_validator_client.nim +++ b/beacon_chain/nimbus_validator_client.nim @@ -101,12 +101,29 @@ proc initGenesis(vc: ValidatorClientRef): Future[RestGenesis] {.async.} = dec(counter) return melem -proc addValidatorsFromWeb3Signer(vc: ValidatorClientRef, web3signerUrl: Web3SignerUrl) {.async.} = - let res = await queryValidatorsSource(web3signerUrl) - if res.isOk(): - let dynamicKeystores = res.get() - for keystore in dynamicKeystores: - vc.addValidator(keystore) +proc addValidatorsFromWeb3Signer(vc: ValidatorClientRef, + web3signerUrl: Web3SignerUrl) {.async.} = + let keystores = + try: + let res = await queryValidatorsSource(web3signerUrl) + if res.isErr(): + # Error is already reported via log warning. + default(seq[KeystoreData]) + else: + res.get() + except CatchableError as exc: + warn "Unexpected error happens while polling validator's source", + error = $exc.name, reason = $exc.msg + default(seq[KeystoreData]) + + proc addValidatorProc(data: KeystoreData) = + vc.addValidator(data) + + debug "Web3Signer has been polled for validators", + keystores_found = len(keystores), + web3signer_url = web3signerUrl.url + vc.attachedValidators.updateDynamicValidators( + web3signerUrl, keystores, vc.keysFilter, addValidatorProc) proc initValidators(vc: ValidatorClientRef): Future[bool] {.async.} = info "Loading validators", validatorsDir = vc.config.validatorsDir() @@ -286,6 +303,7 @@ proc new*(T: type ValidatorClientRef, doppelExit: newAsyncEvent(), indicesAvailable: newAsyncEvent(), dynamicFeeRecipientsStore: newClone(DynamicFeeRecipientsStore.init()), + keysFilter: toHashSet(config.web3SignersKeyFilter), sigintHandleFut: waitSignal(SIGINT), sigtermHandleFut: waitSignal(SIGTERM), keystoreCache: KeystoreCacheRef.init() @@ -303,6 +321,7 @@ proc new*(T: type ValidatorClientRef, indicesAvailable: newAsyncEvent(), doppelExit: newAsyncEvent(), dynamicFeeRecipientsStore: newClone(DynamicFeeRecipientsStore.init()), + keysFilter: toHashSet(config.web3SignersKeyFilter), sigintHandleFut: newFuture[void]("sigint_placeholder"), sigtermHandleFut: newFuture[void]("sigterm_placeholder"), keystoreCache: KeystoreCacheRef.init() diff --git a/beacon_chain/validator_client/common.nim b/beacon_chain/validator_client/common.nim index 9713020855..5fd373897f 100644 --- a/beacon_chain/validator_client/common.nim +++ b/beacon_chain/validator_client/common.nim @@ -235,6 +235,7 @@ type rootsSeen*: Table[Eth2Digest, Slot] processingDelay*: Opt[Duration] finalizedEpoch*: Opt[Epoch] + keysFilter*: HashSet[ValidatorPubKey] rng*: ref HmacDrbgContext ApiStrategyKind* {.pure.} = enum @@ -906,24 +907,6 @@ proc getSubcommitteeIndex*(index: IndexInSyncCommittee): SyncSubcommitteeIndex = proc currentSlot*(vc: ValidatorClientRef): Slot = vc.beaconClock.now().slotOrZero() -proc addValidator*(vc: ValidatorClientRef, keystore: KeystoreData) = - let - withdrawalAddress = - if vc.keymanagerHost.isNil: - Opt.none Eth1Address - else: - vc.keymanagerHost[].getValidatorWithdrawalAddress(keystore.pubkey) - perValidatorDefaultFeeRecipient = getPerValidatorDefaultFeeRecipient( - vc.config.defaultFeeRecipient, withdrawalAddress) - feeRecipient = vc.config.validatorsDir.getSuggestedFeeRecipient( - keystore.pubkey, perValidatorDefaultFeeRecipient).valueOr( - perValidatorDefaultFeeRecipient) - gasLimit = vc.config.validatorsDir.getSuggestedGasLimit( - keystore.pubkey, vc.config.suggestedGasLimit).valueOr( - vc.config.suggestedGasLimit) - - discard vc.attachedValidators[].addValidator(keystore, feeRecipient, gasLimit) - proc removeValidator*(vc: ValidatorClientRef, pubkey: ValidatorPubKey) {.async.} = let validator = vc.attachedValidators[].getValidator(pubkey).valueOr: @@ -956,6 +939,19 @@ proc getGasLimit(vc: ValidatorClientRef, getGasLimit(vc.config.validatorsDir, vc.config.suggestedGasLimit, validator.pubkey) +proc addValidator*(vc: ValidatorClientRef, keystore: KeystoreData) = + let + currentEpoch = vc.beaconClock.now().slotOrZero().epoch() + feeRecipient = getFeeRecipient( + vc.dynamicFeeRecipientsStore, keystore.pubkey, Opt.none(ValidatorIndex), + Opt.none(Validator), vc.config.defaultFeeRecipient(), + vc.config.validatorsDir(), currentEpoch) + gasLimit = + getGasLimit(vc.config.validatorsDir, vc.config.suggestedGasLimit, + keystore.pubkey) + + discard vc.attachedValidators[].addValidator(keystore, feeRecipient, gasLimit) + proc prepareProposersList*(vc: ValidatorClientRef, epoch: Epoch): seq[PrepareBeaconProposer] = var res: seq[PrepareBeaconProposer] diff --git a/beacon_chain/validator_client/duties_service.nim b/beacon_chain/validator_client/duties_service.nim index 1214302a36..4c042ff0b3 100644 --- a/beacon_chain/validator_client/duties_service.nim +++ b/beacon_chain/validator_client/duties_service.nim @@ -655,9 +655,9 @@ proc dynamicValidatorsLoop*(service: DutiesServiceRef, debug "Web3Signer has been polled for validators", keystores_found = len(keystores), web3signer_url = web3signerUrl.url - vc.attachedValidators.updateDynamicValidators(web3signerUrl, - keystores, - addValidatorProc) + vc.attachedValidators.updateDynamicValidators( + web3signerUrl, keystores, vc.keysFilter, + addValidatorProc) seconds(intervalInSeconds) else: seconds(5) diff --git a/beacon_chain/validators/beacon_validators.nim b/beacon_chain/validators/beacon_validators.nim index 268e84c60c..fef0a8caa9 100644 --- a/beacon_chain/validators/beacon_validators.nim +++ b/beacon_chain/validators/beacon_validators.nim @@ -121,22 +121,22 @@ proc addValidatorsFromWeb3Signer( (await queryValidatorsSource(web3signerUrl)).valueOr( default(seq[KeystoreData])) - for keystore in dynamicStores: + proc addValidatorProc(keystore: KeystoreData) = let - data = - withState(node.dag.headState): - getValidator(forkyState.data.validators.asSeq(), keystore.pubkey) - index = - if data.isSome(): - Opt.some(data.get().index) - else: - Opt.none(ValidatorIndex) + epoch = node.currentSlot().epoch + index = Opt.none(ValidatorIndex) feeRecipient = node.consensusManager[].getFeeRecipient(keystore.pubkey, index, epoch) - gasLimit = node.consensusManager[].getGasLimit(keystore.pubkey) - v = node.attachedValidators[].addValidator(keystore, feeRecipient, - gasLimit) - v.updateValidator(data) + gasLimit = + node.consensusManager[].getGasLimit(keystore.pubkey) + discard node.attachedValidators[].addValidator(keystore, feeRecipient, + gasLimit) + + debug "Validators source has been polled for validators", + keystores_found = len(dynamicStores), + web3signer_url = web3signerUrl.url + node.attachedValidators.updateDynamicValidators( + web3signerUrl, dynamicStores, node.keysFilter, addValidatorProc) proc addValidators*(node: BeaconNode) {.async: (raises: [CancelledError]).} = info "Loading validators", validatorsDir = node.config.validatorsDir(), diff --git a/beacon_chain/validators/validator_pool.nim b/beacon_chain/validators/validator_pool.nim index a21d9db897..6bb27ec750 100644 --- a/beacon_chain/validators/validator_pool.nim +++ b/beacon_chain/validators/validator_pool.nim @@ -8,7 +8,7 @@ {.push raises: [].} import - std/[tables, json, streams, sequtils, uri], + std/[tables, sets, json, streams, sequtils, uri], chronos, chronicles, metrics, json_serialization/std/net, presto/client, @@ -392,13 +392,15 @@ func triggersDoppelganger*( proc updateDynamicValidators*(pool: ref ValidatorPool, web3signerUrl: Web3SignerUrl, keystores: openArray[KeystoreData], + keysFilter: HashSet[ValidatorPubKey], addProc: AddValidatorProc) = var keystoresTable: Table[ValidatorPubKey, Opt[KeystoreData]] deleteValidators: seq[ValidatorPubKey] for keystore in keystores: - keystoresTable[keystore.pubkey] = Opt.some(keystore) + if (len(keysFilter) == 0) or (keystore.pubkey in keysFilter): + 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 @@ -422,8 +424,10 @@ proc updateDynamicValidators*(pool: ref ValidatorPool, pool[].removeValidator(pubkey) # Adding new dynamic keystores. - for keystore in keystores.items(): - let res = pool[].getValidator(keystore.pubkey) + for value in keystoresTable.values(): + let + keystore = value.get() + res = pool[].getValidator(keystore.pubkey) if res.isSome(): let validator = res.get() if validator.kind != ValidatorKind.Remote or diff --git a/docs/the_nimbus_book/src/options.md b/docs/the_nimbus_book/src/options.md index c6e46fe5f1..fea3bf2b66 100644 --- a/docs/the_nimbus_book/src/options.md +++ b/docs/the_nimbus_book/src/options.md @@ -38,6 +38,7 @@ The following options are available: verifying Web3Signer (for example ".execution_payload.fee_recipient"). --web3-signer-url Remote Web3Signer URL that will be used as a source of validators. --web3-signer-update-interval Number of seconds between validator list updates [=3600]. + --web3-signer-key Validator keys which will be used with remote Web3Signer. --secrets-dir A directory containing validator keystore passwords. --wallets-dir A directory containing wallet files. --web3-url One or more execution layer Engine API URLs. diff --git a/tests/test_validator_pool.nim b/tests/test_validator_pool.nim index a79c8dca8c..2c89c61f9a 100644 --- a/tests/test_validator_pool.nim +++ b/tests/test_validator_pool.nim @@ -221,15 +221,19 @@ suite "Validator pool": check cmp(value, sortedExpected[index]) == 0 var pool = (ref ValidatorPool)() - discard pool[].addValidator(createLocal(createPubKey(1)), fee, gas) - discard pool[].addValidator(createRemote(createPubKey(2)), fee, gas) - discard pool[].addValidator(createDynamic(remoteSignerUrl.url, createPubKey(3)), fee, gas) + discard pool[].addValidator( + createLocal(createPubKey(1)), fee, gas) + discard pool[].addValidator( + createRemote(createPubKey(2)), fee, gas) + discard pool[].addValidator( + createDynamic(remoteSignerUrl.url, createPubKey(3)), fee, gas) proc addValidator(data: KeystoreData) {.gcsafe.} = discard pool[].addValidator(data, fee, gas) # Adding new dynamic keystores. block: + var keysFilter: HashSet[ValidatorPubKey] let expected = [ createLocal(createPubKey(1)), @@ -243,11 +247,13 @@ suite "Validator pool": createDynamic(remoteSignerUrl.url, createPubKey(4)), createDynamic(remoteSignerUrl.url, createPubKey(5)) ] - pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator) + pool.updateDynamicValidators( + remoteSignerUrl, keystores, keysFilter, addValidator) pool[].checkPool(expected) # Removing dynamic keystores. block: + var keysFilter: HashSet[ValidatorPubKey] let expected = [ createLocal(createPubKey(1)), @@ -257,11 +263,13 @@ suite "Validator pool": keystores = [ createDynamic(remoteSignerUrl.url, createPubKey(3)), ] - pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator) + pool.updateDynamicValidators( + remoteSignerUrl, keystores, keysFilter, addValidator) pool[].checkPool(expected) # Adding and removing keystores at same time. block: + var keysFilter: HashSet[ValidatorPubKey] let expected = [ createLocal(createPubKey(1)), @@ -273,11 +281,13 @@ suite "Validator pool": createDynamic(remoteSignerUrl.url, createPubKey(4)), createDynamic(remoteSignerUrl.url, createPubKey(5)) ] - pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator) + pool.updateDynamicValidators( + remoteSignerUrl, keystores, keysFilter, addValidator) pool[].checkPool(expected) # Adding dynamic keystores with keys which are static. block: + var keysFilter: HashSet[ValidatorPubKey] let expected = [ createLocal(createPubKey(1)), @@ -289,16 +299,42 @@ suite "Validator pool": createDynamic(remoteSignerUrl.url, createPubKey(2)), createDynamic(remoteSignerUrl.url, createPubKey(3)), ] - pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator) + pool.updateDynamicValidators( + remoteSignerUrl, keystores, keysFilter, addValidator) pool[].checkPool(expected) # Empty response block: + var keysFilter: HashSet[ValidatorPubKey] let expected = [ createLocal(createPubKey(1)), createRemote(createPubKey(2)) ] var keystores: seq[KeystoreData] - pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator) + pool.updateDynamicValidators( + remoteSignerUrl, keystores, keysFilter, addValidator) + pool[].checkPool(expected) + + # Key filters test + block: + var keysFilter: HashSet[ValidatorPubKey] + keysFilter.incl(createPubKey(4)) + keysFilter.incl(createPubKey(5)) + let + expected = [ + createLocal(createPubKey(1)), + createRemote(createPubKey(2)), + createDynamic(remoteSignerUrl.url, createPubKey(4)), + createDynamic(remoteSignerUrl.url, createPubKey(5)) + ] + keystores = [ + createDynamic(remoteSignerUrl.url, createPubKey(1)), + createDynamic(remoteSignerUrl.url, createPubKey(2)), + createDynamic(remoteSignerUrl.url, createPubKey(3)), + createDynamic(remoteSignerUrl.url, createPubKey(4)), + createDynamic(remoteSignerUrl.url, createPubKey(5)), + ] + pool.updateDynamicValidators( + remoteSignerUrl, keystores, keysFilter, addValidator) pool[].checkPool(expected)