Dynamic validators set. (#5366)
* Initial commit. * Fix argument to be optional. * Adopt options.md.
This commit is contained in:
parent
1fbf371826
commit
757328372a
|
@ -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]
|
||||||
|
|
|
@ -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.} =
|
||||||
|
|
|
@ -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}",
|
||||||
|
|
|
@ -130,7 +130,7 @@ type
|
||||||
Local, Remote
|
Local, Remote
|
||||||
|
|
||||||
RemoteKeystoreFlag* {.pure.} = enum
|
RemoteKeystoreFlag* {.pure.} = enum
|
||||||
IgnoreSSLVerification
|
IgnoreSSLVerification, DynamicKeystore
|
||||||
|
|
||||||
HttpHostUri* = distinct Uri
|
HttpHostUri* = distinct Uri
|
||||||
|
|
||||||
|
|
|
@ -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] =
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue