Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VC/BN: Dynamic keystores filter #5820

Open
wants to merge 3 commits into
base: unstable
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions beacon_chain/beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# Everything needed to run a full Beacon Node

import
std/osproc,
std/[osproc, sets],

# Nimble packages
chronos, presto, bearssl/rand,
Expand All @@ -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,
Expand Down Expand Up @@ -80,6 +80,7 @@ type
keymanagerHost*: ref KeymanagerHost
keymanagerServer*: RestServerRef
keystoreCache*: KeystoreCacheRef
keysFilter*: HashSet[ValidatorPubKey]
eventBus*: EventBus
vcProcess*: Process
requestManager*: RequestManager
Expand Down
8 changes: 8 additions & 0 deletions beacon_chain/conf.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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]
Expand Down
3 changes: 2 additions & 1 deletion beacon_chain/nimbus_beacon_node.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
31 changes: 25 additions & 6 deletions beacon_chain/nimbus_validator_client.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down
32 changes: 14 additions & 18 deletions beacon_chain/validator_client/common.nim
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ type
rootsSeen*: Table[Eth2Digest, Slot]
processingDelay*: Opt[Duration]
finalizedEpoch*: Opt[Epoch]
keysFilter*: HashSet[ValidatorPubKey]
rng*: ref HmacDrbgContext

ApiStrategyKind* {.pure.} = enum
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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]
Expand Down
6 changes: 3 additions & 3 deletions beacon_chain/validator_client/duties_service.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
26 changes: 13 additions & 13 deletions beacon_chain/validators/beacon_validators.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
12 changes: 8 additions & 4 deletions beacon_chain/validators/validator_pool.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/the_nimbus_book/src/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
52 changes: 44 additions & 8 deletions tests/test_validator_pool.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand All @@ -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)),
Expand All @@ -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)),
Expand All @@ -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)),
Expand All @@ -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)
Loading