Add the --verifying-web3-signer-url configuration option (#5504)

This commit is contained in:
zah 2023-10-13 15:42:00 +03:00 committed by GitHub
parent dc4d366a6d
commit 35bf03a3fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 119 additions and 49 deletions

View File

@ -127,6 +127,10 @@ type
Poll = "poll"
Event = "event"
Web3SignerUrl* = object
url*: Uri
provenBlockProperties*: seq[string] # empty if this is not a verifying Web3Signer
BeaconNodeConf* = object
configFile* {.
desc: "Loads the configuration from a TOML file"
@ -164,7 +168,15 @@ type
desc: "A directory containing validator keystores"
name: "validators-dir" .}: Option[InputDir]
web3signers* {.
verifyingWeb3Signers* {.
desc: "Remote Web3Signer URL that will be used as a source of validators"
name: "verifying-web3-signer-url" .}: seq[Uri]
provenBlockProperties* {.
desc: "The field path of a block property that will be sent for verification to the verifying Web3Signer (for example \".execution_payload.fee_recipient\")"
name: "proven-block-property" .}: seq[string]
web3Signers* {.
desc: "Remote Web3Signer URL that will be used as a source of validators"
name: "web3-signer-url" .}: seq[Uri]
@ -896,15 +908,23 @@ type
desc: "A directory containing validator keystores"
name: "validators-dir" .}: Option[InputDir]
web3signers* {.
verifyingWeb3Signers* {.
desc: "Remote Web3Signer URL that will be used as a source of validators"
name: "web3-signer-url" .}: seq[Uri]
name: "verifying-web3-signer-url" .}: seq[Uri]
provenBlockProperties* {.
desc: "The field path of a block property that will be sent for verification to the verifying Web3Signer (for example \".execution_payload.fee_recipient\")"
name: "proven-block-property" .}: seq[string]
web3signerUpdateInterval* {.
desc: "Number of seconds between validator list updates"
name: "web3-signer-update-interval"
defaultValue: 3600 .}: Natural
web3Signers* {.
desc: "Remote Web3Signer URL that will be used as a source of validators"
name: "web3-signer-url" .}: seq[Uri]
secretsDirFlag* {.
desc: "A directory containing validator keystore passwords"
name: "secrets-dir" .}: Option[InputDir]
@ -1287,6 +1307,14 @@ func runAsService*(config: BeaconNodeConf): bool =
else:
false
func web3SignerUrls*(conf: AnyConf): seq[Web3SignerUrl] =
for url in conf.web3signers:
result.add Web3SignerUrl(url: url)
for url in conf.verifyingWeb3signers:
result.add Web3SignerUrl(url: url,
provenBlockProperties: conf.provenBlockProperties)
template writeValue*(writer: var JsonWriter,
value: TypedInputFile|InputFile|InputDir|OutPath|OutDir|OutFile) =
writer.writeValue(string value)

View File

@ -1732,7 +1732,7 @@ proc run(node: BeaconNode) {.raises: [CatchableError].} =
waitFor node.updateGossipStatus(wallSlot)
for web3signerUrl in node.config.web3signers:
for web3signerUrl in node.config.web3SignerUrls:
# TODO
# The current strategy polls all remote signers independently
# from each other which may lead to some race conditions of

View File

@ -98,7 +98,7 @@ proc initGenesis(vc: ValidatorClientRef): Future[RestGenesis] {.async.} =
dec(counter)
return melem
proc addValidatorsFromWeb3Signer(vc: ValidatorClientRef, web3signerUrl: Uri) {.async.} =
proc addValidatorsFromWeb3Signer(vc: ValidatorClientRef, web3signerUrl: Web3SignerUrl) {.async.} =
let res = await queryValidatorsSource(web3signerUrl)
if res.isOk():
let dynamicKeystores = res.get()
@ -111,7 +111,7 @@ proc initValidators(vc: ValidatorClientRef): Future[bool] {.async.} =
vc.addValidator(keystore)
let web3signerValidatorsFuts = mapIt(
vc.config.web3signers,
vc.config.web3SignerUrls,
vc.addValidatorsFromWeb3Signer(it))
# We use `allFutures` because all failures are already reported as

View File

@ -725,6 +725,26 @@ template writeValue*(w: var JsonWriter,
value: Pbkdf2Salt|SimpleHexEncodedTypes|Aes128CtrIv) =
writeJsonHexString(w.stream, distinctBase value)
func parseProvenBlockProperty*(propertyPath: string): Result[ProvenProperty, string] =
if propertyPath == ".execution_payload.fee_recipient":
ok ProvenProperty(
path: propertyPath,
bellatrixIndex: some GeneralizedIndex(401),
capellaIndex: some GeneralizedIndex(401),
denebIndex: some GeneralizedIndex(801))
elif propertyPath == ".graffiti":
ok ProvenProperty(
path: propertyPath,
# TODO: graffiti is present since genesis, so the correct index in the early
# forks can be supplied here
bellatrixIndex: some GeneralizedIndex(18),
capellaIndex: some GeneralizedIndex(18),
denebIndex: some GeneralizedIndex(18))
else:
err("Keystores with proven properties different than " &
"`.execution_payload.fee_recipient` and `.graffiti` " &
"require a more recent version of Nimbus")
proc readValue*(reader: var JsonReader, value: var RemoteKeystore)
{.raises: [SerializationError, IOError].} =
var
@ -830,6 +850,8 @@ proc readValue*(reader: var JsonReader, value: var RemoteKeystore)
prop.capellaIndex = some GeneralizedIndex(401)
prop.denebIndex = some GeneralizedIndex(801)
elif prop.path == ".graffiti":
# TODO: graffiti is present since genesis, so the correct index in the early
# forks can be supplied here
prop.bellatrixIndex = some GeneralizedIndex(18)
prop.capellaIndex = some GeneralizedIndex(18)
prop.denebIndex = some GeneralizedIndex(18)

View File

@ -601,7 +601,7 @@ proc validatorIndexLoop(service: DutiesServiceRef) {.async.} =
await service.waitForNextSlot()
proc dynamicValidatorsLoop*(service: DutiesServiceRef,
web3signerUrl: Uri,
web3signerUrl: Web3SignerUrl,
intervalInSeconds: int) {.async.} =
let vc = service.client
doAssert(intervalInSeconds > 0)
@ -624,7 +624,7 @@ proc dynamicValidatorsLoop*(service: DutiesServiceRef,
let keystores = res.get()
debug "Web3Signer has been polled for validators",
keystores_found = len(keystores),
web3signer_url = web3signerUrl
web3signer_url = web3signerUrl.url
vc.attachedValidators.updateDynamicValidators(web3signerUrl,
keystores,
addValidatorProc)
@ -710,11 +710,12 @@ proc mainLoop(service: DutiesServiceRef) {.async.} =
nil
dynamicFuts =
if vc.config.web3signerUpdateInterval > 0:
mapIt(vc.config.web3signers,
mapIt(vc.config.web3SignerUrls,
service.dynamicValidatorsLoop(it, vc.config.web3signerUpdateInterval))
else:
debug "Dynamic validators update loop disabled"
@[]
web3SignerUrls = vc.config.web3SignerUrls
while true:
# This loop could look much more nicer/better, when
@ -746,7 +747,7 @@ proc mainLoop(service: DutiesServiceRef) {.async.} =
for i in 0 ..< dynamicFuts.len:
checkAndRestart(DynamicValidatorsLoop, dynamicFuts[i],
service.dynamicValidatorsLoop(
vc.config.web3signers[i],
web3SignerUrls[i],
vc.config.web3signerUpdateInterval))
false
except CancelledError:

View File

@ -115,7 +115,7 @@ proc getValidator*(validators: auto,
Opt.some ValidatorAndIndex(index: ValidatorIndex(idx),
validator: validators[idx])
proc addValidatorsFromWeb3Signer(node: BeaconNode, web3signerUrl: Uri, epoch: Epoch) {.async.} =
proc addValidatorsFromWeb3Signer(node: BeaconNode, web3signerUrl: Web3SignerUrl, epoch: Epoch) {.async.} =
let dynamicStores =
try:
let res = await queryValidatorsSource(web3signerUrl)
@ -173,7 +173,7 @@ proc addValidators*(node: BeaconNode) =
# user-visible warnings in `queryValidatorsSource`.
# We don't consider them fatal because the Web3Signer may be experiencing
# a temporary hiccup that will be resolved later.
waitFor allFutures(mapIt(node.config.web3signers,
waitFor allFutures(mapIt(node.config.web3SignerUrls,
node.addValidatorsFromWeb3Signer(it, epoch)))
except CatchableError as err:
# This should never happen because all errors are handled within
@ -184,7 +184,7 @@ proc addValidators*(node: BeaconNode) =
err = err.msg
proc pollForDynamicValidators*(node: BeaconNode,
web3signerUrl: Uri,
web3signerUrl: Web3SignerUrl,
intervalInSeconds: int) {.async.} =
if intervalInSeconds == 0:
return
@ -215,7 +215,7 @@ proc pollForDynamicValidators*(node: BeaconNode,
let keystores = res.get()
debug "Validators source has been polled for validators",
keystores_found = len(keystores),
web3signer_url = web3signerUrl
web3signer_url = web3signerUrl.url
node.attachedValidators.updateDynamicValidators(web3signerUrl,
keystores,
addValidatorProc)

View File

@ -8,7 +8,7 @@
{.push raises: [].}
import
std/[os, unicode],
std/[os, unicode, sequtils],
chronicles, chronos, json_serialization,
bearssl/rand,
serialization, blscurve, eth/common/eth_types, confutils,
@ -28,7 +28,7 @@ from std/wordwrap import wrapWords
from zxcvbn import passwordEntropy
export
keystore, validator_pool, crypto, rand
keystore, validator_pool, crypto, rand, Web3SignerUrl
when defined(windows):
import stew/[windows/acl]
@ -632,11 +632,11 @@ proc existsKeystore(keystoreDir: string,
return true
false
proc queryValidatorsSource*(web3signerUrl: Uri): Future[QueryResult] {.async.} =
proc queryValidatorsSource*(web3signerUrl: Web3SignerUrl): Future[QueryResult] {.async.} =
var keystores: seq[KeystoreData]
logScope:
web3signer_url = web3signerUrl
web3signer_url = web3signerUrl.url
let
httpFlags: HttpClientFlags = {}
@ -644,7 +644,7 @@ proc queryValidatorsSource*(web3signerUrl: Uri): Future[QueryResult] {.async.} =
socketFlags = {SocketFlags.TcpNoDelay}
client =
block:
let res = RestClientRef.new($web3signerUrl, prestoFlags,
let res = RestClientRef.new($web3signerUrl.url, prestoFlags,
httpFlags, socketFlags = socketFlags)
if res.isErr():
warn "Unable to resolve validator's source distributed signer " &
@ -679,16 +679,29 @@ proc queryValidatorsSource*(web3signerUrl: Uri): Future[QueryResult] {.async.} =
error = $exc.name, reason = $exc.msg
return QueryResult.err($exc.msg)
remoteType = if web3signerUrl.provenBlockProperties.len == 0:
RemoteSignerType.Web3Signer
else:
RemoteSignerType.VerifyingWeb3Signer
provenBlockProperties = mapIt(web3signerUrl.provenBlockProperties,
block:
parseProvenBlockProperty(it).valueOr:
return QueryResult.err(error))
for pubkey in keys:
keystores.add(KeystoreData(
kind: KeystoreKind.Remote,
handle: FileLockHandle(opened: false),
pubkey: pubkey,
remotes: @[RemoteSignerInfo(
url: HttpHostUri(web3signerUrl),
url: HttpHostUri(web3signerUrl.url),
pubkey: pubkey)],
flags: {RemoteKeystoreFlag.DynamicKeystore},
remoteType: RemoteSignerType.Web3Signer))
remoteType: remoteType))
if provenBlockProperties.len > 0:
keystores[^1].provenBlockProperties = provenBlockProperties
QueryResult.ok(keystores)

View File

@ -17,7 +17,7 @@ import
../spec/datatypes/[phase0, altair],
../spec/eth2_apis/[rest_types, eth2_rest_serialization,
rest_remote_signer_calls],
../filepath,
../filepath, ../conf,
./slashing_protection
export
@ -380,7 +380,7 @@ func triggersDoppelganger*(
v.isSome() and v[].triggersDoppelganger(epoch)
proc updateDynamicValidators*(pool: ref ValidatorPool,
web3signerUrl: Uri,
web3signerUrl: Web3SignerUrl,
keystores: openArray[KeystoreData],
addProc: AddValidatorProc) =
var
@ -400,7 +400,7 @@ proc updateDynamicValidators*(pool: ref ValidatorPool,
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):
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

View File

@ -33,6 +33,9 @@ The following options are available:
--network The Eth2 network to join [=mainnet].
-d, --data-dir The directory where nimbus will store all blockchain data.
--validators-dir A directory containing validator keystores.
--verifying-web3-signer-url Remote Web3Signer URL that will be used as a source of validators.
--proven-block-property The field path of a block property that will be sent for verification to the
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].
--secrets-dir A directory containing validator keystore passwords.

View File

@ -156,3 +156,5 @@ Since the generalized index of a particular field may change in a hard-fork, in
Nimbus automatically computes the generalized index depending on the currently active fork.
The remote signer is expected to verify the incoming Merkle proof through the standardized [is_valid_merkle_branch](https://github.com/ethereum/consensus-specs/blob/v1.4.0-beta.2/specs/phase0/beacon-chain.md#is_valid_merkle_branch) function by utilizing a similar automatic mapping mechanism for the generalized index.
You can instruct Nimbus to use the verifying Web3Signer protocol by either supplying the `--verifying-web3-signer` command-line option or by creating a remote keystore file in the format described above. You can use the command-line option `--proven-block-property` once or multiple times to enumerate the properties of the block for which Merkle proofs will be supplied.

View File

@ -31,7 +31,7 @@ func createDynamic(url: Uri, pubkey: ValidatorPubKey): KeystoreData =
flags: {RemoteKeystoreFlag.DynamicKeystore})
const
remoteSignerUrl = parseUri("http://nimbus.team/signer1")
remoteSignerUrl = Web3SignerUrl(url: parseUri("http://nimbus.team/signer1"))
func makeValidatorAndIndex(
index: ValidatorIndex, activation_epoch: Epoch): Opt[ValidatorAndIndex] =
@ -166,35 +166,36 @@ suite "Validator pool":
mapIt(["--web3-signer-url=http://" & $serverAddress], it))
except Exception as exc:
raiseAssert exc.msg
web3SignerUrls = config.web3SignerUrls
server.start()
try:
block:
testStage = 0
let res = await queryValidatorsSource(config.web3signers[0])
let res = await queryValidatorsSource(web3SignerUrls[0])
check:
res.isOk()
checkResponse(
res.get(),
[
createDynamic(remoteSignerUrl, createPubKey(1)),
createDynamic(remoteSignerUrl, createPubKey(2))
createDynamic(remoteSignerUrl.url, createPubKey(1)),
createDynamic(remoteSignerUrl.url, createPubKey(2))
])
block:
testStage = 1
let res = await queryValidatorsSource(config.web3signers[0])
let res = await queryValidatorsSource(web3SignerUrls[0])
check:
res.isOk()
checkResponse(res.get(), [createDynamic(remoteSignerUrl, createPubKey(1))])
checkResponse(res.get(), [createDynamic(remoteSignerUrl.url, createPubKey(1))])
block:
testStage = 2
let res = await queryValidatorsSource(config.web3signers[0])
let res = await queryValidatorsSource(web3SignerUrls[0])
check:
res.isOk()
len(res.get()) == 0
block:
testStage = 3
let res = await queryValidatorsSource(config.web3signers[0])
let res = await queryValidatorsSource(web3SignerUrls[0])
check:
res.isErr()
finally:
@ -221,7 +222,7 @@ suite "Validator pool":
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, createPubKey(3)), fee, gas)
discard pool[].addValidator(createDynamic(remoteSignerUrl.url, createPubKey(3)), fee, gas)
proc addValidator(data: KeystoreData) {.gcsafe.} =
discard pool[].addValidator(data, fee, gas)
@ -232,14 +233,14 @@ suite "Validator pool":
expected = [
createLocal(createPubKey(1)),
createRemote(createPubKey(2)),
createDynamic(remoteSignerUrl, createPubKey(3)),
createDynamic(remoteSignerUrl, createPubKey(4)),
createDynamic(remoteSignerUrl, createPubKey(5))
createDynamic(remoteSignerUrl.url, createPubKey(3)),
createDynamic(remoteSignerUrl.url, createPubKey(4)),
createDynamic(remoteSignerUrl.url, createPubKey(5))
]
keystores = [
createDynamic(remoteSignerUrl, createPubKey(3)),
createDynamic(remoteSignerUrl, createPubKey(4)),
createDynamic(remoteSignerUrl, createPubKey(5))
createDynamic(remoteSignerUrl.url, createPubKey(3)),
createDynamic(remoteSignerUrl.url, createPubKey(4)),
createDynamic(remoteSignerUrl.url, createPubKey(5))
]
pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator)
pool[].checkPool(expected)
@ -250,10 +251,10 @@ suite "Validator pool":
expected = [
createLocal(createPubKey(1)),
createRemote(createPubKey(2)),
createDynamic(remoteSignerUrl, createPubKey(3))
createDynamic(remoteSignerUrl.url, createPubKey(3))
]
keystores = [
createDynamic(remoteSignerUrl, createPubKey(3)),
createDynamic(remoteSignerUrl.url, createPubKey(3)),
]
pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator)
pool[].checkPool(expected)
@ -264,12 +265,12 @@ suite "Validator pool":
expected = [
createLocal(createPubKey(1)),
createRemote(createPubKey(2)),
createDynamic(remoteSignerUrl, createPubKey(4)),
createDynamic(remoteSignerUrl, createPubKey(5))
createDynamic(remoteSignerUrl.url, createPubKey(4)),
createDynamic(remoteSignerUrl.url, createPubKey(5))
]
keystores = [
createDynamic(remoteSignerUrl, createPubKey(4)),
createDynamic(remoteSignerUrl, createPubKey(5))
createDynamic(remoteSignerUrl.url, createPubKey(4)),
createDynamic(remoteSignerUrl.url, createPubKey(5))
]
pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator)
pool[].checkPool(expected)
@ -280,12 +281,12 @@ suite "Validator pool":
expected = [
createLocal(createPubKey(1)),
createRemote(createPubKey(2)),
createDynamic(remoteSignerUrl, createPubKey(3))
createDynamic(remoteSignerUrl.url, createPubKey(3))
]
keystores = [
createDynamic(remoteSignerUrl, createPubKey(1)),
createDynamic(remoteSignerUrl, createPubKey(2)),
createDynamic(remoteSignerUrl, createPubKey(3)),
createDynamic(remoteSignerUrl.url, createPubKey(1)),
createDynamic(remoteSignerUrl.url, createPubKey(2)),
createDynamic(remoteSignerUrl.url, createPubKey(3)),
]
pool.updateDynamicValidators(remoteSignerUrl, keystores, addValidator)
pool[].checkPool(expected)