Dynamic validators set. (#5366)

* Initial commit.

* Fix argument to be optional.

* Adopt options.md.
This commit is contained in:
Eugene Kabanov 2023-08-31 15:16:15 +03:00 committed by GitHub
parent 1fbf371826
commit 757328372a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 126 additions and 15 deletions

View File

@ -161,6 +161,10 @@ type
desc: "A directory containing validator keystores" desc: "A directory containing validator keystores"
name: "validators-dir" .}: Option[InputDir] name: "validators-dir" .}: Option[InputDir]
validatorsSource* {.
desc: "Remote Web3Signer URL that will be used as a source of validators"
name: "validators-source"}: Option[string]
secretsDirFlag* {. secretsDirFlag* {.
desc: "A directory containing validator keystore passwords" desc: "A directory containing validator keystore passwords"
name: "secrets-dir" .}: Option[InputDir] name: "secrets-dir" .}: Option[InputDir]
@ -875,6 +879,10 @@ type
desc: "A directory containing validator keystores" desc: "A directory containing validator keystores"
name: "validators-dir" .}: Option[InputDir] name: "validators-dir" .}: Option[InputDir]
validatorsSource* {.
desc: "Remote Web3Signer URL that will be used as a source of validators"
name: "validators-source"}: Option[string]
secretsDirFlag* {. secretsDirFlag* {.
desc: "A directory containing validator keystore passwords" desc: "A directory containing validator keystore passwords"
name: "secrets-dir" .}: Option[InputDir] name: "secrets-dir" .}: Option[InputDir]

View File

@ -90,6 +90,9 @@ proc initValidators(vc: ValidatorClientRef): Future[bool] {.async.} =
var duplicates: seq[ValidatorPubKey] var duplicates: seq[ValidatorPubKey]
for keystore in listLoadableKeystores(vc.config, vc.keystoreCache): for keystore in listLoadableKeystores(vc.config, vc.keystoreCache):
vc.addValidator(keystore) vc.addValidator(keystore)
let dynamicKeystores = await queryValidatorsSource(vc.config)
for keystore in dynamicKeystores:
vc.addValidator(keystore)
return true return true
proc initClock(vc: ValidatorClientRef): Future[BeaconClock] {.async.} = proc initClock(vc: ValidatorClientRef): Future[BeaconClock] {.async.} =

View File

@ -78,6 +78,11 @@ proc getKeys*(): RestResponse[Web3SignerKeysResponse] {.
meth: MethodGet, accept: "application/json" .} meth: MethodGet, accept: "application/json" .}
## https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Public-Key ## https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Public-Key
proc getKeysPlain*(): RestPlainResponse {.
rest, endpoint: "/api/v1/eth2/publicKeys",
meth: MethodGet, accept: "application/json" .}
## https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Public-Key
proc signDataPlain*(identifier: ValidatorPubKey, proc signDataPlain*(identifier: ValidatorPubKey,
body: Web3SignerRequest): RestPlainResponse {. body: Web3SignerRequest): RestPlainResponse {.
rest, endpoint: "/api/v1/eth2/sign/{identifier}", rest, endpoint: "/api/v1/eth2/sign/{identifier}",

View File

@ -130,7 +130,7 @@ type
Local, Remote Local, Remote
RemoteKeystoreFlag* {.pure.} = enum RemoteKeystoreFlag* {.pure.} = enum
IgnoreSSLVerification IgnoreSSLVerification, DynamicKeystore
HttpHostUri* = distinct Uri HttpHostUri* = distinct Uri

View File

@ -117,8 +117,8 @@ proc getValidator*(validators: auto,
proc addValidators*(node: BeaconNode) = proc addValidators*(node: BeaconNode) =
info "Loading validators", validatorsDir = node.config.validatorsDir(), info "Loading validators", validatorsDir = node.config.validatorsDir(),
keystore_cache_available = not(isNil(node.keystoreCache)) keystore_cache_available = not(isNil(node.keystoreCache))
let let epoch = node.currentSlot().epoch
epoch = node.currentSlot().epoch
for keystore in listLoadableKeystores(node.config, node.keystoreCache): for keystore in listLoadableKeystores(node.config, node.keystoreCache):
let let
data = withState(node.dag.headState): data = withState(node.dag.headState):
@ -132,7 +132,33 @@ proc addValidators*(node: BeaconNode) =
keystore.pubkey, index, epoch) keystore.pubkey, index, epoch)
gasLimit = node.consensusManager[].getGasLimit(keystore.pubkey) gasLimit = node.consensusManager[].getGasLimit(keystore.pubkey)
v = node.attachedValidators[].addValidator(keystore, feeRecipient, gasLimit) v = node.attachedValidators[].addValidator(keystore, feeRecipient,
gasLimit)
v.updateValidator(data)
let dynamicStores =
try:
waitFor(queryValidatorsSource(node.config))
except CatchableError as exc:
warn "Unexpected error happens while polling validator's source",
error = $exc.name, reason = $exc.msg
default(seq[KeystoreData])
for keystore in dynamicStores:
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)
feeRecipient =
node.consensusManager[].getFeeRecipient(keystore.pubkey, index, epoch)
gasLimit = node.consensusManager[].getGasLimit(keystore.pubkey)
v = node.attachedValidators[].addValidator(keystore, feeRecipient,
gasLimit)
v.updateValidator(data) v.updateValidator(data)
proc getValidator*(node: BeaconNode, idx: ValidatorIndex): Opt[AttachedValidator] = proc getValidator*(node: BeaconNode, idx: ValidatorIndex): Opt[AttachedValidator] =

