2021-03-26 06:52:01 +00:00
|
|
|
# beacon_chain
|
2023-01-09 22:44:44 +00:00
|
|
|
# Copyright (c) 2018-2023 Status Research & Development GmbH
|
2021-03-26 06:52:01 +00:00
|
|
|
# Licensed and distributed under either of
|
|
|
|
# * MIT license (license terms in the root directory or at https://opensource.org/licenses/MIT).
|
|
|
|
# * Apache v2 license (license terms in the root directory or at https://www.apache.org/licenses/LICENSE-2.0).
|
|
|
|
# at your option. This file may not be copied, modified, or distributed except according to those terms.
|
|
|
|
|
2023-01-20 14:14:37 +00:00
|
|
|
{.push raises: [].}
|
2021-03-26 06:52:01 +00:00
|
|
|
|
2018-11-23 23:58:49 +00:00
|
|
|
import
|
2023-02-03 15:28:28 +00:00
|
|
|
std/[tables, json, streams, sequtils, uri],
|
2022-08-19 21:51:30 +00:00
|
|
|
chronos, chronicles, metrics, eth/async_utils,
|
2021-10-19 14:09:26 +00:00
|
|
|
json_serialization/std/net,
|
2021-11-30 01:20:21 +00:00
|
|
|
presto, presto/client,
|
|
|
|
|
|
|
|
../spec/[keystore, signatures, helpers, crypto],
|
2021-08-17 08:07:17 +00:00
|
|
|
../spec/datatypes/[phase0, altair],
|
2021-11-30 01:20:21 +00:00
|
|
|
../spec/eth2_apis/[rest_types, eth2_rest_serialization,
|
|
|
|
rest_remote_signer_calls],
|
2022-08-07 21:53:20 +00:00
|
|
|
../filepath,
|
2021-03-02 10:27:45 +00:00
|
|
|
./slashing_protection
|
2020-11-27 22:16:13 +00:00
|
|
|
|
2021-10-19 14:09:26 +00:00
|
|
|
export
|
2023-02-03 15:28:28 +00:00
|
|
|
streams, keystore, phase0, altair, tables, uri, crypto,
|
2022-02-11 20:40:49 +00:00
|
|
|
rest_types, eth2_rest_serialization, rest_remote_signer_calls,
|
|
|
|
slashing_protection
|
2021-10-19 14:09:26 +00:00
|
|
|
|
2022-08-19 21:51:30 +00:00
|
|
|
const
|
|
|
|
WEB3_SIGNER_DELAY_TOLERANCE = 3.seconds
|
2022-12-09 16:05:55 +00:00
|
|
|
DOPPELGANGER_EPOCHS_COUNT = 1
|
|
|
|
## The number of full epochs that we monitor validators for doppelganger
|
|
|
|
## protection
|
2022-08-19 21:51:30 +00:00
|
|
|
|
2020-11-27 22:16:13 +00:00
|
|
|
declareGauge validators,
|
|
|
|
"Number of validators attached to the beacon node"
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2021-10-19 14:09:26 +00:00
|
|
|
type
|
|
|
|
ValidatorKind* {.pure.} = enum
|
|
|
|
Local, Remote
|
|
|
|
|
2021-11-30 01:20:21 +00:00
|
|
|
ValidatorConnection* = RestClientRef
|
2021-10-19 14:09:26 +00:00
|
|
|
|
2023-02-07 14:53:36 +00:00
|
|
|
ValidatorAndIndex* = object
|
|
|
|
index*: ValidatorIndex
|
|
|
|
validator*: Validator
|
|
|
|
|
2022-12-09 16:05:55 +00:00
|
|
|
DoppelgangerStatus {.pure.} = enum
|
|
|
|
Unknown, Checking, Checked
|
|
|
|
|
2021-10-19 14:09:26 +00:00
|
|
|
AttachedValidator* = ref object
|
2021-12-22 12:37:31 +00:00
|
|
|
data*: KeystoreData
|
2021-10-19 14:09:26 +00:00
|
|
|
case kind*: ValidatorKind
|
|
|
|
of ValidatorKind.Local:
|
2021-11-30 01:20:21 +00:00
|
|
|
discard
|
2021-10-19 14:09:26 +00:00
|
|
|
of ValidatorKind.Remote:
|
2022-05-10 00:32:12 +00:00
|
|
|
clients*: seq[(RestClientRef, RemoteSignerInfo)]
|
|
|
|
threshold*: uint32
|
2021-10-19 14:09:26 +00:00
|
|
|
|
2023-02-07 14:53:36 +00:00
|
|
|
updated*: bool
|
2022-08-19 21:51:30 +00:00
|
|
|
index*: Opt[ValidatorIndex]
|
2022-12-09 16:05:55 +00:00
|
|
|
## Validator index which is assigned after the eth1 deposit has been
|
|
|
|
## processed - this index is valid across all eth2 forks for fork depths
|
|
|
|
## up to ETH1_FOLLOW_DISTANCE - we don't support changing indices.
|
2021-10-19 14:09:26 +00:00
|
|
|
|
2022-12-09 16:05:55 +00:00
|
|
|
activationEpoch*: Epoch
|
|
|
|
## Epoch when validator activated - this happens at the time or some time
|
|
|
|
## after the validator index has been assigned depending on how many
|
|
|
|
## validators are in the activation queue - this is the first epoch that
|
|
|
|
## the validator starts performing duties
|
2022-10-21 14:53:30 +00:00
|
|
|
|
2021-10-19 14:09:26 +00:00
|
|
|
# Cache the latest slot signature - the slot signature is used to determine
|
|
|
|
# if the validator will be aggregating (in the near future)
|
2022-08-31 00:29:03 +00:00
|
|
|
slotSignature*: Opt[tuple[slot: Slot, signature: ValidatorSig]]
|
|
|
|
|
|
|
|
# For the external payload builder; each epoch, the external payload
|
|
|
|
# builder should be informed of current validators
|
|
|
|
externalBuilderRegistration*: Opt[SignedValidatorRegistrationV1]
|
2021-10-19 14:09:26 +00:00
|
|
|
|
2022-12-09 16:05:55 +00:00
|
|
|
doppelStatus: DoppelgangerStatus
|
|
|
|
doppelEpoch*: Opt[Epoch]
|
|
|
|
## The epoch where doppelganger detection started doing its monitoring
|
2022-07-21 16:54:07 +00:00
|
|
|
|
2022-11-24 07:48:10 +00:00
|
|
|
lastWarning*: Opt[Slot]
|
|
|
|
|
2021-11-30 01:20:21 +00:00
|
|
|
SignResponse* = Web3SignerDataResponse
|
|
|
|
|
|
|
|
SignatureResult* = Result[ValidatorSig, string]
|
|
|
|
SyncCommitteeMessageResult* = Result[SyncCommitteeMessage, string]
|
|
|
|
|
2021-10-19 14:09:26 +00:00
|
|
|
ValidatorPool* = object
|
|
|
|
validators*: Table[ValidatorPubKey, AttachedValidator]
|
|
|
|
slashingProtection*: SlashingProtectionDB
|
2022-12-09 16:05:55 +00:00
|
|
|
doppelgangerDetectionEnabled*: bool
|
2021-10-19 14:09:26 +00:00
|
|
|
|
2022-09-17 05:30:07 +00:00
|
|
|
template pubkey*(v: AttachedValidator): ValidatorPubKey =
|
|
|
|
v.data.pubkey
|
|
|
|
|
2021-11-30 01:20:21 +00:00
|
|
|
func shortLog*(v: AttachedValidator): string =
|
|
|
|
case v.kind
|
|
|
|
of ValidatorKind.Local:
|
2021-12-22 12:37:31 +00:00
|
|
|
shortLog(v.pubkey)
|
2021-11-30 01:20:21 +00:00
|
|
|
of ValidatorKind.Remote:
|
2022-05-10 00:32:12 +00:00
|
|
|
shortLog(v.pubkey)
|
2021-10-19 14:09:26 +00:00
|
|
|
|
2020-09-16 11:30:03 +00:00
|
|
|
func init*(T: type ValidatorPool,
|
2022-12-09 16:05:55 +00:00
|
|
|
slashingProtectionDB: SlashingProtectionDB,
|
|
|
|
doppelgangerDetectionEnabled: bool): T =
|
2020-09-16 11:30:03 +00:00
|
|
|
## Initialize the validator pool and the slashing protection service
|
2021-02-09 15:23:06 +00:00
|
|
|
## `genesis_validators_root` is used as an unique ID for the
|
2020-09-16 11:30:03 +00:00
|
|
|
## blockchain
|
|
|
|
## `backend` is the KeyValue Store backend
|
2022-12-09 16:05:55 +00:00
|
|
|
T(
|
|
|
|
slashingProtection: slashingProtectionDB,
|
|
|
|
doppelgangerDetectionEnabled: doppelgangerDetectionEnabled)
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2018-12-19 12:58:53 +00:00
|
|
|
template count*(pool: ValidatorPool): int =
|
2021-07-13 11:15:07 +00:00
|
|
|
len(pool.validators)
|
2018-12-19 12:58:53 +00:00
|
|
|
|
2023-02-07 14:53:36 +00:00
|
|
|
proc addLocalValidator(
|
2022-12-09 16:05:55 +00:00
|
|
|
pool: var ValidatorPool, keystore: KeystoreData,
|
2023-02-15 15:10:31 +00:00
|
|
|
feeRecipient: Eth1Address, gasLimit: uint64): AttachedValidator =
|
2022-09-17 05:30:07 +00:00
|
|
|
doAssert keystore.kind == KeystoreKind.Local
|
2022-08-31 00:29:03 +00:00
|
|
|
let v = AttachedValidator(
|
2022-11-20 13:55:43 +00:00
|
|
|
kind: ValidatorKind.Local,
|
|
|
|
data: keystore,
|
2022-08-31 00:29:03 +00:00
|
|
|
externalBuilderRegistration: Opt.none SignedValidatorRegistrationV1,
|
2022-12-09 16:05:55 +00:00
|
|
|
activationEpoch: FAR_FUTURE_EPOCH
|
2022-11-20 13:55:43 +00:00
|
|
|
)
|
2022-09-17 05:30:07 +00:00
|
|
|
pool.validators[v.pubkey] = v
|
|
|
|
|
|
|
|
# Fee recipient may change after startup, but we log the initial value here
|
|
|
|
notice "Local validator attached",
|
|
|
|
pubkey = v.pubkey,
|
|
|
|
validator = shortLog(v),
|
2023-02-15 15:10:31 +00:00
|
|
|
initial_fee_recipient = feeRecipient.toHex(),
|
|
|
|
initial_gas_limit = gasLimit
|
2021-07-13 11:15:07 +00:00
|
|
|
validators.set(pool.count().int64)
|
2019-04-06 07:46:07 +00:00
|
|
|
|
2022-12-09 16:05:55 +00:00
|
|
|
v
|
2020-11-27 22:16:13 +00:00
|
|
|
|
2023-02-07 14:53:36 +00:00
|
|
|
proc addRemoteValidator(pool: var ValidatorPool, keystore: KeystoreData,
|
|
|
|
clients: seq[(RestClientRef, RemoteSignerInfo)],
|
2023-02-15 15:10:31 +00:00
|
|
|
feeRecipient: Eth1Address,
|
|
|
|
gasLimit: uint64): AttachedValidator =
|
2022-09-17 05:30:07 +00:00
|
|
|
doAssert keystore.kind == KeystoreKind.Remote
|
2022-08-31 00:29:03 +00:00
|
|
|
let v = AttachedValidator(
|
2022-11-20 13:55:43 +00:00
|
|
|
kind: ValidatorKind.Remote,
|
|
|
|
data: keystore,
|
2022-08-31 00:29:03 +00:00
|
|
|
clients: clients,
|
|
|
|
externalBuilderRegistration: Opt.none SignedValidatorRegistrationV1,
|
2022-12-09 16:05:55 +00:00
|
|
|
activationEpoch: FAR_FUTURE_EPOCH,
|
2022-11-20 13:55:43 +00:00
|
|
|
)
|
2022-09-17 05:30:07 +00:00
|
|
|
pool.validators[v.pubkey] = v
|
|
|
|
notice "Remote validator attached",
|
|
|
|
pubkey = v.pubkey,
|
|
|
|
validator = shortLog(v),
|
|
|
|
remote_signer = $keystore.remotes,
|
2023-02-15 15:10:31 +00:00
|
|
|
initial_fee_recipient = feeRecipient.toHex(),
|
|
|
|
initial_gas_limit = gasLimit
|
2022-12-09 16:05:55 +00:00
|
|
|
|
2020-11-27 22:16:13 +00:00
|
|
|
validators.set(pool.count().int64)
|
|
|
|
|
2022-12-09 16:05:55 +00:00
|
|
|
v
|
|
|
|
|
2023-02-07 14:53:36 +00:00
|
|
|
proc addRemoteValidator(pool: var ValidatorPool,
|
|
|
|
keystore: KeystoreData,
|
2023-02-15 15:10:31 +00:00
|
|
|
feeRecipient: Eth1Address,
|
|
|
|
gasLimit: uint64): AttachedValidator =
|
2023-02-07 14:53:36 +00:00
|
|
|
let
|
|
|
|
httpFlags =
|
|
|
|
if RemoteKeystoreFlag.IgnoreSSLVerification in keystore.flags:
|
|
|
|
{HttpClientFlag.NoVerifyHost, HttpClientFlag.NoVerifyServerName}
|
|
|
|
else:
|
|
|
|
{}
|
|
|
|
prestoFlags = {RestClientFlag.CommaSeparatedArray}
|
|
|
|
clients =
|
|
|
|
block:
|
|
|
|
var res: seq[(RestClientRef, RemoteSignerInfo)]
|
|
|
|
for remote in keystore.remotes:
|
|
|
|
let client = RestClientRef.new($remote.url, prestoFlags, httpFlags)
|
|
|
|
if client.isErr():
|
|
|
|
# TODO keep trying in case of temporary network failure
|
|
|
|
warn "Unable to resolve distributed signer address",
|
|
|
|
remote_url = $remote.url, validator = $remote.pubkey
|
|
|
|
else:
|
|
|
|
res.add((client.get(), remote))
|
|
|
|
res
|
|
|
|
|
2023-02-15 15:10:31 +00:00
|
|
|
pool.addRemoteValidator(keystore, clients, feeRecipient, gasLimit)
|
2023-02-07 14:53:36 +00:00
|
|
|
|
|
|
|
proc addValidator*(pool: var ValidatorPool,
|
|
|
|
keystore: KeystoreData,
|
2023-02-15 15:10:31 +00:00
|
|
|
feeRecipient: Eth1Address,
|
|
|
|
gasLimit: uint64): AttachedValidator =
|
2023-02-07 14:53:36 +00:00
|
|
|
pool.validators.withValue(keystore.pubkey, v):
|
|
|
|
notice "Adding already-known validator", validator = shortLog(v[])
|
|
|
|
return v[]
|
|
|
|
|
|
|
|
case keystore.kind
|
2023-02-15 15:10:31 +00:00
|
|
|
of KeystoreKind.Local:
|
|
|
|
pool.addLocalValidator(keystore, feeRecipient, gasLimit)
|
|
|
|
of KeystoreKind.Remote:
|
|
|
|
pool.addRemoteValidator(keystore, feeRecipient, gasLimit)
|
2023-02-07 14:53:36 +00:00
|
|
|
|
2020-08-10 13:21:31 +00:00
|
|
|
proc getValidator*(pool: ValidatorPool,
|
2018-11-29 01:08:34 +00:00
|
|
|
validatorKey: ValidatorPubKey): AttachedValidator =
|
performance fixes (#2259)
* performance fixes
* don't mark tree cache as dirty on read-only List accesses
* store only blob in memory for keys and signatures, parse blob lazily
* compare public keys by blob instead of parsing / converting to raw
* compare Eth2Digest using non-constant-time comparison
* avoid some unnecessary validator copying
This branch will in particular speed up deposit processing which has
been slowing down block replay.
Pre (mainnet, 1600 blocks):
```
All time are ms
Average, StdDev, Min, Max, Samples, Test
Validation is turned off meaning that no BLS operations are performed
3450.269, 0.000, 3450.269, 3450.269, 1, Initialize DB
0.417, 0.822, 0.036, 21.098, 1400, Load block from database
16.521, 0.000, 16.521, 16.521, 1, Load state from database
27.906, 50.846, 8.104, 1507.633, 1350, Apply block
52.617, 37.029, 20.640, 135.938, 50, Apply epoch block
```
Post:
```
3502.715, 0.000, 3502.715, 3502.715, 1, Initialize DB
0.080, 0.560, 0.035, 21.015, 1400, Load block from database
17.595, 0.000, 17.595, 17.595, 1, Load state from database
15.706, 11.028, 8.300, 107.537, 1350, Apply block
33.217, 12.622, 17.331, 60.580, 50, Apply epoch block
```
* more perf fixes
* load EpochRef cache into StateCache more aggressively
* point out security concern with public key cache
* reuse proposer index from state when processing block
* avoid genericAssign in a few more places
* don't parse key when signature is unparseable
* fix `==` overload for Eth2Digest
* preallocate validator list when getting active validators
* speed up proposer index calculation a little bit
* reuse cache when replaying blocks in ncli_db
* avoid a few more copying loops
```
Average, StdDev, Min, Max, Samples, Test
Validation is turned off meaning that no BLS operations are performed
3279.158, 0.000, 3279.158, 3279.158, 1, Initialize DB
0.072, 0.357, 0.035, 13.400, 1400, Load block from database
17.295, 0.000, 17.295, 17.295, 1, Load state from database
5.918, 9.896, 0.198, 98.028, 1350, Apply block
15.888, 10.951, 7.902, 39.535, 50, Apply epoch block
0.000, 0.000, 0.000, 0.000, 0, Database block store
```
* clear full balance cache before processing rewards and penalties
```
All time are ms
Average, StdDev, Min, Max, Samples, Test
Validation is turned off meaning that no BLS operations are performed
3947.901, 0.000, 3947.901, 3947.901, 1, Initialize DB
0.124, 0.506, 0.026, 202.370, 363345, Load block from database
97.614, 0.000, 97.614, 97.614, 1, Load state from database
0.186, 0.188, 0.012, 99.561, 357262, Advance slot, non-epoch
14.161, 5.966, 1.099, 395.511, 11524, Advance slot, epoch
1.372, 4.170, 0.017, 276.401, 363345, Apply block, no slot processing
0.000, 0.000, 0.000, 0.000, 0, Database block store
```
2021-01-25 12:04:18 +00:00
|
|
|
pool.validators.getOrDefault(validatorKey)
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2021-12-22 12:37:31 +00:00
|
|
|
proc contains*(pool: ValidatorPool, pubkey: ValidatorPubKey): bool =
|
|
|
|
## Returns ``true`` if validator with key ``pubkey`` present in ``pool``.
|
|
|
|
pool.validators.contains(pubkey)
|
2021-07-13 11:15:07 +00:00
|
|
|
|
2021-12-22 12:37:31 +00:00
|
|
|
proc removeValidator*(pool: var ValidatorPool, pubkey: ValidatorPubKey) =
|
|
|
|
## Delete validator with public key ``pubkey`` from ``pool``.
|
|
|
|
let validator = pool.validators.getOrDefault(pubkey)
|
2021-10-04 19:08:31 +00:00
|
|
|
if not(isNil(validator)):
|
2021-12-22 12:37:31 +00:00
|
|
|
pool.validators.del(pubkey)
|
2022-02-07 20:36:09 +00:00
|
|
|
case validator.kind
|
|
|
|
of ValidatorKind.Local:
|
|
|
|
notice "Local validator detached", pubkey, validator = shortLog(validator)
|
|
|
|
of ValidatorKind.Remote:
|
|
|
|
notice "Remote validator detached", pubkey,
|
|
|
|
validator = shortLog(validator)
|
2021-10-04 19:08:31 +00:00
|
|
|
validators.set(pool.count().int64)
|
2021-07-13 11:15:07 +00:00
|
|
|
|
2022-12-09 16:05:55 +00:00
|
|
|
proc needsUpdate*(validator: AttachedValidator): bool =
|
|
|
|
validator.index.isNone() or validator.activationEpoch == FAR_FUTURE_EPOCH
|
|
|
|
|
2023-02-07 14:53:36 +00:00
|
|
|
proc updateValidator*(
|
|
|
|
validator: AttachedValidator, validatorData: Opt[ValidatorAndIndex]) =
|
|
|
|
defer: validator.updated = true
|
|
|
|
|
|
|
|
let
|
|
|
|
data = validatorData.valueOr:
|
|
|
|
if not validator.updated:
|
|
|
|
notice "Validator deposit not yet processed, monitoring",
|
|
|
|
pubkey = validator.pubkey
|
|
|
|
|
|
|
|
return
|
|
|
|
index = data.index
|
|
|
|
activationEpoch = data.validator.activation_epoch
|
|
|
|
|
2022-12-09 16:05:55 +00:00
|
|
|
## Update activation information for a validator
|
2023-02-07 14:53:36 +00:00
|
|
|
if validator.index != Opt.some data.index:
|
|
|
|
validator.index = Opt.some data.index
|
|
|
|
|
|
|
|
if validator.activationEpoch != data.validator.activation_epoch:
|
|
|
|
# In theory, activation epoch could change but that's rare enough that it
|
|
|
|
# shouldn't practically matter for the current uses
|
|
|
|
info "Validator activation updated",
|
|
|
|
validator = shortLog(validator), pubkey = validator.pubkey, index,
|
|
|
|
activationEpoch
|
|
|
|
|
|
|
|
validator.activationEpoch = activationEpoch
|
|
|
|
|
|
|
|
if validator.doppelStatus == DoppelgangerStatus.Unknown:
|
|
|
|
if validator.doppelEpoch.isSome() and activationEpoch != FAR_FUTURE_EPOCH:
|
|
|
|
let doppelEpoch = validator.doppelEpoch.get()
|
|
|
|
if doppelEpoch >= validator.activationEpoch + DOPPELGANGER_EPOCHS_COUNT:
|
|
|
|
validator.doppelStatus = DoppelgangerStatus.Checking
|
|
|
|
else:
|
|
|
|
validator.doppelStatus = DoppelgangerStatus.Checked
|
2021-07-13 11:15:07 +00:00
|
|
|
|
2022-08-07 21:53:20 +00:00
|
|
|
proc close*(pool: var ValidatorPool) =
|
|
|
|
## Unlock and close all validator keystore's files managed by ``pool``.
|
|
|
|
for validator in pool.validators.values():
|
|
|
|
let res = validator.data.handle.closeLockedFile()
|
|
|
|
if res.isErr():
|
|
|
|
notice "Could not unlock validator's keystore file",
|
|
|
|
pubkey = validator.pubkey, validator = shortLog(validator)
|
|
|
|
|
2021-07-13 11:15:07 +00:00
|
|
|
iterator publicKeys*(pool: ValidatorPool): ValidatorPubKey =
|
|
|
|
for item in pool.validators.keys():
|
|
|
|
yield item
|
|
|
|
|
|
|
|
iterator indices*(pool: ValidatorPool): ValidatorIndex =
|
|
|
|
for item in pool.validators.values():
|
|
|
|
if item.index.isSome():
|
|
|
|
yield item.index.get()
|
|
|
|
|
|
|
|
iterator items*(pool: ValidatorPool): AttachedValidator =
|
|
|
|
for item in pool.validators.values():
|
|
|
|
yield item
|
|
|
|
|
2022-12-09 16:05:55 +00:00
|
|
|
proc triggersDoppelganger*(v: AttachedValidator, epoch: Epoch): bool =
|
|
|
|
## Returns true iff detected activity in the given epoch would trigger
|
|
|
|
## doppelganger detection
|
|
|
|
if v.doppelStatus != DoppelgangerStatus.Checked:
|
|
|
|
if v.activationEpoch == FAR_FUTURE_EPOCH:
|
|
|
|
false
|
|
|
|
elif epoch < v.activationEpoch + DOPPELGANGER_EPOCHS_COUNT:
|
|
|
|
v.doppelStatus = DoppelgangerStatus.Checked
|
|
|
|
false
|
2022-11-20 13:55:43 +00:00
|
|
|
else:
|
2022-12-09 16:05:55 +00:00
|
|
|
true
|
|
|
|
else:
|
|
|
|
false
|
|
|
|
|
|
|
|
proc updateDoppelganger*(validator: AttachedValidator, epoch: Epoch) =
|
|
|
|
## Called when the validator has proven to be inactive in the given epoch -
|
|
|
|
## this call should be made after the end of `epoch` before acting on duties
|
|
|
|
## in `epoch + 1`.
|
|
|
|
|
|
|
|
if validator.doppelStatus == DoppelgangerStatus.Checked:
|
|
|
|
return
|
|
|
|
|
|
|
|
if validator.doppelEpoch.isNone():
|
|
|
|
validator.doppelEpoch = Opt.some epoch
|
|
|
|
|
|
|
|
let doppelEpoch = validator.doppelEpoch.get()
|
|
|
|
|
|
|
|
if validator.doppelStatus == DoppelgangerStatus.Unknown:
|
|
|
|
if validator.activationEpoch == FAR_FUTURE_EPOCH:
|
|
|
|
return
|
|
|
|
|
|
|
|
# We don't do doppelganger checking for validators that are about to be
|
|
|
|
# activated since both clients would be waiting for the other to start
|
|
|
|
# performing duties - this accounts for genesis as well
|
|
|
|
# The slot is rounded up to ensure we cover all slots
|
|
|
|
if doppelEpoch + 1 <= validator.activationEpoch + DOPPELGANGER_EPOCHS_COUNT:
|
|
|
|
validator.doppelStatus = DoppelgangerStatus.Checked
|
|
|
|
return
|
|
|
|
|
|
|
|
validator.doppelStatus = DoppelgangerStatus.Checking
|
|
|
|
|
|
|
|
if epoch + 1 >= doppelEpoch + DOPPELGANGER_EPOCHS_COUNT:
|
|
|
|
validator.doppelStatus = DoppelgangerStatus.Checked
|
|
|
|
|
|
|
|
proc getValidatorForDuties*(
|
|
|
|
pool: ValidatorPool, key: ValidatorPubKey, slot: Slot):
|
|
|
|
Opt[AttachedValidator] =
|
|
|
|
## Return validator only if it is ready for duties (has index and has passed
|
|
|
|
## doppelganger check where applicable)
|
|
|
|
let validator = pool.getValidator(key)
|
|
|
|
if isNil(validator) or validator.index.isNone():
|
|
|
|
return Opt.none(AttachedValidator)
|
|
|
|
|
|
|
|
if pool.doppelgangerDetectionEnabled and
|
|
|
|
validator.triggersDoppelganger(slot.epoch):
|
|
|
|
# If the validator would trigger for an activity in the given slot, we don't
|
|
|
|
# return it for duties
|
|
|
|
notice "Doppelganger detection active - " &
|
2022-12-09 19:15:46 +00:00
|
|
|
"skipping validator duties while observing the network",
|
2022-12-09 16:05:55 +00:00
|
|
|
validator = shortLog(validator)
|
|
|
|
return Opt.none(AttachedValidator)
|
|
|
|
|
|
|
|
return Opt.some(validator)
|
2022-11-20 13:55:43 +00:00
|
|
|
|
2022-05-10 00:32:12 +00:00
|
|
|
proc signWithDistributedKey(v: AttachedValidator,
|
|
|
|
request: Web3SignerRequest): Future[SignatureResult]
|
|
|
|
{.async.} =
|
|
|
|
doAssert v.data.threshold <= uint32(v.clients.len)
|
|
|
|
|
2022-08-19 21:51:30 +00:00
|
|
|
let
|
|
|
|
signatureReqs = mapIt(v.clients, it[0].signData(it[1].pubkey, request))
|
|
|
|
deadline = sleepAsync(WEB3_SIGNER_DELAY_TOLERANCE)
|
|
|
|
|
|
|
|
await allFutures(signatureReqs) or deadline
|
2022-05-10 00:32:12 +00:00
|
|
|
|
|
|
|
var shares: seq[SignatureShare]
|
|
|
|
var neededShares = v.data.threshold
|
|
|
|
|
|
|
|
for i, req in signatureReqs:
|
|
|
|
template shareInfo: untyped = v.clients[i][1]
|
|
|
|
if req.done and req.read.isOk:
|
|
|
|
shares.add req.read.get.toSignatureShare(shareInfo.id)
|
|
|
|
neededShares = neededShares - 1
|
|
|
|
else:
|
|
|
|
warn "Failed to obtain signature from remote signer",
|
|
|
|
pubkey = shareInfo.pubkey,
|
|
|
|
signerUrl = $(v.clients[i][0].address)
|
|
|
|
|
|
|
|
if neededShares == 0:
|
|
|
|
let recovered = shares.recoverSignature()
|
|
|
|
return SignatureResult.ok recovered.toValidatorSig
|
|
|
|
|
|
|
|
return SignatureResult.err "Not enough shares to recover the signature"
|
|
|
|
|
|
|
|
proc signWithSingleKey(v: AttachedValidator,
|
|
|
|
request: Web3SignerRequest): Future[SignatureResult]
|
|
|
|
{.async.} =
|
|
|
|
doAssert v.clients.len == 1
|
|
|
|
let (client, info) = v.clients[0]
|
2022-08-19 21:51:30 +00:00
|
|
|
let res = awaitWithTimeout(client.signData(info.pubkey, request),
|
|
|
|
WEB3_SIGNER_DELAY_TOLERANCE):
|
|
|
|
return SignatureResult.err "Timeout"
|
2022-05-10 00:32:12 +00:00
|
|
|
if res.isErr:
|
|
|
|
return SignatureResult.err res.error
|
|
|
|
else:
|
|
|
|
return SignatureResult.ok res.get.toValidatorSig
|
|
|
|
|
|
|
|
proc signData(v: AttachedValidator,
|
2022-06-29 16:53:59 +00:00
|
|
|
request: Web3SignerRequest): Future[SignatureResult] =
|
|
|
|
doAssert v.kind == ValidatorKind.Remote
|
|
|
|
debug "Signing request with remote signer",
|
|
|
|
validator = shortLog(v), kind = request.kind
|
|
|
|
if v.clients.len == 1:
|
|
|
|
v.signWithSingleKey(request)
|
|
|
|
else:
|
|
|
|
v.signWithDistributedKey(request)
|
2020-09-01 13:44:40 +00:00
|
|
|
|
2023-02-09 22:08:43 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/phase0/validator.md#signature
|
2022-06-29 16:53:59 +00:00
|
|
|
proc getBlockSignature*(v: AttachedValidator, fork: Fork,
|
2020-03-30 11:31:44 +00:00
|
|
|
genesis_validators_root: Eth2Digest, slot: Slot,
|
2022-08-01 06:41:47 +00:00
|
|
|
block_root: Eth2Digest,
|
2022-11-24 09:14:05 +00:00
|
|
|
blck: ForkedBeaconBlock | ForkedBlindedBeaconBlock |
|
2023-02-06 18:07:30 +00:00
|
|
|
bellatrix_mev.BlindedBeaconBlock
|
2022-05-10 00:32:12 +00:00
|
|
|
): Future[SignatureResult] {.async.} =
|
2021-10-04 19:08:31 +00:00
|
|
|
return
|
|
|
|
case v.kind
|
|
|
|
of ValidatorKind.Local:
|
2021-11-30 01:20:21 +00:00
|
|
|
SignatureResult.ok(
|
2022-06-29 16:53:59 +00:00
|
|
|
get_block_signature(
|
|
|
|
fork, genesis_validators_root, slot, block_root,
|
|
|
|
v.data.privateKey).toValidatorSig())
|
2021-10-04 19:08:31 +00:00
|
|
|
of ValidatorKind.Remote:
|
2022-11-24 09:14:05 +00:00
|
|
|
when blck is ForkedBlindedBeaconBlock:
|
|
|
|
let
|
|
|
|
web3SignerBlock =
|
|
|
|
case blck.kind
|
2023-01-28 19:53:41 +00:00
|
|
|
of ConsensusFork.Phase0:
|
2022-11-24 09:14:05 +00:00
|
|
|
Web3SignerForkedBeaconBlock(
|
2023-01-28 19:53:41 +00:00
|
|
|
kind: ConsensusFork.Phase0,
|
2022-11-24 09:14:05 +00:00
|
|
|
phase0Data: blck.phase0Data)
|
2023-01-28 19:53:41 +00:00
|
|
|
of ConsensusFork.Altair:
|
2022-11-24 09:14:05 +00:00
|
|
|
Web3SignerForkedBeaconBlock(
|
2023-01-28 19:53:41 +00:00
|
|
|
kind: ConsensusFork.Altair,
|
2022-11-24 09:14:05 +00:00
|
|
|
altairData: blck.altairData)
|
2023-01-28 19:53:41 +00:00
|
|
|
of ConsensusFork.Bellatrix:
|
2022-11-24 09:14:05 +00:00
|
|
|
Web3SignerForkedBeaconBlock(
|
2023-01-28 19:53:41 +00:00
|
|
|
kind: ConsensusFork.Bellatrix,
|
2022-11-24 09:14:05 +00:00
|
|
|
bellatrixData: blck.bellatrixData.toBeaconBlockHeader)
|
2023-01-28 19:53:41 +00:00
|
|
|
of ConsensusFork.Capella:
|
2022-11-24 14:38:07 +00:00
|
|
|
Web3SignerForkedBeaconBlock(
|
2023-01-28 19:53:41 +00:00
|
|
|
kind: ConsensusFork.Capella,
|
2022-11-24 14:38:07 +00:00
|
|
|
capellaData: blck.capellaData.toBeaconBlockHeader)
|
2023-01-28 19:53:41 +00:00
|
|
|
of ConsensusFork.EIP4844:
|
2022-12-14 17:30:56 +00:00
|
|
|
Web3SignerForkedBeaconBlock(
|
2023-01-28 19:53:41 +00:00
|
|
|
kind: ConsensusFork.EIP4844,
|
2022-12-14 17:30:56 +00:00
|
|
|
eip4844Data: blck.eip4844Data.toBeaconBlockHeader)
|
2022-11-24 09:14:05 +00:00
|
|
|
|
|
|
|
request = Web3SignerRequest.init(
|
|
|
|
fork, genesis_validators_root, web3SignerBlock)
|
|
|
|
await v.signData(request)
|
|
|
|
elif blck is BlindedBeaconBlock:
|
2022-08-01 06:41:47 +00:00
|
|
|
let request = Web3SignerRequest.init(
|
|
|
|
fork, genesis_validators_root,
|
|
|
|
Web3SignerForkedBeaconBlock(
|
2023-01-28 19:53:41 +00:00
|
|
|
kind: ConsensusFork.Bellatrix,
|
2022-08-01 06:41:47 +00:00
|
|
|
bellatrixData: blck.toBeaconBlockHeader))
|
|
|
|
await v.signData(request)
|
|
|
|
else:
|
|
|
|
let
|
|
|
|
web3SignerBlock =
|
|
|
|
case blck.kind
|
2023-01-28 19:53:41 +00:00
|
|
|
of ConsensusFork.Phase0:
|
2022-08-01 06:41:47 +00:00
|
|
|
Web3SignerForkedBeaconBlock(
|
2023-01-28 19:53:41 +00:00
|
|
|
kind: ConsensusFork.Phase0,
|
2022-08-01 06:41:47 +00:00
|
|
|
phase0Data: blck.phase0Data)
|
2023-01-28 19:53:41 +00:00
|
|
|
of ConsensusFork.Altair:
|
2022-08-01 06:41:47 +00:00
|
|
|
Web3SignerForkedBeaconBlock(
|
2023-01-28 19:53:41 +00:00
|
|
|
kind: ConsensusFork.Altair,
|
2022-08-01 06:41:47 +00:00
|
|
|
altairData: blck.altairData)
|
2023-01-28 19:53:41 +00:00
|
|
|
of ConsensusFork.Bellatrix:
|
2022-08-01 06:41:47 +00:00
|
|
|
Web3SignerForkedBeaconBlock(
|
2023-01-28 19:53:41 +00:00
|
|
|
kind: ConsensusFork.Bellatrix,
|
2022-08-01 06:41:47 +00:00
|
|
|
bellatrixData: blck.bellatrixData.toBeaconBlockHeader)
|
2023-01-28 19:53:41 +00:00
|
|
|
of ConsensusFork.Capella:
|
2022-11-24 14:38:07 +00:00
|
|
|
Web3SignerForkedBeaconBlock(
|
2023-01-28 19:53:41 +00:00
|
|
|
kind: ConsensusFork.Capella,
|
2022-11-24 14:38:07 +00:00
|
|
|
capellaData: blck.capellaData.toBeaconBlockHeader)
|
2023-01-28 19:53:41 +00:00
|
|
|
of ConsensusFork.EIP4844:
|
2022-12-14 17:30:56 +00:00
|
|
|
Web3SignerForkedBeaconBlock(
|
2023-01-28 19:53:41 +00:00
|
|
|
kind: ConsensusFork.EIP4844,
|
2022-12-14 17:30:56 +00:00
|
|
|
eip4844Data: blck.eip4844Data.toBeaconBlockHeader)
|
2022-08-01 06:41:47 +00:00
|
|
|
|
|
|
|
request = Web3SignerRequest.init(
|
|
|
|
fork, genesis_validators_root, web3SignerBlock)
|
|
|
|
await v.signData(request)
|
2018-11-23 23:58:49 +00:00
|
|
|
|
2023-02-09 22:08:43 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/phase0/validator.md#aggregate-signature
|
2022-06-29 16:53:59 +00:00
|
|
|
proc getAttestationSignature*(v: AttachedValidator, fork: Fork,
|
|
|
|
genesis_validators_root: Eth2Digest,
|
|
|
|
data: AttestationData
|
|
|
|
): Future[SignatureResult] {.async.} =
|
2021-07-13 11:15:07 +00:00
|
|
|
return
|
2021-10-04 19:08:31 +00:00
|
|
|
case v.kind
|
|
|
|
of ValidatorKind.Local:
|
2021-11-30 01:20:21 +00:00
|
|
|
SignatureResult.ok(
|
2022-06-29 16:53:59 +00:00
|
|
|
get_attestation_signature(
|
|
|
|
fork, genesis_validators_root, data,
|
|
|
|
v.data.privateKey).toValidatorSig())
|
2021-10-04 19:08:31 +00:00
|
|
|
of ValidatorKind.Remote:
|
2022-06-29 16:53:59 +00:00
|
|
|
let request = Web3SignerRequest.init(fork, genesis_validators_root, data)
|
|
|
|
await v.signData(request)
|
|
|
|
|
2023-02-09 22:08:43 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/phase0/validator.md#broadcast-aggregate
|
2022-06-29 16:53:59 +00:00
|
|
|
proc getAggregateAndProofSignature*(v: AttachedValidator,
|
|
|
|
fork: Fork,
|
|
|
|
genesis_validators_root: Eth2Digest,
|
|
|
|
aggregate_and_proof: AggregateAndProof
|
|
|
|
): Future[SignatureResult] {.async.} =
|
2021-07-13 11:15:07 +00:00
|
|
|
return
|
2021-10-04 19:08:31 +00:00
|
|
|
case v.kind
|
|
|
|
of ValidatorKind.Local:
|
2021-11-30 01:20:21 +00:00
|
|
|
SignatureResult.ok(
|
2022-06-29 16:53:59 +00:00
|
|
|
get_aggregate_and_proof_signature(
|
|
|
|
fork, genesis_validators_root, aggregate_and_proof,
|
|
|
|
v.data.privateKey).toValidatorSig()
|
2021-11-30 01:20:21 +00:00
|
|
|
)
|
2021-10-04 19:08:31 +00:00
|
|
|
of ValidatorKind.Remote:
|
2022-06-29 16:53:59 +00:00
|
|
|
let request = Web3SignerRequest.init(
|
|
|
|
fork, genesis_validators_root, aggregate_and_proof)
|
|
|
|
await v.signData(request)
|
2020-04-15 02:41:22 +00:00
|
|
|
|
2023-02-09 22:08:43 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/altair/validator.md#prepare-sync-committee-message
|
2022-06-29 16:53:59 +00:00
|
|
|
proc getSyncCommitteeMessage*(v: AttachedValidator,
|
|
|
|
fork: Fork,
|
|
|
|
genesis_validators_root: Eth2Digest,
|
|
|
|
slot: Slot,
|
|
|
|
beacon_block_root: Eth2Digest
|
|
|
|
): Future[SyncCommitteeMessageResult] {.async.} =
|
2021-10-04 19:08:31 +00:00
|
|
|
let signature =
|
|
|
|
case v.kind
|
|
|
|
of ValidatorKind.Local:
|
2022-05-10 00:32:12 +00:00
|
|
|
SignatureResult.ok(get_sync_committee_message_signature(
|
2021-12-09 12:56:54 +00:00
|
|
|
fork, genesis_validators_root, slot, beacon_block_root,
|
2022-05-10 00:32:12 +00:00
|
|
|
v.data.privateKey).toValidatorSig())
|
2021-10-04 19:08:31 +00:00
|
|
|
of ValidatorKind.Remote:
|
2022-06-29 16:53:59 +00:00
|
|
|
let request = Web3SignerRequest.init(
|
|
|
|
fork, genesis_validators_root, beacon_block_root, slot)
|
|
|
|
await v.signData(request)
|
2022-05-10 00:32:12 +00:00
|
|
|
|
|
|
|
if signature.isErr:
|
|
|
|
return SyncCommitteeMessageResult.err("Failed to obtain signature")
|
2021-08-17 08:07:17 +00:00
|
|
|
|
2021-11-30 01:20:21 +00:00
|
|
|
return
|
|
|
|
SyncCommitteeMessageResult.ok(
|
|
|
|
SyncCommitteeMessage(
|
|
|
|
slot: slot,
|
2021-12-09 12:56:54 +00:00
|
|
|
beacon_block_root: beacon_block_root,
|
2021-11-30 01:20:21 +00:00
|
|
|
validator_index: uint64(v.index.get()),
|
2022-05-10 00:32:12 +00:00
|
|
|
signature: signature.get()
|
2021-11-30 01:20:21 +00:00
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2023-02-09 22:08:43 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/altair/validator.md#aggregation-selection
|
2022-06-29 16:53:59 +00:00
|
|
|
proc getSyncCommitteeSelectionProof*(v: AttachedValidator, fork: Fork,
|
2021-11-30 01:20:21 +00:00
|
|
|
genesis_validators_root: Eth2Digest,
|
|
|
|
slot: Slot,
|
2022-05-10 10:03:40 +00:00
|
|
|
subcommittee_index: SyncSubcommitteeIndex
|
2021-11-30 01:20:21 +00:00
|
|
|
): Future[SignatureResult] {.async.} =
|
2021-10-04 19:08:31 +00:00
|
|
|
return
|
|
|
|
case v.kind
|
|
|
|
of ValidatorKind.Local:
|
2021-12-09 12:56:54 +00:00
|
|
|
SignatureResult.ok(get_sync_committee_selection_proof(
|
|
|
|
fork, genesis_validators_root, slot, subcommittee_index,
|
|
|
|
v.data.privateKey).toValidatorSig())
|
2021-10-04 19:08:31 +00:00
|
|
|
of ValidatorKind.Remote:
|
2022-06-29 16:53:59 +00:00
|
|
|
let request = Web3SignerRequest.init(
|
|
|
|
fork, genesis_validators_root,
|
|
|
|
SyncAggregatorSelectionData(
|
|
|
|
slot: slot, subcommittee_index: uint64 subcommittee_index)
|
|
|
|
)
|
|
|
|
await v.signData(request)
|
2021-11-30 01:20:21 +00:00
|
|
|
|
2023-02-09 22:08:43 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/altair/validator.md#broadcast-sync-committee-contribution
|
2022-06-29 16:53:59 +00:00
|
|
|
proc getContributionAndProofSignature*(v: AttachedValidator, fork: Fork,
|
|
|
|
genesis_validators_root: Eth2Digest,
|
|
|
|
contribution_and_proof: ContributionAndProof
|
|
|
|
): Future[SignatureResult] {.async.} =
|
|
|
|
return
|
2021-10-04 19:08:31 +00:00
|
|
|
case v.kind
|
|
|
|
of ValidatorKind.Local:
|
2022-05-10 00:32:12 +00:00
|
|
|
SignatureResult.ok(get_contribution_and_proof_signature(
|
2022-06-29 16:53:59 +00:00
|
|
|
fork, genesis_validators_root, contribution_and_proof,
|
|
|
|
v.data.privateKey).toValidatorSig())
|
2021-10-04 19:08:31 +00:00
|
|
|
of ValidatorKind.Remote:
|
2022-06-29 16:53:59 +00:00
|
|
|
let request = Web3SignerRequest.init(
|
|
|
|
fork, genesis_validators_root, contribution_and_proof)
|
|
|
|
await v.signData(request)
|
2021-08-17 08:07:17 +00:00
|
|
|
|
2023-02-09 22:08:43 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/phase0/validator.md#randao-reveal
|
2022-06-29 16:53:59 +00:00
|
|
|
proc getEpochSignature*(v: AttachedValidator, fork: Fork,
|
|
|
|
genesis_validators_root: Eth2Digest, epoch: Epoch
|
|
|
|
): Future[SignatureResult] {.async.} =
|
2021-07-13 11:15:07 +00:00
|
|
|
return
|
2021-10-04 19:08:31 +00:00
|
|
|
case v.kind
|
|
|
|
of ValidatorKind.Local:
|
2022-06-29 16:53:59 +00:00
|
|
|
SignatureResult.ok(get_epoch_signature(
|
|
|
|
fork, genesis_validators_root, epoch,
|
|
|
|
v.data.privateKey).toValidatorSig())
|
2021-10-04 19:08:31 +00:00
|
|
|
of ValidatorKind.Remote:
|
2022-06-29 16:53:59 +00:00
|
|
|
let request = Web3SignerRequest.init(
|
|
|
|
fork, genesis_validators_root, epoch)
|
|
|
|
await v.signData(request)
|
|
|
|
|
2023-02-09 22:08:43 +00:00
|
|
|
# https://github.com/ethereum/consensus-specs/blob/v1.3.0-rc.2/specs/phase0/validator.md#aggregation-selection
|
2022-06-29 16:53:59 +00:00
|
|
|
proc getSlotSignature*(v: AttachedValidator, fork: Fork,
|
|
|
|
genesis_validators_root: Eth2Digest, slot: Slot
|
|
|
|
): Future[SignatureResult] {.async.} =
|
2022-05-10 00:32:12 +00:00
|
|
|
if v.slotSignature.isSome and v.slotSignature.get.slot == slot:
|
|
|
|
return SignatureResult.ok(v.slotSignature.get.signature)
|
2021-10-18 09:11:44 +00:00
|
|
|
|
|
|
|
let signature =
|
2021-10-04 19:08:31 +00:00
|
|
|
case v.kind
|
|
|
|
of ValidatorKind.Local:
|
2022-06-29 16:53:59 +00:00
|
|
|
SignatureResult.ok(get_slot_signature(
|
|
|
|
fork, genesis_validators_root, slot,
|
|
|
|
v.data.privateKey).toValidatorSig())
|
2021-10-04 19:08:31 +00:00
|
|
|
of ValidatorKind.Remote:
|
2022-06-29 16:53:59 +00:00
|
|
|
let request = Web3SignerRequest.init(fork, genesis_validators_root, slot)
|
|
|
|
await v.signData(request)
|
2022-05-10 00:32:12 +00:00
|
|
|
|
|
|
|
if signature.isErr:
|
|
|
|
return signature
|
2021-11-30 01:20:21 +00:00
|
|
|
|
2022-08-31 00:29:03 +00:00
|
|
|
v.slotSignature = Opt.some((slot, signature.get))
|
2022-05-10 00:32:12 +00:00
|
|
|
return signature
|
2022-08-01 06:41:47 +00:00
|
|
|
|
|
|
|
# https://github.com/ethereum/builder-specs/blob/v0.2.0/specs/builder.md#signing
|
|
|
|
proc getBuilderSignature*(v: AttachedValidator, fork: Fork,
|
|
|
|
validatorRegistration: ValidatorRegistrationV1):
|
|
|
|
Future[SignatureResult] {.async.} =
|
|
|
|
return
|
|
|
|
case v.kind
|
|
|
|
of ValidatorKind.Local:
|
|
|
|
SignatureResult.ok(get_builder_signature(
|
|
|
|
fork, validatorRegistration, v.data.privateKey).toValidatorSig())
|
|
|
|
of ValidatorKind.Remote:
|
|
|
|
let request = Web3SignerRequest.init(
|
|
|
|
fork, ZERO_HASH, validatorRegistration)
|
|
|
|
await v.signData(request)
|