View File

@ -627,6 +627,70 @@ proc existsKeystore(keystoreDir: string,
return true return true
false false
proc queryValidatorsSource*(config: AnyConf): Future[seq[KeystoreData]] {.
async.} =
var keystores: seq[KeystoreData]
if config.validatorsSource.isNone() or
len(config.validatorsSource.get()) == 0:
return keystores
let vsource = config.validatorsSource.get()
logScope:
validators_source = vsource
let
httpFlags: HttpClientFlags = {}
prestoFlags = {RestClientFlag.CommaSeparatedArray}
socketFlags = {SocketFlags.TcpNoDelay}
client =
block:
let res = RestClientRef.new(vsource, prestoFlags,
httpFlags, socketFlags = socketFlags)
if res.isErr():
# TODO keep trying in case of temporary network failure
warn "Unable to resolve validator's source distributed signer address"
return keystores
res.get()
keys =
try:
let response = await getKeysPlain(client)
if response.status != 200:
warn "Remote validator's source responded with error",
error = response.status
return keystores
let res = decodeBytes(Web3SignerKeysResponse, response.data,
response.contentType)
if res.isErr():
warn "Unable to obtain validator's source response",
reason = res.error
return keystores
res.get()
except RestError as exc:
warn "Unable to poll validator's source", reason = $exc.msg
return keystores
except CancelledError as exc:
debug "The polling of validator's source was interrupted"
raise exc
except CatchableError as exc:
warn "Unexpected error occured while polling validator's source",
error = $exc.name, reason = $exc.msg
return keystores
for pubkey in keys:
keystores.add(KeystoreData(
kind: KeystoreKind.Remote,
handle: FileLockHandle(opened: false),
pubkey: pubkey,
remotes: @[RemoteSignerInfo(
url: HttpHostUri(parseUri(vsource)),
pubkey: pubkey)],
flags: {RemoteKeystoreFlag.DynamicKeystore},
remoteType: RemoteSignerType.Web3Signer))
keystores
iterator listLoadableKeys*(validatorsDir, secretsDir: string, iterator listLoadableKeys*(validatorsDir, secretsDir: string,
keysMask: set[KeystoreKind]): CookedPubKey = keysMask: set[KeystoreKind]): CookedPubKey =
try: try:
@ -1244,8 +1308,8 @@ proc saveKeystore*(
proc importKeystore*(pool: var ValidatorPool, proc importKeystore*(pool: var ValidatorPool,
validatorsDir: string, validatorsDir: string,
keystore: RemoteKeystore): ImportResult[KeystoreData] keystore: RemoteKeystore): ImportResult[KeystoreData] {.
{.raises: [].} = raises: [].} =
let let
publicKey = keystore.pubkey publicKey = keystore.pubkey
keyName = publicKey.fsName keyName = publicKey.fsName
@ -1253,9 +1317,9 @@ proc importKeystore*(pool: var ValidatorPool,
# We check `publicKey`. # We check `publicKey`.
let cookedKey = publicKey.load().valueOr: let cookedKey = publicKey.load().valueOr:
return err( return err(
AddValidatorFailure.init(AddValidatorStatus.failed, AddValidatorFailure.init(AddValidatorStatus.failed,
"Invalid validator's public key")) "Invalid validator's public key"))
# We check `publicKey` in memory storage first. # We check `publicKey` in memory storage first.
if publicKey in pool: if publicKey in pool:

View File

@ -146,12 +146,16 @@ proc addRemoteValidator(pool: var ValidatorPool, keystore: KeystoreData,
activationEpoch: FAR_FUTURE_EPOCH, activationEpoch: FAR_FUTURE_EPOCH,
) )
pool.validators[v.pubkey] = v pool.validators[v.pubkey] = v
notice "Remote validator attached", if RemoteKeystoreFlag.DynamicKeystore in keystore.flags:
pubkey = v.pubkey, notice "Dynamic remote validator attached", pubkey = v.pubkey,
validator = shortLog(v), validator = shortLog(v), remote_signer = $keystore.remotes,
remote_signer = $keystore.remotes, initial_fee_recipient = feeRecipient.toHex(),
initial_fee_recipient = feeRecipient.toHex(), initial_gas_limit = gasLimit
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) validators.set(pool.count().int64)

View File

@ -33,6 +33,7 @@ The following options are available:
--network The Eth2 network to join [=mainnet]. --network The Eth2 network to join [=mainnet].
-d, --data-dir The directory where nimbus will store all blockchain data. -d, --data-dir The directory where nimbus will store all blockchain data.
--validators-dir A directory containing validator keystores. --validators-dir A directory containing validator keystores.
--validators-source Remote Web3Signer URL that will be used as a source of validators.
--secrets-dir A directory containing validator keystore passwords. --secrets-dir A directory containing validator keystore passwords.
--wallets-dir A directory containing wallet files. --wallets-dir A directory containing wallet files.
--web3-url One or more execution layer Engine API URLs. --web3-url One or more execution layer Engine API URLs.