Remote signing client/server. (#3077)

This commit is contained in:
Eugene Kabanov 2021-11-30 03:20:21 +02:00 committed by GitHub
parent 3aa804035f
commit e62c7c7c37
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 2064 additions and 432 deletions

View File

@ -49,7 +49,7 @@ TOOLS := \
ncli_db \
stack_sizes \
nimbus_validator_client \
nimbus_signing_process
nimbus_signing_node
.PHONY: $(TOOLS)
# bench_bls_sig_agggregation TODO reenable after bls v0.10.1 changes
@ -287,7 +287,7 @@ clean-testnet0:
clean-testnet1:
rm -rf build/data/testnet1*
testnet0 testnet1: | nimbus_beacon_node nimbus_signing_process
testnet0 testnet1: | nimbus_beacon_node nimbus_signing_node
build/nimbus_beacon_node \
--network=$@ \
--log-level="$(RUNTIME_LOG_LEVEL)" \
@ -393,7 +393,7 @@ endef
###
### Pyrmont
###
pyrmont-build: | nimbus_beacon_node nimbus_signing_process
pyrmont-build: | nimbus_beacon_node nimbus_signing_node
# https://www.gnu.org/software/make/manual/html_node/Call-Function.html#Call-Function
pyrmont: | pyrmont-build
@ -420,7 +420,7 @@ clean-pyrmont:
###
### Prater
###
prater-build: | nimbus_beacon_node nimbus_signing_process
prater-build: | nimbus_beacon_node nimbus_signing_node
# https://www.gnu.org/software/make/manual/html_node/Call-Function.html#Call-Function
prater: | prater-build

View File

@ -33,6 +33,7 @@ const
# Maybe there should be a config option for this.
defaultListenAddress* = (static ValidIpAddress.init("0.0.0.0"))
defaultAdminListenAddress* = (static ValidIpAddress.init("127.0.0.1"))
defaultSigningNodeRequestTimeout* = 60
type
BNStartUpCmd* = enum
@ -58,6 +59,9 @@ type
VCStartUpCmd* = enum
VCNoCommand
SNStartUpCmd* = enum
SNNoCommand
RecordCmd* {.pure.} = enum
create = "Create a new ENR"
print = "Print the content of a given ENR"
@ -610,7 +614,84 @@ type
desc: "URL addresses to one or more beacon node HTTP REST APIs",
name: "beacon-node" }: seq[string]
proc defaultDataDir*(config: BeaconNodeConf|ValidatorClientConf): string =
SigningNodeConf* = object
logLevel* {.
desc: "Sets the log level"
defaultValue: "INFO"
name: "log-level" }: string
logStdout* {.
desc: "Specifies what kind of logs should be written to stdout (auto, colors, nocolors, json)"
defaultValueDesc: "auto"
defaultValue: StdoutLogKind.Auto
name: "log-stdout" }: StdoutLogKind
logFile* {.
desc: "Specifies a path for the written Json log file"
name: "log-file" }: Option[OutFile]
nonInteractive* {.
desc: "Do not display interative prompts. Quit on missing configuration"
name: "non-interactive" }: bool
dataDir* {.
desc: "The directory where nimbus will store validator's keys"
defaultValue: config.defaultDataDir()
defaultValueDesc: ""
abbr: "d"
name: "data-dir" }: OutDir
validatorsDirFlag* {.
desc: "A directory containing validator keystores"
name: "validators-dir" }: Option[InputDir]
secretsDirFlag* {.
desc: "A directory containing validator keystore passwords"
name: "secrets-dir" }: Option[InputDir]
serverIdent* {.
desc: "Server identifier which will be used in HTTP Host header"
name: "server-ident" }: Option[string]
requestTimeout* {.
desc: "Request timeout, maximum time that node will wait for remote " &
"client request (in seconds)"
defaultValue: defaultSigningNodeRequestTimeout
name: "request-timeout" }: int
case cmd* {.
command
defaultValue: SNNoCommand }: SNStartUpCmd
of SNNoCommand:
bindPort* {.
desc: "Port for the REST (BETA version) HTTP server"
defaultValue: DefaultEth2RestPort
defaultValueDesc: "5052"
name: "bind-port" }: Port
bindAddress* {.
desc: "Listening address of the REST (BETA version) HTTP server"
defaultValue: defaultAdminListenAddress
defaultValueDesc: "127.0.0.1"
name: "bind-address" }: ValidIpAddress
tlsEnabled* {.
desc: "Use secure TLS communication for REST (BETA version) server"
defaultValue: false
name: "tls" }: bool
tlsCertificate* {.
desc: "Path to SSL certificate file"
name: "tls-cert" }: Option[InputFile]
tlsPrivateKey* {.
desc: "Path to SSL ceritificate's private key"
name: "tls-key" }: Option[InputFile]
AnyConf* = BeaconNodeConf | ValidatorClientConf | SigningNodeConf
proc defaultDataDir*(config: AnyConf): string =
let dataDir = when defined(windows):
"AppData" / "Roaming" / "Nimbus"
elif defined(macosx):
@ -620,16 +701,16 @@ proc defaultDataDir*(config: BeaconNodeConf|ValidatorClientConf): string =
getHomeDir() / dataDir / "BeaconNode"
func dumpDir*(config: BeaconNodeConf|ValidatorClientConf): string =
func dumpDir*(config: AnyConf): string =
config.dataDir / "dump"
func dumpDirInvalid*(config: BeaconNodeConf|ValidatorClientConf): string =
func dumpDirInvalid*(config: AnyConf): string =
config.dumpDir / "invalid" # things that failed validation
func dumpDirIncoming*(config: BeaconNodeConf|ValidatorClientConf): string =
func dumpDirIncoming*(config: AnyConf): string =
config.dumpDir / "incoming" # things that couldn't be validated (missingparent etc)
func dumpDirOutgoing*(config: BeaconNodeConf|ValidatorClientConf): string =
func dumpDirOutgoing*(config: AnyConf): string =
config.dumpDir / "outgoing" # things we produced
proc createDumpDirs*(config: BeaconNodeConf) =
@ -717,10 +798,10 @@ proc parseCmdArg*(T: type enr.Record, p: TaintedString): T
proc completeCmdArg*(T: type enr.Record, val: TaintedString): seq[string] =
return @[]
func validatorsDir*(config: BeaconNodeConf|ValidatorClientConf): string =
func validatorsDir*(config: AnyConf): string =
string config.validatorsDirFlag.get(InputDir(config.dataDir / "validators"))
func secretsDir*(config: BeaconNodeConf|ValidatorClientConf): string =
func secretsDir*(config: AnyConf): string =
string config.secretsDirFlag.get(InputDir(config.dataDir / "secrets"))
func walletsDir*(config: BeaconNodeConf): string =
@ -763,7 +844,7 @@ func outWalletFile*(config: BeaconNodeConf): Option[OutFile] =
else:
fail()
func databaseDir*(config: BeaconNodeConf|ValidatorClientConf): string =
func databaseDir*(config: AnyConf): string =
config.dataDir / "db"
template writeValue*(writer: var JsonWriter,

View File

@ -444,16 +444,7 @@ proc init*(T: type BeaconNode,
onAttestationSent: onAttestationSent,
)
if node.config.inProcessValidators:
node.addLocalValidators()
else:
let cmd = getAppDir() / "nimbus_signing_process".addFileExt(ExeExt)
let args = [$node.config.validatorsDir, $node.config.secretsDir]
let workdir = io2.getCurrentDir().tryGet()
node.vcProcess = try: startProcess(cmd, workdir, args)
except CatchableError as exc: raise exc
except Exception as exc: raiseAssert exc.msg
node.addRemoteValidators()
node.addValidators()
block:
# Add in-process validators to the list of "known" validators such that

View File

@ -0,0 +1,349 @@
# nimbus_sign_node
# Copyright (c) 2018-2021 Status Research & Development GmbH
# 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.
import std/[tables, os, strutils]
import serialization, json_serialization,
json_serialization/std/[options, net],
chronos, presto, presto/secureserver, chronicles, confutils,
stew/[base10, results, byteutils, io2]
import "."/spec/datatypes/[base, altair, phase0, merge],
"."/spec/[crypto, digest, network, signatures, forks],
"."/spec/eth2_apis/[rest_types, eth2_rest_serialization],
"."/rpc/rest_constants,
"."/[conf, version, nimbus_binary_common],
"."/validators/[keystore_management, validator_pool]
const
NimbusSigningNodeIdent = "nimbus_remote_signer/" & fullVersionStr
type
SigningNodeKind* {.pure.} = enum
NonSecure, Secure
SigningNodeServer* = object
case kind: SigningNodeKind
of SigningNodeKind.Secure:
sserver: SecureRestServerRef
of SigningNodeKind.NonSecure:
nserver: RestServerRef
SigningNode* = object
config: SigningNodeConf
attachedValidators: ValidatorPool
signingServer: SigningNodeServer
keysList: string
proc getRouter*(): RestRouter
proc router(sn: SigningNode): RestRouter =
case sn.signingServer.kind
of SigningNodeKind.Secure:
sn.signingServer.sserver.router
of SigningNodeKind.NonSecure:
sn.signingServer.nserver.router
proc start(sn: SigningNode) =
case sn.signingServer.kind
of SigningNodeKind.Secure:
sn.signingServer.sserver.start()
of SigningNodeKind.NonSecure:
sn.signingServer.nserver.start()
proc stop(sn: SigningNode) {.async.} =
case sn.signingServer.kind
of SigningNodeKind.Secure:
await sn.signingServer.sserver.stop()
of SigningNodeKind.NonSecure:
await sn.signingServer.nserver.stop()
proc close(sn: SigningNode) {.async.} =
case sn.signingServer.kind
of SigningNodeKind.Secure:
await sn.signingServer.sserver.stop()
of SigningNodeKind.NonSecure:
await sn.signingServer.nserver.stop()
proc loadTLSCert(pathName: InputFile): Result[TLSCertificate, cstring] =
let data =
block:
let res = io2.readAllChars(string(pathName))
if res.isErr():
return err("Could not read certificate file")
res.get()
let cert =
try:
TLSCertificate.init(data)
except TLSStreamProtocolError:
return err("Invalid certificate or incorrect file format")
ok(cert)
proc loadTLSKey(pathName: InputFile): Result[TLSPrivateKey, cstring] =
let data =
block:
let res = io2.readAllChars(string(pathName))
if res.isErr():
return err("Could not read private key file")
res.get()
let key =
try:
TLSPrivateKey.init(data)
except TLSStreamProtocolError:
return err("Invalid private key or incorrect file format")
ok(key)
proc initValidators(sn: var SigningNode): bool =
info "Initializaing validators", path = sn.config.validatorsDir()
var publicKeyIdents: seq[string]
for item in sn.config.validatorItems():
case item.kind
of ValidatorKind.Local:
let pubkey = item.privateKey.toPubKey().toPubKey()
sn.attachedValidators.addLocalValidator(item)
publicKeyIdents.add("\"0x" & pubkey.toHex() & "\"")
of ValidatorKind.Remote:
error "Signing node do not support remote validators",
validator_pubkey = item.publicKey
return false
sn.keysList = "[" & publicKeyIdents.join(", ") & "]"
true
proc init(t: typedesc[SigningNode], config: SigningNodeConf): SigningNode =
var sn = SigningNode(config: config)
if not(initValidators(sn)):
fatal "Could not find/initialize local validators"
quit 1
let
address = initTAddress(config.bindAddress, config.bindPort)
serverFlags = {HttpServerFlags.QueryCommaSeparatedArray,
HttpServerFlags.NotifyDisconnect}
timeout =
if config.requestTimeout < 0:
warn "Negative value of request timeout, using default instead"
seconds(defaultSigningNodeRequestTimeout)
else:
seconds(config.requestTimeout)
serverIdent =
if config.serverIdent.isSome():
config.serverIdent.get()
else:
NimbusSigningNodeIdent
sn.signingServer =
if config.tlsEnabled:
if config.tlsCertificate.isNone():
fatal "TLS certificate path is missing, please use --tls-cert option"
quit 1
if config.tlsPrivateKey.isNone():
fatal "TLS private key path is missing, please use --tls-key option"
quit 1
let cert =
block:
let res = loadTLSCert(config.tlsCertificate.get())
if res.isErr():
fatal "Could not initialize SSL certificate", reason = $res.error()
quit 1
res.get()
let key =
block:
let res = loadTLSKey(config.tlsPrivateKey.get())
if res.isErr():
fatal "Could not initialize SSL private key", reason = $res.error()
quit 1
res.get()
let res = SecureRestServerRef.new(getRouter(), address, key, cert,
serverFlags = serverFlags,
httpHeadersTimeout = timeout,
serverIdent = serverIdent)
if res.isErr():
fatal "HTTPS(REST) server could not be started", address = $address,
reason = $res.error()
quit 1
SigningNodeServer(kind: SigningNodeKind.Secure, sserver: res.get())
else:
let res = RestServerRef.new(getRouter(), address,
serverFlags = serverFlags,
httpHeadersTimeout = timeout,
serverIdent = serverIdent)
if res.isErr():
fatal "HTTP(REST) server could not be started", address = $address,
reason = $res.error()
quit 1
SigningNodeServer(kind: SigningNodeKind.NonSecure, nserver: res.get())
sn
template errorResponse(code: HttpCode, message: string): RestApiResponse =
RestApiResponse.response("{\"error\": \"" & message & "\"}", code)
template signatureResponse(code: HttpCode, signature: string): RestApiResponse =
RestApiResponse.response("{\"signature\": \"0x" & signature & "\"}", code)
proc installApiHandlers*(node: SigningNode) =
var router = node.router()
router.api(MethodGet, "/api/v1/eth2/publicKeys") do () -> RestApiResponse:
return RestApiResponse.response(node.keysList, Http200,
"application/json")
router.api(MethodGet, "/upcheck") do () -> RestApiResponse:
return RestApiResponse.response("{\"status\": \"OK\"}", Http200,
"application/json")
router.api(MethodPost, "/api/v1/eth2/sign/{validator_key}") do (
validator_key: ValidatorPubKey,
contentBody: Option[ContentBody]) -> RestApiResponse:
let request =
block:
if contentBody.isNone():
return errorResponse(Http400, EmptyRequestBodyError)
let res = decodeBody(Web3SignerRequest, contentBody.get())
if res.isErr():
return errorResponse(Http400, $res.error())
res.get()
let validator =
block:
if validator_key.isErr():
return errorResponse(Http400, InvalidValidatorPublicKey)
let key = validator_key.get()
let validator = node.attachedValidators.getValidator(key)
if isNil(validator):
return errorResponse(Http404, ValidatorNotFoundError)
validator
return
case request.kind
of Web3SignerRequestKind.AggregationSlot:
let
forkInfo = request.forkInfo.get()
cooked = get_slot_signature(forkInfo.fork,
forkInfo.genesisValidatorsRoot,
request.aggregationSlot.slot, validator.data.privateKey)
signature = cooked.toValidatorSig().toHex()
signatureResponse(Http200, signature)
of Web3SignerRequestKind.AggregateAndProof:
let
forkInfo = request.forkInfo.get()
cooked = get_aggregate_and_proof_signature(forkInfo.fork,
forkInfo.genesisValidatorsRoot, request.aggregateAndProof,
validator.data.privateKey)
signature = cooked.toValidatorSig().toHex()
signatureResponse(Http200, signature)
of Web3SignerRequestKind.Attestation:
let
forkInfo = request.forkInfo.get()
cooked = get_attestation_signature(forkInfo.fork,
forkInfo.genesisValidatorsRoot, request.attestation,
validator.data.privateKey)
signature = cooked.toValidatorSig().toHex()
signatureResponse(Http200, signature)
of Web3SignerRequestKind.Block:
let
forkInfo = request.forkInfo.get()
blck = request.blck
blockRoot = hash_tree_root(blck)
cooked = get_block_signature(forkInfo.fork,
forkInfo.genesisValidatorsRoot, blck.slot, blockRoot,
validator.data.privateKey)
signature = cooked.toValidatorSig().toHex()
signatureResponse(Http200, signature)
of Web3SignerRequestKind.BlockV2:
let
forkInfo = request.forkInfo.get()
forked = request.beaconBlock
blockRoot = hash_tree_root(forked)
cooked =
withBlck(forked):
get_block_signature(forkInfo.fork,
forkInfo.genesisValidatorsRoot, blck.slot, blockRoot,
validator.data.privateKey)
signature = cooked.toValidatorSig().toHex()
signatureResponse(Http200, signature)
of Web3SignerRequestKind.Deposit:
let
data = DepositMessage(pubkey: request.deposit.pubkey,
withdrawal_credentials: request.deposit.withdrawalCredentials,
amount: request.deposit.amount)
cooked = get_deposit_signature(data,
request.deposit.genesisForkVersion, validator.data.privateKey)
signature = cooked.toValidatorSig().toHex()
signatureResponse(Http200, signature)
of Web3SignerRequestKind.RandaoReveal:
let
forkInfo = request.forkInfo.get()
cooked = get_epoch_signature(forkInfo.fork,
forkInfo.genesisValidatorsRoot, request.randaoReveal.epoch,
validator.data.privateKey)
signature = cooked.toValidatorSig().toHex()
signatureResponse(Http200, signature)
of Web3SignerRequestKind.VoluntaryExit:
let
forkInfo = request.forkInfo.get()
cooked = get_voluntary_exit_signature(forkInfo.fork,
forkInfo.genesisValidatorsRoot, request.voluntaryExit,
validator.data.privateKey)
signature = cooked.toValidatorSig().toHex()
signatureResponse(Http200, signature)
of Web3SignerRequestKind.SyncCommitteeMessage:
let
forkInfo = request.forkInfo.get()
msg = request.syncCommitteeMessage
cooked = get_sync_committee_message_signature(forkInfo.fork,
forkInfo.genesisValidatorsRoot, msg.slot, msg.beaconBlockRoot,
validator.data.privateKey)
signature = cooked.toValidatorSig().toHex()
signatureResponse(Http200, signature)
of Web3SignerRequestKind.SyncCommitteeSelectionProof:
let
forkInfo = request.forkInfo.get()
msg = request.syncAggregatorSelectionData
cooked = get_sync_aggregator_selection_data_signature(forkInfo.fork,
forkInfo.genesisValidatorsRoot, msg.slot, msg.subcommittee_index,
validator.data.privateKey)
signature = cooked.toValidatorSig().toHex()
signatureResponse(Http200, signature)
of Web3SignerRequestKind.SyncCommitteeContributionAndProof:
let
forkInfo = request.forkInfo.get()
msg = request.syncCommitteeContributionAndProof
cooked = get_sync_committee_contribution_and_proof_signature(
forkInfo.fork, forkInfo.genesisValidatorsRoot, msg,
validator.data.privateKey)
signature = cooked.toValidatorSig().toHex()
signatureResponse(Http200, signature)
proc validate(key: string, value: string): int =
case key
of "{validator_key}":
0
else:
1
proc getRouter*(): RestRouter =
RestRouter.init(validate)
programMain:
let config = makeBannerAndConfig("Nimbus signing node " & fullVersionStr,
SigningNodeConf)
setupLogging(config.logLevel, config.logStdout, config.logFile)
case config.cmd
of SNNoCommand:
var sn = SigningNode.init(config)
notice "Launching signing node", version = fullVersionStr,
cmdParams = commandLineParams(), config,
validators_count = sn.attachedValidators.count()
sn.installApiHandlers()
sn.start()
try:
runForever()
finally:
waitFor sn.stop()
waitFor sn.close()
discard sn.stop()

View File

@ -0,0 +1,10 @@
-d:"chronicles_sinks=textlines[dynamic],json[dynamic]"
-d:"chronicles_runtime_filtering=on"
-d:"chronicles_disable_thread_id"
@if release:
-d:"chronicles_line_numbers:0"
@end
# Use only `secp256k1` public key cryptography as an identity in LibP2P.
-d:"libp2p_pki_schemes=secp256k1"

View File

@ -1,32 +0,0 @@
# beacon_chain
# Copyright (c) 2018-2021 Status Research & Development GmbH
# 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.
{.push raises: [Defect].}
import std/[os, strutils, tables]
import "."/validators/[keystore_management]
{.pop.} # TODO moduletests exceptions
programMain:
var validators: Table[ValidatorPubKey, ValidatorPrivateItem]
# load and send all public keys so the BN knows for which ones to ping us
doAssert paramCount() == 2
for curr in validatorKeysFromDirs(paramStr(1), paramStr(2)):
validators[curr.privateKey.toPubKey().toPubKey()] = curr
echo curr.privateKey.toPubKey
echo "end"
# simple format: `<pubkey> <eth2digest_to_sign>` => `<signature>`
while true:
let args = stdin.readLine.split(" ")
doAssert args.len == 2
let item = validators[ValidatorPubKey.fromHex(args[0]).get()]
echo blsSign(item.privateKey,
Eth2Digest.fromHex(args[1]).data).toValidatorSig()

View File

@ -0,0 +1,185 @@
import ../spec/helpers
const
MaxEpoch* = compute_epoch_at_slot(not(0'u64))
BlockValidationError* =
"The block failed validation, but was successfully broadcast anyway. It " &
"was not integrated into the beacon node's database."
BlockValidationSuccess* =
"The block was validated successfully and has been broadcast"
BeaconNodeInSyncError* =
"Beacon node is currently syncing and not serving request on that endpoint"
BlockNotFoundError* =
"Block header/data has not been found"
BlockProduceError* =
"Could not produce the block"
EmptyRequestBodyError* =
"Empty request's body"
InvalidBlockObjectError* =
"Unable to decode block object(s)"
InvalidAttestationObjectError* =
"Unable to decode attestation object(s)"
AttestationValidationError* =
"Some errors happened while validating attestation(s)"
AttestationValidationSuccess* =
"Attestation object(s) was broadcasted"
InvalidAttesterSlashingObjectError* =
"Unable to decode attester slashing object(s)"
AttesterSlashingValidationError* =
"Invalid attester slashing, it will never pass validation so it's rejected"
AttesterSlashingValidationSuccess* =
"Attester slashing object was broadcasted"
InvalidProposerSlashingObjectError* =
"Unable to decode proposer slashing object(s)"
ProposerSlashingValidationError* =
"Invalid proposer slashing, it will never pass validation so it's rejected"
ProposerSlashingValidationSuccess* =
"Proposer slashing object was broadcasted"
InvalidVoluntaryExitObjectError* =
"Unable to decode voluntary exit object(s)"
VoluntaryExitValidationError* =
"Invalid voluntary exit, it will never pass validation so it's rejected"
VoluntaryExitValidationSuccess* =
"Voluntary exit object(s) was broadcasted"
InvalidAggregateAndProofObjectError* =
"Unable to decode aggregate and proof object(s)"
AggregateAndProofValidationError* =
"Invalid aggregate and proof, it will never pass validation so it's " &
"rejected"
AggregateAndProofValidationSuccess* =
"Aggregate and proof object(s) was broadcasted"
BeaconCommitteeSubscriptionSuccess* =
"Beacon node processed committee subscription request(s)"
SyncCommitteeSubscriptionSuccess* =
"Beacon node processed sync committee subscription request(s)"
InvalidParentRootValueError* =
"Invalid parent root value"
MissingSlotValueError* =
"Missing `slot` value"
InvalidSlotValueError* =
"Invalid slot value"
MissingCommitteeIndexValueError* =
"Missing `committee_index` value"
InvalidCommitteeIndexValueError* =
"Invalid committee index value"
MissingAttestationDataRootValueError* =
"Missing `attestation_data_root` value"
InvalidAttestationDataRootValueError* =
"Invalid attestation data root value"
UnableToGetAggregatedAttestationError* =
"Unable to retrieve an aggregated attestation"
MissingRandaoRevealValue* =
"Missing `randao_reveal` value"
InvalidRandaoRevealValue* =
"Invalid randao reveal value"
InvalidGraffitiBytesValye* =
"Invalid graffiti bytes value"
InvalidEpochValueError* =
"Invalid epoch value"
EpochFromFutureError* =
"Epoch value is far from the future"
InvalidStateIdValueError* =
"Invalid state identifier value"
InvalidBlockIdValueError* =
"Invalid block identifier value"
InvalidValidatorIdValueError* =
"Invalid validator's identifier value(s)"
MaximumNumberOfValidatorIdsError* =
"Maximum number of validator identifier values exceeded"
InvalidValidatorStatusValueError* =
"Invalid validator's status value error"
InvalidValidatorIndexValueError* =
"Invalid validator's index value(s)"
EmptyValidatorIndexArrayError* =
"Empty validator's index array"
InvalidSubscriptionRequestValueError* =
"Invalid subscription request object(s)"
ValidatorNotFoundError* =
"Could not find validator"
ValidatorStatusNotFoundError* =
"Could not obtain validator's status"
TooHighValidatorIndexValueError* =
"Validator index exceeds maximum number of validators allowed"
UnsupportedValidatorIndexValueError* =
"Validator index exceeds maximum supported number of validators"
StateNotFoundError* =
"Could not get requested state"
SlotNotFoundError* =
"Slot number is too far away"
SlotNotInNextWallSlotEpochError* =
"Requested slot not in next wall-slot epoch"
SlotFromThePastError* =
"Requested slot from the past"
SlotFromTheIncorrectForkError* =
"Requested slot is from incorrect fork"
EpochFromTheIncorrectForkError* =
"Requested epoch is from incorrect fork"
ProposerNotFoundError* =
"Could not find proposer for the head and slot"
NoHeadForSlotError* =
"Cound not find head for slot"
EpochOverflowValueError* =
"Requesting epoch for which slot would overflow"
InvalidPeerStateValueError* =
"Invalid peer's state value(s) error"
InvalidPeerDirectionValueError* =
"Invalid peer's direction value(s) error"
InvalidPeerIdValueError* =
"Invalid peer's id value(s) error"
PeerNotFoundError* =
"Peer not found"
InvalidLogLevelValueError* =
"Invalid log level value error"
ContentNotAcceptableError* =
"Could not find out accepted content type"
InvalidAcceptError* =
"Incorrect accept response type"
MissingSubCommitteeIndexValueError* =
"Missing `subcommittee_index` value"
InvalidSubCommitteeIndexValueError* =
"Invalid `subcommittee_index` value"
MissingBeaconBlockRootValueError* =
"Missing `beacon_block_root` value"
InvalidBeaconBlockRootValueError* =
"Invalid `beacon_block_root` value"
EpochOutsideSyncCommitteePeriodError* =
"Epoch is outside the sync committee period of the state"
InvalidSyncCommitteeSignatureMessageError* =
"Unable to decode sync committee message(s)"
InvalidSyncCommitteeSubscriptionRequestError* =
"Unable to decode sync committee subscription request(s)"
InvalidContributionAndProofMessageError* =
"Unable to decode contribute and proof message(s)"
SyncCommitteeMessageValidationError* =
"Some errors happened while validating sync committee message(s)"
SyncCommitteeMessageValidationSuccess* =
"Sync committee message(s) was broadcasted"
ContributionAndProofValidationError* =
"Some errors happened while validating contribution and proof(s)"
ContributionAndProofValidationSuccess* =
"Contribution and proof(s) was broadcasted"
ProduceContributionError* =
"Unable to produce contribution using the passed parameters"
InternalServerError* =
"Internal server error"
NoImplementationError* =
"Not implemented yet"
KeystoreAdditionFailure* =
"Could not add some keystores"
InvalidKeystoreObjects* =
"Invalid keystore objects found"
KeystoreAdditionSuccess* =
"All keystores has been added"
KeystoreModificationFailure* =
"Could not change keystore(s) state"
KeystoreModificationSuccess* =
"Keystore(s) state was successfully modified"
KeystoreRemovalSuccess* =
"Keystore(s) was successfully removed"
KeystoreRemovalFailure* =
"Could not remove keystore(s)"
InvalidValidatorPublicKey* =
"Invalid validator's public key(s) found"
BadRequestFormatError* =
"Bad request format"

View File

@ -61,19 +61,19 @@ proc init*(t: typedesc[ValidatorListItem],
ValidatorListItem(pubkey: key.pubkey, status: $key.flag,
description: key.description, path: string(key.path))
proc listValidators*(conf: AnyConf): seq[StoredValidatorKey] {.
proc listValidators*(config: AnyConf): seq[StoredValidatorKey] {.
raises: [Defect].} =
var validators: seq[StoredValidatorKey]
try:
for kind, file in walkDir(conf.validatorsDir()):
for kind, file in walkDir(config.validatorsDir()):
if kind == pcDir:
let keyName = splitFile(file).name
let rkey = ValidatorPubKey.fromHex(keyName)
if rkey.isErr():
# Skip folders which represents invalid public key
continue
let secretFile = conf.secretsDir() / keyName
let keystorePath = conf.validatorsDir() / keyName
let secretFile = config.secretsDir() / keyName
let keystorePath = config.validatorsDir() / keyName
let keystoreFile = keystorePath / KeystoreFileName
let disableFile = keystorePath / DisableFileName

View File

@ -4,192 +4,12 @@ import std/options,
../spec/[forks],
../spec/eth2_apis/[rest_types, eth2_rest_serialization],
../beacon_node,
../consensus_object_pools/blockchain_dag
../consensus_object_pools/blockchain_dag,
./rest_constants
export
options, eth2_rest_serialization, blockchain_dag, presto, rest_types
const
MaxEpoch* = compute_epoch_at_slot(not(0'u64))
BlockValidationError* =
"The block failed validation, but was successfully broadcast anyway. It " &
"was not integrated into the beacon node's database."
BlockValidationSuccess* =
"The block was validated successfully and has been broadcast"
BeaconNodeInSyncError* =
"Beacon node is currently syncing and not serving request on that endpoint"
BlockNotFoundError* =
"Block header/data has not been found"
BlockProduceError* =
"Could not produce the block"
EmptyRequestBodyError* =
"Empty request's body"
InvalidBlockObjectError* =
"Unable to decode block object(s)"
InvalidAttestationObjectError* =
"Unable to decode attestation object(s)"
AttestationValidationError* =
"Some errors happened while validating attestation(s)"
AttestationValidationSuccess* =
"Attestation object(s) was broadcasted"
InvalidAttesterSlashingObjectError* =
"Unable to decode attester slashing object(s)"
AttesterSlashingValidationError* =
"Invalid attester slashing, it will never pass validation so it's rejected"
AttesterSlashingValidationSuccess* =
"Attester slashing object was broadcasted"
InvalidProposerSlashingObjectError* =
"Unable to decode proposer slashing object(s)"
ProposerSlashingValidationError* =
"Invalid proposer slashing, it will never pass validation so it's rejected"
ProposerSlashingValidationSuccess* =
"Proposer slashing object was broadcasted"
InvalidVoluntaryExitObjectError* =
"Unable to decode voluntary exit object(s)"
VoluntaryExitValidationError* =
"Invalid voluntary exit, it will never pass validation so it's rejected"
VoluntaryExitValidationSuccess* =
"Voluntary exit object(s) was broadcasted"
InvalidAggregateAndProofObjectError* =
"Unable to decode aggregate and proof object(s)"
AggregateAndProofValidationError* =
"Invalid aggregate and proof, it will never pass validation so it's " &
"rejected"
AggregateAndProofValidationSuccess* =
"Aggregate and proof object(s) was broadcasted"
BeaconCommitteeSubscriptionSuccess* =
"Beacon node processed committee subscription request(s)"
SyncCommitteeSubscriptionSuccess* =
"Beacon node processed sync committee subscription request(s)"
InvalidParentRootValueError* =
"Invalid parent root value"
MissingSlotValueError* =
"Missing `slot` value"
InvalidSlotValueError* =
"Invalid slot value"
MissingCommitteeIndexValueError* =
"Missing `committee_index` value"
InvalidCommitteeIndexValueError* =
"Invalid committee index value"
MissingAttestationDataRootValueError* =
"Missing `attestation_data_root` value"
InvalidAttestationDataRootValueError* =
"Invalid attestation data root value"
UnableToGetAggregatedAttestationError* =
"Unable to retrieve an aggregated attestation"
MissingRandaoRevealValue* =
"Missing `randao_reveal` value"
InvalidRandaoRevealValue* =
"Invalid randao reveal value"
InvalidGraffitiBytesValye* =
"Invalid graffiti bytes value"
InvalidEpochValueError* =
"Invalid epoch value"
EpochFromFutureError* =
"Epoch value is far from the future"
InvalidStateIdValueError* =
"Invalid state identifier value"
InvalidBlockIdValueError* =
"Invalid block identifier value"
InvalidValidatorIdValueError* =
"Invalid validator's identifier value(s)"
MaximumNumberOfValidatorIdsError* =
"Maximum number of validator identifier values exceeded"
InvalidValidatorStatusValueError* =
"Invalid validator's status value error"
InvalidValidatorIndexValueError* =
"Invalid validator's index value(s)"
EmptyValidatorIndexArrayError* =
"Empty validator's index array"
InvalidSubscriptionRequestValueError* =
"Invalid subscription request object(s)"
ValidatorNotFoundError* =
"Could not find validator"
ValidatorStatusNotFoundError* =
"Could not obtain validator's status"
TooHighValidatorIndexValueError* =
"Validator index exceeds maximum number of validators allowed"
UnsupportedValidatorIndexValueError* =
"Validator index exceeds maximum supported number of validators"
StateNotFoundError* =
"Could not get requested state"
SlotNotFoundError* =
"Slot number is too far away"
SlotNotInNextWallSlotEpochError* =
"Requested slot not in next wall-slot epoch"
SlotFromThePastError* =
"Requested slot from the past"
SlotFromTheIncorrectForkError* =
"Requested slot is from incorrect fork"
EpochFromTheIncorrectForkError* =
"Requested epoch is from incorrect fork"
ProposerNotFoundError* =
"Could not find proposer for the head and slot"
NoHeadForSlotError* =
"Cound not find head for slot"
EpochOverflowValueError* =
"Requesting epoch for which slot would overflow"
InvalidPeerStateValueError* =
"Invalid peer's state value(s) error"
InvalidPeerDirectionValueError* =
"Invalid peer's direction value(s) error"
InvalidPeerIdValueError* =
"Invalid peer's id value(s) error"
PeerNotFoundError* =
"Peer not found"
InvalidLogLevelValueError* =
"Invalid log level value error"
ContentNotAcceptableError* =
"Could not find out accepted content type"
InvalidAcceptError* =
"Incorrect accept response type"
MissingSubCommitteeIndexValueError* =
"Missing `subcommittee_index` value"
InvalidSubCommitteeIndexValueError* =
"Invalid `subcommittee_index` value"
MissingBeaconBlockRootValueError* =
"Missing `beacon_block_root` value"
InvalidBeaconBlockRootValueError* =
"Invalid `beacon_block_root` value"
EpochOutsideSyncCommitteePeriodError* =
"Epoch is outside the sync committee period of the state"
InvalidSyncCommitteeSignatureMessageError* =
"Unable to decode sync committee message(s)"
InvalidSyncCommitteeSubscriptionRequestError* =
"Unable to decode sync committee subscription request(s)"
InvalidContributionAndProofMessageError* =
"Unable to decode contribute and proof message(s)"
SyncCommitteeMessageValidationError* =
"Some errors happened while validating sync committee message(s)"
SyncCommitteeMessageValidationSuccess* =
"Sync committee message(s) was broadcasted"
ContributionAndProofValidationError* =
"Some errors happened while validating contribution and proof(s)"
ContributionAndProofValidationSuccess* =
"Contribution and proof(s) was broadcasted"
ProduceContributionError* =
"Unable to produce contribution using the passed parameters"
InternalServerError* =
"Internal server error"
NoImplementationError* =
"Not implemented yet"
KeystoreAdditionFailure* =
"Could not add some keystores"
InvalidKeystoreObjects* =
"Invalid keystore objects found"
KeystoreAdditionSuccess* =
"All keystores has been added"
KeystoreModificationFailure* =
"Could not change keystore(s) state"
KeystoreModificationSuccess* =
"Keystore(s) state was successfully modified"
KeystoreRemovalSuccess* =
"Keystore(s) was successfully removed"
KeystoreRemovalFailure* =
"Could not remove keystore(s)"
InvalidValidatorPublicKey* =
"Invalid validator's public key(s) found"
options, eth2_rest_serialization, blockchain_dag, presto, rest_types,
rest_constants
type
ValidatorIndexError* {.pure.} = enum

View File

@ -53,7 +53,8 @@ type
phase0.SignedBeaconBlock |
altair.SignedBeaconBlock |
SignedVoluntaryExit |
SyncSubcommitteeIndex
SyncSubcommitteeIndex |
Web3SignerRequest
EncodeArrays* =
seq[ValidatorIndex] |
@ -72,7 +73,11 @@ type
RestAttestationError |
RestGenericError |
GetBlockV2Response |
GetStateV2Response
GetStateV2Response |
Web3SignerStatusResponse |
Web3SignerKeysResponse |
Web3SignerSignatureResponse |
Web3SignerErrorResponse
# These types may be extended with additional fields in the future.
# Locally unknown fields are silently ignored when decoding them.
@ -319,6 +324,14 @@ proc sszResponse*(t: typedesc[RestApiResponse], data: auto): RestApiResponse =
template hexOriginal(data: openarray[byte]): string =
"0x" & ncrutils.toHex(data, true)
proc decodeJsonString*[T](t: typedesc[T],
data: JsonString,
requireAllFields = true): Result[T, cstring] =
try:
ok(RestJson.decode(string(data), T, requireAllFields = requireAllFields))
except SerializationError:
err("Unable to deserialize data")
## uint64
proc writeValue*(w: var JsonWriter[RestJson], value: uint64) {.
raises: [IOError, Defect].} =
@ -945,6 +958,355 @@ proc readValue*(reader: var JsonReader[RestJson],
else:
reader.raiseUnexpectedValue($res.error())
# Web3SignerRequest
proc writeValue*(writer: var JsonWriter[RestJson],
value: Web3SignerRequest) {.
raises: [IOError, Defect].} =
case value.kind
of Web3SignerRequestKind.AggregationSlot:
doAssert(value.forkInfo.isSome(),
"forkInfo should be set for this type of request")
writer.writeField("type", "AGGREGATION_SLOT")
writer.writeField("fork_info", value.forkInfo.get())
if isSome(value.signingRoot):
writer.writeField("signingRoot", value.signingRoot)
writer.writeField("aggregation_slot", value.aggregationSlot)
of Web3SignerRequestKind.AggregateAndProof:
doAssert(value.forkInfo.isSome(),
"forkInfo should be set for this type of request")
writer.writeField("type", "AGGREGATE_AND_PROOF")
writer.writeField("fork_info", value.forkInfo.get())
if isSome(value.signingRoot):
writer.writeField("signingRoot", value.signingRoot)
writer.writeField("aggregate_and_proof", value.aggregateAndProof)
of Web3SignerRequestKind.Attestation:
doAssert(value.forkInfo.isSome(),
"forkInfo should be set for this type of request")
writer.writeField("type", "ATTESTATION")
writer.writeField("fork_info", value.forkInfo.get())
if isSome(value.signingRoot):
writer.writeField("signingRoot", value.signingRoot)
writer.writeField("attestation", value.attestation)
of Web3SignerRequestKind.Block:
doAssert(value.forkInfo.isSome(),
"forkInfo should be set for this type of request")
writer.writeField("type", "BLOCK")
writer.writeField("fork_info", value.forkInfo.get())
if isSome(value.signingRoot):
writer.writeField("signingRoot", value.signingRoot)
writer.writeField("block", value.blck)
of Web3SignerRequestKind.BlockV2:
doAssert(value.forkInfo.isSome(),
"forkInfo should be set for this type of request")
writer.writeField("type", "BLOCK_V2")
writer.writeField("fork_info", value.forkInfo.get())
if isSome(value.signingRoot):
writer.writeField("signingRoot", value.signingRoot)
writer.writeField("beacon_block", value.beaconBlock)
of Web3SignerRequestKind.Deposit:
writer.writeField("type", "DEPOSIT")
if isSome(value.signingRoot):
writer.writeField("signingRoot", value.signingRoot)
writer.writeField("deposit", value.deposit)
of Web3SignerRequestKind.RandaoReveal:
doAssert(value.forkInfo.isSome(),
"forkInfo should be set for this type of request")
writer.writeField("type", "RANDAO_REVEAL")
writer.writeField("fork_info", value.forkInfo.get())
if isSome(value.signingRoot):
writer.writeField("signingRoot", value.signingRoot)
writer.writeField("randao_reveal", value.randaoReveal)
of Web3SignerRequestKind.VoluntaryExit:
doAssert(value.forkInfo.isSome(),
"forkInfo should be set for this type of request")
writer.writeField("type", "VOLUNTARY_EXIT")
writer.writeField("fork_info", value.forkInfo.get())
if isSome(value.signingRoot):
writer.writeField("signingRoot", value.signingRoot)
writer.writeField("voluntary_exit", value.voluntaryExit)
of Web3SignerRequestKind.SyncCommitteeMessage:
doAssert(value.forkInfo.isSome(),
"forkInfo should be set for this type of request")
writer.writeField("type", "SYNC_COMMITTEE_MESSAGE")
writer.writeField("fork_info", value.forkInfo.get())
if isSome(value.signingRoot):
writer.writeField("signingRoot", value.signingRoot)
writer.writeField("sync_committee_message", value.syncCommitteeMessage)
of Web3SignerRequestKind.SyncCommitteeSelectionProof:
doAssert(value.forkInfo.isSome(),
"forkInfo should be set for this type of request")
writer.writeField("type", "SYNC_COMMITTEE_SELECTION_PROOF")
writer.writeField("fork_info", value.forkInfo.get())
if isSome(value.signingRoot):
writer.writeField("signingRoot", value.signingRoot)
writer.writeField("sync_aggregator_selection_data",
value.syncAggregatorSelectionData)
of Web3SignerRequestKind.SyncCommitteeContributionAndProof:
doAssert(value.forkInfo.isSome(),
"forkInfo should be set for this type of request")
writer.writeField("type", "SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF")
writer.writeField("fork_info", value.forkInfo.get())
if isSome(value.signingRoot):
writer.writeField("signingRoot", value.signingRoot)
writer.writeField("contribution_and_proof",
value.syncCommitteeContributionAndProof)
proc readValue*(reader: var JsonReader[RestJson],
value: var Web3SignerRequest) {.
raises: [IOError, SerializationError, Defect].} =
var
requestKind: Option[Web3SignerRequestKind]
forkInfo: Option[Web3SignerForkInfo]
signingRoot: Option[Eth2Digest]
data: Option[JsonString]
dataName: string
for fieldName in readObjectFields(reader):
case fieldName
of "type":
if requestKind.isSome():
reader.raiseUnexpectedField("Multiple `type` fields found",
"Web3SignerRequest")
let vres = reader.readValue(string)
requestKind = some(
case vres
of "AGGREGATION_SLOT":
Web3SignerRequestKind.AggregationSlot
of "AGGREGATE_AND_PROOF":
Web3SignerRequestKind.AggregateAndProof
of "ATTESTATION":
Web3SignerRequestKind.Attestation
of "BLOCK":
Web3SignerRequestKind.Block
of "BLOCK_V2":
Web3SignerRequestKind.BlockV2
of "DEPOSIT":
Web3SignerRequestKind.Deposit
of "RANDAO_REVEAL":
Web3SignerRequestKind.RandaoReveal
of "VOLUNTARY_EXIT":
Web3SignerRequestKind.VoluntaryExit
of "SYNC_COMMITTEE_MESSAGE":
Web3SignerRequestKind.SyncCommitteeMessage
of "SYNC_COMMITTEE_SELECTION_PROOF":
Web3SignerRequestKind.SyncCommitteeSelectionProof
of "SYNC_COMMITTEE_CONTRIBUTION_AND_PROOF":
Web3SignerRequestKind.SyncCommitteeContributionAndProof
else:
reader.raiseUnexpectedValue("Unexpected `type` value")
)
of "fork_info":
if forkInfo.isSome():
reader.raiseUnexpectedField("Multiple `fork_info` fields found",
"Web3SignerRequest")
forkInfo = some(reader.readValue(Web3SignerForkInfo))
of "signingRoot":
if signingRoot.isSome():
reader.raiseUnexpectedField("Multiple `signingRoot` fields found",
"Web3SignerRequest")
signingRoot = some(reader.readValue(Eth2Digest))
of "aggregation_slot", "aggregate_and_proof", "block", "beacon_block",
"randao_reveal", "voluntary_exit", "sync_committee_message",
"sync_aggregator_selection_data", "contribution_and_proof":
if data.isSome():
reader.raiseUnexpectedField("Multiple data fields found",
"Web3SignerRequest")
dataName = fieldName
data = some(reader.readValue(JsonString))
else:
# We ignore all unknown fields.
discard
if requestKind.isNone():
reader.raiseUnexpectedValue("Field `type` is missing")
value =
case requestKind.get()
of Web3SignerRequestKind.AggregationSlot:
if dataName != "aggregation_slot":
reader.raiseUnexpectedValue("Field `aggregation_slot` is missing")
if forkInfo.isNone():
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(Web3SignerAggregationSlotData,
data.get(), true)
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `aggregation_slot` format")
res.get()
Web3SignerRequest(kind: Web3SignerRequestKind.AggregationSlot,
forkInfo: forkInfo, signingRoot: signingRoot, aggregationSlot: data
)
of Web3SignerRequestKind.AggregateAndProof:
if dataName != "aggregate_and_proof":
reader.raiseUnexpectedValue("Field `aggregate_and_proof` is missing")
if forkInfo.isNone():
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(AggregateAndProof, data.get(), true)
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `aggregate_and_proof` format")
res.get()
Web3SignerRequest(
kind: Web3SignerRequestKind.AggregateAndProof,
forkInfo: forkInfo, signingRoot: signingRoot, aggregateAndProof: data
)
of Web3SignerRequestKind.Attestation:
if dataName != "attestation":
reader.raiseUnexpectedValue("Field `attestation` is missing")
if forkInfo.isNone():
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(AttestationData, data.get(), true)
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `attestation` format")
res.get()
Web3SignerRequest(
kind: Web3SignerRequestKind.Attestation,
forkInfo: forkInfo, signingRoot: signingRoot, attestation: data
)
of Web3SignerRequestKind.Block:
if dataName != "block":
reader.raiseUnexpectedValue("Field `block` is missing")
if forkInfo.isNone():
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(phase0.BeaconBlock, data.get(), true)
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `block` format")
res.get()
Web3SignerRequest(
kind: Web3SignerRequestKind.Block,
forkInfo: forkInfo, signingRoot: signingRoot, blck: data
)
of Web3SignerRequestKind.BlockV2:
if dataName != "beacon_block":
reader.raiseUnexpectedValue("Field `beacon_block` is missing")
if forkInfo.isNone():
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(ForkedBeaconBlock, data.get(), true)
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `beacon_block` format")
res.get()
Web3SignerRequest(
kind: Web3SignerRequestKind.BlockV2,
forkInfo: forkInfo, signingRoot: signingRoot, beaconBlock: data
)
of Web3SignerRequestKind.Deposit:
if dataName != "deposit":
reader.raiseUnexpectedValue("Field `deposit` is missing")
let data =
block:
let res = decodeJsonString(Web3SignerDepositData, data.get(), true)
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `deposit` format")
res.get()
Web3SignerRequest(
kind: Web3SignerRequestKind.Deposit,
signingRoot: signingRoot, deposit: data
)
of Web3SignerRequestKind.RandaoReveal:
if dataName != "randao_reveal":
reader.raiseUnexpectedValue("Field `randao_reveal` is missing")
if forkInfo.isNone():
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(Web3SignerRandaoRevealData, data.get(),
true)
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `randao_reveal` format")
res.get()
Web3SignerRequest(
kind: Web3SignerRequestKind.RandaoReveal,
forkInfo: forkInfo, signingRoot: signingRoot, randaoReveal: data
)
of Web3SignerRequestKind.VoluntaryExit:
if dataName != "voluntary_exit":
reader.raiseUnexpectedValue("Field `voluntary_exit` is missing")
if forkInfo.isNone():
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(VoluntaryExit, data.get(), true)
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `voluntary_exit` format")
res.get()
Web3SignerRequest(
kind: Web3SignerRequestKind.VoluntaryExit,
forkInfo: forkInfo, signingRoot: signingRoot, voluntaryExit: data
)
of Web3SignerRequestKind.SyncCommitteeMessage:
if dataName != "sync_committee_message":
reader.raiseUnexpectedValue(
"Field `sync_committee_message` is missing")
if forkInfo.isNone():
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(Web3SignerSyncCommitteeMessageData,
data.get(), true)
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `sync_committee_message` format")
res.get()
Web3SignerRequest(
kind: Web3SignerRequestKind.SyncCommitteeMessage,
forkInfo: forkInfo, signingRoot: signingRoot,
syncCommitteeMessage: data
)
of Web3SignerRequestKind.SyncCommitteeSelectionProof:
if dataName != "sync_aggregator_selection_data":
reader.raiseUnexpectedValue(
"Field `sync_aggregator_selection_data` is missing")
if forkInfo.isNone():
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(SyncAggregatorSelectionData,
data.get(), true)
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `sync_aggregator_selection_data` format")
res.get()
Web3SignerRequest(
kind: Web3SignerRequestKind.SyncCommitteeSelectionProof,
forkInfo: forkInfo, signingRoot: signingRoot,
syncAggregatorSelectionData: data
)
of Web3SignerRequestKind.SyncCommitteeContributionAndProof:
if dataName != "contribution_and_proof":
reader.raiseUnexpectedValue(
"Field `contribution_and_proof` is missing")
if forkInfo.isNone():
reader.raiseUnexpectedValue("Field `fork_info` is missing")
let data =
block:
let res = decodeJsonString(ContributionAndProof,
data.get(), true)
if res.isErr():
reader.raiseUnexpectedValue(
"Incorrect field `contribution_and_proof` format")
res.get()
Web3SignerRequest(
kind: Web3SignerRequestKind.SyncCommitteeContributionAndProof,
forkInfo: forkInfo, signingRoot: signingRoot,
syncCommitteeContributionAndProof: data
)
proc parseRoot(value: string): Result[Eth2Digest, cstring] =
try:
ok(Eth2Digest(data: hexToByteArray[32](value)))
@ -1063,6 +1425,9 @@ proc encodeString*(value: ValidatorIdent): RestResult[string] =
of ValidatorQueryKind.Key:
ok(hexOriginal(toRaw(value.key)))
proc encodeString*(value: ValidatorPubKey): RestResult[string] =
ok(hexOriginal(toRaw(value)))
proc encodeString*(value: StateIdent): RestResult[string] =
case value.kind
of StateQueryKind.Slot:
@ -1168,6 +1533,15 @@ proc decodeString*(t: typedesc[ValidatorSig],
return err("Incorrect validator signature encoding")
ValidatorSig.fromHex(value)
proc decodeString*(t: typedesc[ValidatorPubKey],
value: string): Result[ValidatorPubKey, cstring] =
if len(value) != ValidatorKeySize + 2:
return err("Incorrect validator's key value length")
if value[0] != '0' and value[1] != 'x':
err("Incorrect validator's key encoding")
else:
ValidatorPubKey.fromHex(value)
proc decodeString*(t: typedesc[GraffitiBytes],
value: string): Result[GraffitiBytes, cstring] =
try:

View File

@ -0,0 +1,184 @@
# Copyright (c) 2018-2021 Status Research & Development GmbH
# 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.
{.push raises: [Defect].}
import
std/strutils, chronicles, metrics,
chronos, chronos/apps/http/httpclient, presto, presto/client,
nimcrypto/utils as ncrutils,
serialization, json_serialization,
json_serialization/std/[options, net, sets],
stew/[results, base10],
"."/[rest_types, eth2_rest_serialization]
export chronos, httpclient, client, rest_types, eth2_rest_serialization, results
type
Web3SignerResult*[T] = Result[T, string]
Web3SignerDataResponse* = Web3SignerResult[CookedSig]
declareCounter nbc_remote_signer_requests,
"Number of remote signer requests"
declareCounter nbc_remote_signer_signatures,
"Number of remote signer signatures"
declareCounter nbc_remote_signer_failures,
"Number of remote signer signatures"
declareCounter nbc_remote_signer_200_responses,
"Number of 200 responses (signature)"
declareCounter nbc_remote_signer_400_responses,
"Number of 400 responses (bad request format error)"
declareCounter nbc_remote_signer_404_responses,
"Number of 404 responses (validator not found error)"
declareCounter nbc_remote_signer_412_responses,
"Number of 412 responses (slashing protection error)"
declareCounter nbc_remote_signer_500_responses,
"Number of 500 responses (internal server error)"
declareCounter nbc_remote_signer_unknown_responses,
"Number of unrecognized responses (unknown response code)"
declareCounter nbc_remote_signer_communication_errors,
"Number of communication errors"
const delayBuckets = [0.050, 0.100, 0.500, 1.0, 5.0, 10.0]
declareHistogram nbc_remote_signer_time,
"Time(s) used to generate signature usign remote signer",
buckets = delayBuckets
proc getUpcheck*(): RestResponse[Web3SignerStatusResponse] {.
rest, endpoint: "/upcheck", meth: MethodGet.}
## https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Server-Status
proc getKeys*(): RestResponse[Web3SignerKeysResponse] {.
rest, endpoint: "/api/v1/eth2/publicKeys", meth: MethodGet.}
## https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Public-Key
proc signDataPlain*(identifier: ValidatorPubKey,
body: Web3SignerRequest): RestPlainResponse {.
rest, endpoint: "/api/v1/eth2/sign/{identifier}", meth: MethodPost.}
# https://consensys.github.io/web3signer/web3signer-eth2.html#tag/Signing
proc signData*(client: RestClientRef, identifier: ValidatorPubKey,
body: Web3SignerRequest
): Future[Web3SignerDataResponse] {.async.} =
let startSignTick = Moment.now()
inc(nbc_remote_signer_requests)
let response =
try:
await client.signDataPlain(identifier, body)
except RestError as exc:
let msg = "[" & $exc.name & "] " & $exc.msg
debug "Error occured while generating signature",
validator = shortLog(identifier),
remote_signer = $client.address.getUri(),
error_name = $exc.name, error_msg = $exc.msg,
signDur = Moment.now() - startSignTick
inc(nbc_remote_signer_communication_errors)
return Web3SignerDataResponse.err(msg)
except CatchableError as exc:
let signDur = Moment.now() - startSignTick
let msg = "[" & $exc.name & "] " & $exc.msg
debug "Unexpected error occured while generating signature",
validator = shortLog(identifier),
remote_signer = $client.address.getUri(),
error_name = $exc.name, error_msg = $exc.msg,
signDur = Moment.now() - startSignTick
inc(nbc_remote_signer_communication_errors)
return Web3SignerDataResponse.err(msg)
let res =
case response.status
of 200:
inc(nbc_remote_signer_200_responses)
let res = decodeBytes(Web3SignerSignatureResponse, response.data,
response.contentType)
if res.isErr():
let msg = "Unable to decode remote signer response [" &
$res.error() & "]"
inc(nbc_remote_signer_failures)
return Web3SignerDataResponse.err(msg)
let sig = res.get().signature.load()
if sig.isNone():
let msg = "Remote signer returns invalid signature"
inc(nbc_remote_signer_failures)
return Web3SignerDataResponse.err(msg)
Web3SignerDataResponse.ok(sig.get())
of 400:
inc(nbc_remote_signer_400_responses)
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
let msg =
if res.isErr():
"Remote signer returns 400 Bad Request Format Error"
else:
"Remote signer returns 400 Bad Request Format Error [" &
res.get().error & "]"
Web3SignerDataResponse.err(msg)
of 404:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
let msg =
if res.isErr():
"Remote signer returns 404 Validator's Key Not Found Error"
else:
"Remote signer returns 404 Validator's Key Not Found Error [" &
res.get().error & "]"
inc(nbc_remote_signer_404_responses)
Web3SignerDataResponse.err(msg)
of 412:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
let msg =
if res.isErr():
"Remote signer returns 412 Slashing Protection Error"
else:
"Remote signer returns 412 Slashing Protection Error [" &
res.get().error & "]"
inc(nbc_remote_signer_412_responses)
Web3SignerDataResponse.err(msg)
of 500:
let res = decodeBytes(Web3SignerErrorResponse, response.data,
response.contentType)
let msg =
if res.isErr():
"Remote signer returns 500 Internal Server Error"
else:
"Remote signer returns 500 Internal Server Error [" &
res.get().error & "]"
inc(nbc_remote_signer_500_responses)
Web3SignerDataResponse.err(msg)
else:
let msg = "Remote signer returns unexpected status code " &
Base10.toString(uint64(response.status))
inc(nbc_remote_signer_unknown_responses)
Web3SignerDataResponse.err(msg)
if res.isOk():
let delay = Moment.now() - startSignTick
inc(nbc_remote_signer_signatures)
nbc_remote_signer_time.observe(float(milliseconds(delay)) / 1000.0)
debug "Signature was successfully generated",
validator = shortLog(identifier),
remote_signer = $client.address.getUri(),
signDur = delay
else:
inc(nbc_remote_signer_failures)
debug "Signature generation was failed",
validator = shortLog(identifier),
remote_signer = $client.address.getUri(),
error_msg = res.error(),
signDur = Moment.now() - startSignTick
return res

View File

@ -412,6 +412,85 @@ type
genesis_validators_root*: Eth2Digest
slot*: Slot
Web3SignerKeysResponse* = object
keys*: seq[ValidatorPubKey]
Web3SignerStatusResponse* = object
status*: string
Web3SignerSignatureResponse* = object
signature*: ValidatorSig
Web3SignerErrorResponse* = object
error*: string
Web3SignerForkInfo* = object
fork*: Fork
genesisValidatorsRoot* {.
serializedFieldName: "genesis_validators_root".}: Eth2Digest
Web3SignerAggregationSlotData* = object
slot*: Slot
Web3SignerRandaoRevealData* = object
epoch*: Epoch
Web3SignerDepositData* = object
pubkey*: ValidatorPubKey
withdrawalCredentials* {.
serializedFieldName: "withdrawal_credentials".}: Eth2Digest
genesisForkVersion* {.
serializedFieldName: "genesis_fork_version".}: Version
amount*: Gwei
Web3SignerSyncCommitteeMessageData* = object
beaconBlockRoot* {.
serializedFieldName: "beacon_block_root".}: Eth2Digest
slot*: Slot
Web3SignerRequestKind* {.pure.} = enum
AggregationSlot, AggregateAndProof, Attestation, Block, BlockV2,
Deposit, RandaoReveal, VoluntaryExit, SyncCommitteeMessage,
SyncCommitteeSelectionProof, SyncCommitteeContributionAndProof
Web3SignerRequest* = object
signingRoot*: Option[Eth2Digest]
forkInfo* {.serializedFieldName: "fork_info".}: Option[Web3SignerForkInfo]
case kind* {.dontSerialize.}: Web3SignerRequestKind
of Web3SignerRequestKind.AggregationSlot:
aggregationSlot* {.
serializedFieldName: "aggregation_slot".}: Web3SignerAggregationSlotData
of Web3SignerRequestKind.AggregateAndProof:
aggregateAndProof* {.
serializedFieldName: "aggregate_and_proof".}: AggregateAndProof
of Web3SignerRequestKind.Attestation:
attestation*: AttestationData
of Web3SignerRequestKind.Block:
blck* {.
serializedFieldName: "block".}: phase0.BeaconBlock
of Web3SignerRequestKind.BlockV2:
beaconBlock* {.
serializedFieldName: "beacon_block".}: ForkedBeaconBlock
of Web3SignerRequestKind.Deposit:
deposit*: Web3SignerDepositData
of Web3SignerRequestKind.RandaoReveal:
randaoReveal* {.
serializedFieldName: "randao_reveal".}: Web3SignerRandaoRevealData
of Web3SignerRequestKind.VoluntaryExit:
voluntaryExit* {.
serializedFieldName: "voluntary_exit".}: VoluntaryExit
of Web3SignerRequestKind.SyncCommitteeMessage:
syncCommitteeMessage* {.
serializedFieldName: "sync_committee_message".}:
Web3SignerSyncCommitteeMessageData
of Web3SignerRequestKind.SyncCommitteeSelectionProof:
syncAggregatorSelectionData* {.
serializedFieldName: "sync_aggregator_selection_data".}:
SyncAggregatorSelectionData
of Web3SignerRequestKind.SyncCommitteeContributionAndProof:
syncCommitteeContributionAndProof* {.
serializedFieldName: "contribution_and_proof".}: ContributionAndProof
GetBlockResponse* = DataEnclosedObject[phase0.SignedBeaconBlock]
GetStateResponse* = DataEnclosedObject[phase0.BeaconState]
GetBlockV2Response* = ForkedSignedBeaconBlock
@ -500,3 +579,152 @@ func init*(t: typedesc[RestValidator], index: ValidatorIndex,
func init*(t: typedesc[RestValidatorBalance], index: ValidatorIndex,
balance: uint64): RestValidatorBalance =
RestValidatorBalance(index: index, balance: Base10.toString(balance))
func init*(t: typedesc[Web3SignerRequest], fork: Fork,
genesisValidatorsRoot: Eth2Digest, data: Slot,
signingRoot: Option[Eth2Digest] = none[Eth2Digest]()
): Web3SignerRequest =
Web3SignerRequest(
kind: Web3SignerRequestKind.AggregationSlot,
forkInfo: some(Web3SignerForkInfo(
fork: fork, genesisValidatorsRoot: genesisValidatorsRoot
)),
signingRoot: signingRoot,
aggregationSlot: Web3SignerAggregationSlotData(slot: data)
)
func init*(t: typedesc[Web3SignerRequest], fork: Fork,
genesisValidatorsRoot: Eth2Digest, data: AggregateAndProof,
signingRoot: Option[Eth2Digest] = none[Eth2Digest]()
): Web3SignerRequest =
Web3SignerRequest(
kind: Web3SignerRequestKind.AggregateAndProof,
forkInfo: some(Web3SignerForkInfo(
fork: fork, genesisValidatorsRoot: genesisValidatorsRoot
)),
signingRoot: signingRoot,
aggregateAndProof: data
)
func init*(t: typedesc[Web3SignerRequest], fork: Fork,
genesisValidatorsRoot: Eth2Digest, data: AttestationData,
signingRoot: Option[Eth2Digest] = none[Eth2Digest]()
): Web3SignerRequest =
Web3SignerRequest(
kind: Web3SignerRequestKind.Attestation,
forkInfo: some(Web3SignerForkInfo(
fork: fork, genesisValidatorsRoot: genesisValidatorsRoot
)),
signingRoot: signingRoot,
attestation: data
)
func init*(t: typedesc[Web3SignerRequest], fork: Fork,
genesisValidatorsRoot: Eth2Digest, data: phase0.BeaconBlock,
signingRoot: Option[Eth2Digest] = none[Eth2Digest]()
): Web3SignerRequest =
Web3SignerRequest(
kind: Web3SignerRequestKind.Block,
forkInfo: some(Web3SignerForkInfo(
fork: fork, genesisValidatorsRoot: genesisValidatorsRoot
)),
signingRoot: signingRoot,
blck: data
)
func init*(t: typedesc[Web3SignerRequest], fork: Fork,
genesisValidatorsRoot: Eth2Digest, data: ForkedBeaconBlock,
signingRoot: Option[Eth2Digest] = none[Eth2Digest]()
): Web3SignerRequest =
Web3SignerRequest(
kind: Web3SignerRequestKind.BlockV2,
forkInfo: some(Web3SignerForkInfo(
fork: fork, genesisValidatorsRoot: genesisValidatorsRoot
)),
signingRoot: signingRoot,
beaconBlock: data
)
func init*(t: typedesc[Web3SignerRequest], genesisForkVersion: Version,
data: DepositMessage,
signingRoot: Option[Eth2Digest] = none[Eth2Digest]()
): Web3SignerRequest =
Web3SignerRequest(
kind: Web3SignerRequestKind.Deposit,
signingRoot: signingRoot,
deposit: Web3SignerDepositData(
pubkey: data.pubkey,
withdrawalCredentials: data.withdrawalCredentials,
genesisForkVersion: genesisForkVersion,
amount: data.amount
)
)
func init*(t: typedesc[Web3SignerRequest], fork: Fork,
genesisValidatorsRoot: Eth2Digest, data: Epoch,
signingRoot: Option[Eth2Digest] = none[Eth2Digest]()
): Web3SignerRequest =
Web3SignerRequest(
kind: Web3SignerRequestKind.RandaoReveal,
forkInfo: some(Web3SignerForkInfo(
fork: fork, genesisValidatorsRoot: genesisValidatorsRoot
)),
signingRoot: signingRoot,
randaoReveal: Web3SignerRandaoRevealData(epoch: data)
)
func init*(t: typedesc[Web3SignerRequest], fork: Fork,
genesisValidatorsRoot: Eth2Digest, data: VoluntaryExit,
signingRoot: Option[Eth2Digest] = none[Eth2Digest]()
): Web3SignerRequest =
Web3SignerRequest(
kind: Web3SignerRequestKind.VoluntaryExit,
forkInfo: some(Web3SignerForkInfo(
fork: fork, genesisValidatorsRoot: genesisValidatorsRoot
)),
signingRoot: signingRoot,
voluntaryExit: data
)
func init*(t: typedesc[Web3SignerRequest], fork: Fork,
genesisValidatorsRoot: Eth2Digest, blockRoot: Eth2Digest,
slot: Slot, signingRoot: Option[Eth2Digest] = none[Eth2Digest]()
): Web3SignerRequest =
Web3SignerRequest(
kind: Web3SignerRequestKind.SyncCommitteeMessage,
forkInfo: some(Web3SignerForkInfo(
fork: fork, genesisValidatorsRoot: genesisValidatorsRoot
)),
signingRoot: signingRoot,
syncCommitteeMessage: Web3SignerSyncCommitteeMessageData(
beaconBlockRoot: blockRoot, slot: slot
)
)
func init*(t: typedesc[Web3SignerRequest], fork: Fork,
genesisValidatorsRoot: Eth2Digest,
data: SyncAggregatorSelectionData,
signingRoot: Option[Eth2Digest] = none[Eth2Digest]()
): Web3SignerRequest =
Web3SignerRequest(
kind: Web3SignerRequestKind.SyncCommitteeSelectionProof,
forkInfo: some(Web3SignerForkInfo(
fork: fork, genesisValidatorsRoot: genesisValidatorsRoot
)),
signingRoot: signingRoot,
syncAggregatorSelectionData: data
)
func init*(t: typedesc[Web3SignerRequest], fork: Fork,
genesisValidatorsRoot: Eth2Digest,
data: ContributionAndProof,
signingRoot: Option[Eth2Digest] = none[Eth2Digest]()
): Web3SignerRequest =
Web3SignerRequest(
kind: Web3SignerRequestKind.SyncCommitteeContributionAndProof,
forkInfo: some(Web3SignerForkInfo(
fork: fork, genesisValidatorsRoot: genesisValidatorsRoot
)),
signingRoot: signingRoot,
syncCommitteeContributionAndProof: data
)

View File

@ -9,7 +9,8 @@
import
# Standard library
std/[algorithm, math, parseutils, strformat, strutils, typetraits, unicode],
std/[algorithm, math, parseutils, strformat, strutils, typetraits, unicode,
uri],
# Third-party libraries
normalize,
# Status libraries
@ -20,7 +21,7 @@ import
libp2p/crypto/crypto as lcrypto,
./datatypes/base, ./signatures
export base
export base, uri
# We use `ncrutils` for constant-time hexadecimal encoding/decoding procedures.
import nimcrypto/utils as ncrutils
@ -127,6 +128,20 @@ type
uuid*: string
version*: int
RemoteKeystoreFlag* {.pure.} = enum
IgnoreSSLVerification
RemoteSignerType* {.pure.} = enum
Web3Signer
RemoteKeystore* = object
version*: Option[uint64]
description*: Option[string]
remoteType*: RemoteSignerType
pubkey*: ValidatorPubKey
remote*: Uri
flags*: set[RemoteKeystoreFlag]
KsResult*[T] = Result[T, string]
Eth2KeyKind* = enum
@ -497,6 +512,75 @@ proc readValue*(r: var JsonReader, value: var Kdf)
r.raiseUnexpectedValue(
"The Kdf value should have sub-fields named 'function' and 'params'")
proc readValue*(r: var JsonReader, value: var RemoteKeystore)
{.raises: [SerializationError, IOError, Defect].} =
var
version: Option[uint64]
description: Option[string]
remote: Option[Uri]
remoteType: Option[string]
ignoreSslVerification: Option[bool]
pubkey: Option[ValidatorPubKey]
for fieldName in readObjectFields(r):
case fieldName:
of "pubkey":
if pubkey.isSome():
r.raiseUnexpectedField("Multiple `pubkey` fields found",
"RemoteKeystore")
let res = r.readValue(ValidatorPubKey)
pubkey = some(res)
value.pubkey = res
of "remote":
if remote.isSome():
r.raiseUnexpectedField("Multiple `remote` fields found",
"RemoteKeystore")
let res = r.readValue(Uri)
remote = some(res)
value.remote = res
of "version":
if version.isSome():
r.raiseUnexpectedField("Multiple `version` fields found",
"RemoteKeystore")
value.version = some(r.readValue(uint64))
of "description":
let res = r.readValue(string)
if value.description.isSome():
value.description = some(value.description.get() & "\n" & res)
else:
value.description = some(res)
of "ignore_ssl_verification":
if ignoreSslVerification.isSome():
r.raiseUnexpectedField("Multiple conflicting options found",
"RemoteKeystore")
let res = r.readValue(bool)
ignoreSslVerification = some(res)
if res:
value.flags.incl(RemoteKeystoreFlag.IgnoreSSLVerification)
else:
value.flags.excl(RemoteKeystoreFlag.IgnoreSSLVerification)
of "type":
if remoteType.isSome():
r.raiseUnexpectedField("Multiple `type` fields found",
"RemoteKeystore")
let res = r.readValue(string)
remoteType = some(res)
case res
of "web3signer":
value.remoteType = RemoteSignerType.Web3Signer
else:
r.raiseUnexpectedValue("Unsupported remote signer `type` value")
else:
# Ignore unknown field names.
discard
if remote.isNone():
r.raiseUnexpectedValue("Field remote is missing")
if pubkey.isNone():
r.raiseUnexpectedValue("Field pubkey is missing")
# Set default remote signer type to `Web3Signer`.
if remoteType.isNone():
value.remoteType = RemoteSignerType.Web3Signer
template writeValue*(w: var JsonWriter,
value: Pbkdf2Salt|SimpleHexEncodedTypes|Aes128CtrIv) =
writeJsonHexString(w.stream, distinctBase value)

View File

@ -206,6 +206,13 @@ func get_deposit_signature*(preset: RuntimeConfig,
blsSign(privKey, signing_root.data)
func get_deposit_signature*(message: DepositMessage, version: Version,
privkey: ValidatorPrivKey): CookedSig =
let
domain = compute_domain(DOMAIN_DEPOSIT, version)
signing_root = compute_signing_root(message, domain)
blsSign(privkey, signing_root.data)
proc verify_deposit_signature*(preset: RuntimeConfig,
deposit: DepositData): bool =
let
@ -242,6 +249,28 @@ proc verify_voluntary_exit_signature*(
blsVerify(pubkey, signing_root.data, signature)
func get_sync_committee_message_signature*(fork: Fork,
genesis_validators_root: Eth2Digest, slot: Slot,
block_root: Eth2Digest, privkey: ValidatorPrivKey): CookedSig =
let signing_root = sync_committee_msg_signing_root(fork, slot.epoch,
genesis_validators_root, block_root)
blsSign(privkey, signing_root.data)
func get_sync_aggregator_selection_data_signature*(fork: Fork,
genesis_validators_root: Eth2Digest, slot: Slot,
subcommittee_index: uint64,
privkey: ValidatorPrivKey): CookedSig =
let signing_root = sync_committee_selection_proof_signing_root(fork,
genesis_validators_root, slot, subcommittee_index)
blsSign(privkey, signing_root.data)
proc get_sync_committee_contribution_and_proof_signature*(fork: Fork,
genesis_validators_root: Eth2Digest, msg: ContributionAndProof,
privkey: ValidatorPrivKey): CookedSig =
let signing_root = contribution_and_proof_signing_root(fork,
genesis_validators_root, msg)
blsSign(privkey, signing_root.data)
proc verify_sync_committee_message_signature*(
epoch: Epoch,
beacon_block_root: Eth2Digest,

View File

@ -1,7 +1,6 @@
import
../spec/eth2_apis/eth2_rest_serialization,
../spec/datatypes/[phase0, altair],
common
import chronicles,
../spec/eth2_apis/eth2_rest_serialization,
../spec/datatypes/[phase0, altair], common
export eth2_rest_serialization, common

View File

@ -41,10 +41,17 @@ proc serveAttestation(service: AttestationServiceRef, adata: AttestationData,
validator_index = vindex, badVoteDetails = $notSlashable.error
return false
let attestation = await validator.produceAndSignAttestation(adata,
int(duty.data.committee_length),
Natural(duty.data.validator_committee_index),
fork, vc.beaconGenesis.genesis_validators_root)
let attestation =
block:
let res = await validator.produceAndSignAttestation(adata,
int(duty.data.committee_length),
Natural(duty.data.validator_committee_index),
fork, vc.beaconGenesis.genesis_validators_root)
if res.isErr():
error "Unable to sign attestation", validator = shortLog(validator),
error_msg = res.error()
return false
res.get()
debug "Sending attestation", attestation = shortLog(attestation),
validator = shortLog(validator), validator_index = vindex,
@ -91,8 +98,17 @@ proc serveAggregateAndProof*(service: AttestationServiceRef,
genesisRoot = vc.beaconGenesis.genesis_validators_root
fork = vc.fork.get()
let signature = await signAggregateAndProof(validator, proof, fork,
genesisRoot)
let signature =
block:
let res = await signAggregateAndProof(validator, proof, fork,
genesisRoot)
if res.isErr():
error "Unable to sign aggregate and proof using remote signer",
validator = shortLog(validator),
aggregationSlot = proof.aggregate.data.slot,
error_msg = res.error()
return false
res.get()
let signedProof = SignedAggregateAndProof(message: proof,
signature: signature)

View File

@ -22,7 +22,15 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot,
graffiti = graffiti, fork = fork, slot = slot,
wall_slot = currentSlot
try:
let randaoReveal = await validator.genRandaoReveal(fork, genesisRoot, slot)
let randaoReveal =
block:
let res = await validator.genRandaoReveal(fork, genesisRoot, slot)
if res.isErr():
error "Unable to generate randao reveal usint remote signer",
validator = shortLog(validator), error_msg = res.error()
return
res.get()
let beaconBlock =
try:
await vc.produceBlockV2(slot, randaoReveal, graffiti)
@ -45,8 +53,17 @@ proc publishBlock(vc: ValidatorClientRef, currentSlot, slot: Slot,
validator.pubKey, slot, signing_root)
if notSlashable.isOk():
let signature = await validator.signBlockProposal(fork, genesisRoot, slot,
blockRoot)
let signature =
block:
let res = await validator.signBlockProposal(fork, genesisRoot,
slot, blockRoot,
beaconBlock)
if res.isErr():
error "Unable to sign block proposal using remote signer",
validator = shortLog(validator), error_msg = res.error()
return
res.get()
debug "Sending block",
blockRoot = shortLog(blockRoot), blck = shortLog(beaconBlock),
signature = shortLog(signature), validator = shortLog(validator)

View File

@ -152,11 +152,13 @@ proc pollForAttesterDuties*(vc: ValidatorClientRef,
res
if len(addOrReplaceItems) > 0:
var pending: seq[Future[ValidatorSig]]
var pending: seq[Future[SignatureResult]]
var validators: seq[AttachedValidator]
for item in addOrReplaceItems:
let validator = vc.attachedValidators.getValidator(item.duty.pubkey)
let future = validator.getSlotSig(fork, genesisRoot, item.duty.slot)
pending.add(future)
validators.add(validator)
await allFutures(pending)
@ -164,8 +166,16 @@ proc pollForAttesterDuties*(vc: ValidatorClientRef,
let item = addOrReplaceItems[index]
let dap =
if fut.done():
DutyAndProof.init(item.epoch, dependentRoot, item.duty,
some(fut.read()))
let sigRes = fut.read()
if sigRes.isErr():
error "Unable to create slot signature using remote signer",
validator = shortLog(validators[index]),
error_msg = sigRes.error()
DutyAndProof.init(item.epoch, dependentRoot, item.duty,
none[ValidatorSig]())
else:
DutyAndProof.init(item.epoch, dependentRoot, item.duty,
some(sigRes.get()))
else:
DutyAndProof.init(item.epoch, dependentRoot, item.duty,
none[ValidatorSig]())

View File

@ -11,7 +11,7 @@ import
std/[os, strutils, terminal, wordwrap, unicode],
chronicles, chronos, json_serialization, zxcvbn,
serialization, blscurve, eth/common/eth_types, eth/keys, confutils, bearssl,
".."/spec/[eth2_merkleization, keystore],
".."/spec/[eth2_merkleization, keystore, crypto],
".."/spec/datatypes/base,
stew/io2, libp2p/crypto/crypto as lcrypto,
nimcrypto/utils as ncrutils,
@ -20,7 +20,7 @@ import
./validator_pool
export
keystore, validator_pool
keystore, validator_pool, crypto
when defined(windows):
import stew/[windows/acl]
@ -29,6 +29,7 @@ when defined(windows):
const
KeystoreFileName* = "keystore.json"
RemoteKeystoreFileName* = "remote_keystore.json"
NetKeystoreFileName* = "network_keystore.json"
DisableFileName* = ".disable"
DisableFileContent* = "Please do not remove this file manually. " &
@ -43,8 +44,6 @@ type
walletPath*: WalletPathPair
seed*: KeySeed
AnyConf* = BeaconNodeConf | ValidatorClientConf
const
minPasswordLen = 12
minPasswordEntropy = 60.0
@ -59,9 +58,10 @@ proc echoP*(msg: string) =
echo ""
echo wrapWords(msg, 80)
proc init*(t: typedesc[ValidatorPrivateItem], privateKey: ValidatorPrivKey,
func init*(t: typedesc[ValidatorPrivateItem], privateKey: ValidatorPrivKey,
keystore: Keystore): ValidatorPrivateItem =
ValidatorPrivateItem(
kind: ValidatorKind.Local,
privateKey: privateKey,
description: if keystore.description == nil: none(string)
else: some(keystore.description[]),
@ -70,6 +70,22 @@ proc init*(t: typedesc[ValidatorPrivateItem], privateKey: ValidatorPrivKey,
version: some(uint64(keystore.version))
)
func init*(t: typedesc[ValidatorPrivateItem],
keystore: RemoteKeystore): Result[ValidatorPrivateItem, cstring] =
let cookedKey =
block:
let res = keystore.pubkey.load()
if res.isNone():
return err("Invalid validator's public key")
res.get()
ok(ValidatorPrivateItem(
kind: ValidatorKind.Remote,
publicKey: cookedKey,
description: keystore.description,
version: keystore.version,
remoteUrl: keystore.remote
))
proc checkAndCreateDataDir*(dataDir: string): bool =
when defined(posix):
let requiredPerms = 0o700
@ -312,8 +328,29 @@ proc loadKeystoreUnsafe*(validatorsDir, secretsDir,
else:
err("Failed to decrypt keystore")
proc loadKeystore*(validatorsDir, secretsDir, keyName: string,
nonInteractive: bool): Option[ValidatorPrivateItem] =
proc loadRemoteKeystoreImpl(validatorsDir,
keyName: string): Option[ValidatorPrivateItem] =
let remoteKeystorePath = validatorsDir / keyName / RemoteKeystoreFileName
let privateItem =
block:
let keystore =
try:
Json.decode(remoteKeystorePath, RemoteKeystore)
except SerializationError as e:
error "Failed to read remote keystore file",
keystore_path = remoteKeystorePath,
err_msg = e.formatMsg(remoteKeystorePath)
return
let res = ValidatorPrivateItem.init(keystore)
if res.isErr():
error "Invalid validator's public key in keystore file",
keystore_path = remoteKeystorePath
return
res.get()
some(privateItem)
proc loadKeystoreImpl(validatorsDir, secretsDir, keyName: string,
nonInteractive: bool): Option[ValidatorPrivateItem] =
let
keystorePath = validatorsDir / keyName / KeystoreFileName
keystore =
@ -367,6 +404,21 @@ proc loadKeystore*(validatorsDir, secretsDir, keyName: string,
else:
return
proc loadKeystore*(validatorsDir, secretsDir, keyName: string,
nonInteractive: bool): Option[ValidatorPrivateItem] =
let
keystorePath = validatorsDir / keyName
localKeystorePath = keystorePath / KeystoreFileName
remoteKeystorePath = keystorePath / RemoteKeystoreFileName
if fileExists(localKeystorePath):
loadKeystoreImpl(validatorsDir, secretsDir, keyName, nonInteractive)
elif fileExists(remoteKeystorePath):
loadRemoteKeystoreImpl(validatorsDir, keyName)
else:
error "Unable to find any keystore files", keystorePath
none[ValidatorPrivateItem]()
proc isEnabled*(validatorsDir, keyName: string): bool {.
raises: [Defect].} =
## Returns ``true`` if specific validator with key ``keyName`` in validators
@ -680,7 +732,7 @@ proc importKeystoresFromDir*(rng: var BrHmacDrbgContext,
KeystorePass.init password,
secret)
case status
of Success:
of DecryptionStatus.Success:
let privKey = ValidatorPrivKey.fromRaw(secret)
if privKey.isOk:
let pubKey = privKey.value.toPubKey
@ -695,10 +747,10 @@ proc importKeystoresFromDir*(rng: var BrHmacDrbgContext,
else:
error "Imported keystore holds invalid key", file, err = privKey.error
break
of InvalidKeystore:
of DecryptionStatus.InvalidKeystore:
warn "Invalid keystore", file
break
of InvalidPassword:
of DecryptionStatus.InvalidPassword:
if firstDecryptionAttempt:
try:
const msg = "Please enter the password for decrypting '$1' " &
@ -953,7 +1005,7 @@ proc unlockWalletInteractively*(wallet: Wallet): Result[KeySeed, string] =
defer: burnMem(secret)
let status = decryptCryptoField(wallet.crypto, KeystorePass.init password, secret)
case status
of Success:
of DecryptionStatus.Success:
ok(KeySeed secret)
else:
# TODO Handle InvalidKeystore in a special way here

View File

@ -86,37 +86,56 @@ proc findValidator(validators: auto, pubKey: ValidatorPubKey):
proc addLocalValidator(node: BeaconNode,
validators: auto,
item: ValidatorPrivateItem) =
let pubKey = item.privateKey.toPubKey()
node.attachedValidators[].addLocalValidator(
item,
findValidator(validators, pubKey.toPubKey()))
let
pubKey = item.privateKey.toPubKey()
index = findValidator(validators, pubKey.toPubKey())
node.attachedValidators[].addLocalValidator(item, index)
proc addLocalValidators*(node: BeaconNode) =
proc addRemoteValidator(node: BeaconNode, validators: auto,
item: ValidatorPrivateItem) =
let httpFlags =
block:
var res: set[HttpClientFlag]
if RemoteKeystoreFlag.IgnoreSSLVerification in item.flags:
res.incl({HttpClientFlag.NoVerifyHost,
HttpClientFlag.NoVerifyServerName})
res
let prestoFlags = {RestClientFlag.CommaSeparatedArray}
let client = RestClientRef.new($item.remoteUrl, prestoFlags, httpFlags)
if client.isErr():
warn "Unable to resolve remote signer address",
remote_url = $item.remoteUrl, validator = item.publicKey
return
let index = findValidator(validators, item.publicKey.toPubKey())
node.attachedValidators[].addRemoteValidator(item, client.get(), index)
proc addLocalValidators*(node: BeaconNode,
validators: openArray[ValidatorPrivateItem]) =
withState(node.dag.headState.data):
for validatorItem in node.config.validatorItems():
node.addLocalValidator(state.data.validators.asSeq(), validatorItem)
for item in validators:
node.addLocalValidator(state.data.validators.asSeq(), item)
proc addRemoteValidators*(node: BeaconNode) {.raises: [Defect, OSError, IOError].} =
# load all the validators from the child process - loop until `end`
var line = newStringOfCap(120).TaintedString
while line != "end" and running(node.vcProcess):
if node.vcProcess.outputStream.readLine(line) and line != "end":
let
key = ValidatorPubKey.fromHex(line).get()
index = findValidator(
getStateField(node.dag.headState.data, validators).asSeq, key)
pk = key.load()
if pk.isSome():
let v = AttachedValidator(pubKey: key,
index: index,
kind: ValidatorKind.Remote,
connection: ValidatorConnection(
inStream: node.vcProcess.inputStream,
outStream: node.vcProcess.outputStream,
pubKeyStr: $key))
node.attachedValidators[].addRemoteValidator(key, v)
else:
warn "Could not load public key", line
proc addRemoteValidators*(node: BeaconNode,
validators: openArray[ValidatorPrivateItem]) =
withState(node.dag.headState.data):
for item in validators:
node.addRemoteValidator(state.data.validators.asSeq(), item)
proc addValidators*(node: BeaconNode) =
let (localValidators, remoteValidators) =
block:
var local, remote: seq[ValidatorPrivateItem]
for item in node.config.validatorItems():
case item.kind
of ValidatorKind.Local:
local.add(item)
of ValidatorKind.Remote:
remote.add(item)
(local, remote)
# Adding local validators.
node.addLocalValidators(localValidators)
# Adding remote validators.
node.addRemoteValidators(remoteValidators)
proc getAttachedValidator*(node: BeaconNode,
pubkey: ValidatorPubKey): AttachedValidator =
@ -341,10 +360,16 @@ proc createAndSendAttestation(node: BeaconNode,
indexInCommittee: int,
subnet_id: SubnetId) {.async.} =
try:
var
attestation = await validator.produceAndSignAttestation(
attestationData, committeeLen, indexInCommittee, fork,
genesis_validators_root)
var attestation =
block:
let res = await validator.produceAndSignAttestation(
attestationData, committeeLen, indexInCommittee, fork,
genesis_validators_root)
if res.isErr():
error "Unable to sign attestation", validator = shortLog(validator),
error_msg = res.error()
return
res.get()
let res = await node.sendAttestation(
attestation, subnet_id, checkSignature = false)
@ -481,8 +506,16 @@ proc proposeBlock(node: BeaconNode,
fork = node.dag.forkAtEpoch(slot.epoch)
genesis_validators_root =
getStateField(node.dag.headState.data, genesis_validators_root)
randao = await validator.genRandaoReveal(
fork, genesis_validators_root, slot)
randao =
block:
let res = await validator.genRandaoReveal(fork, genesis_validators_root,
slot)
if res.isErr():
error "Unable to generate randao reveal",
validator = shortLog(validator), error_msg = res.error()
return head
res.get()
var newBlock = await makeBeaconBlockForHeadAndSlot(
node, randao, validator_index, node.graffitiBytes, head, slot)
@ -511,8 +544,15 @@ proc proposeBlock(node: BeaconNode,
existingProposal = notSlashable.error
return head
let signature = await validator.signBlockProposal(
fork, genesis_validators_root, slot, root)
let signature =
block:
let res = await validator.signBlockProposal(fork,
genesis_validators_root, slot, root, blck)
if res.isErr():
error "Unable to sign block proposal",
validator = shortLog(validator), error_msg = res.error()
return head
res.get()
ForkedSignedBeaconBlock.init(
phase0.SignedBeaconBlock(
message: blck.phase0Data, root: root, signature: signature)
@ -534,8 +574,15 @@ proc proposeBlock(node: BeaconNode,
existingProposal = notSlashable.error
return head
let signature = await validator.signBlockProposal(
fork, genesis_validators_root, slot, root)
let signature =
block:
let res = await validator.signBlockProposal(
fork, genesis_validators_root, slot, root, blck)
if res.isErr():
error "Unable to sign block proposal",
validator = shortLog(validator), error_msg = res.error()
return head
res.get()
ForkedSignedBeaconBlock.init(
altair.SignedBeaconBlock(
@ -558,8 +605,15 @@ proc proposeBlock(node: BeaconNode,
existingProposal = notSlashable.error
return head
let signature = await validator.signBlockProposal(
fork, genesis_validators_root, slot, root)
let signature =
block:
let res = await validator.signBlockProposal(
fork, genesis_validators_root, slot, root, blck)
if res.isErr():
error "Unable to sign block proposal",
validator = shortLog(validator), error_msg = res.error()
return head
res.get()
ForkedSignedBeaconBlock.init(
merge.SignedBeaconBlock(
@ -650,8 +704,17 @@ proc createAndSendSyncCommitteeMessage(node: BeaconNode,
let
fork = node.dag.forkAtEpoch(slot.epoch)
genesisValidatorsRoot = node.dag.genesisValidatorsRoot
msg = await signSyncCommitteeMessage(validator, slot, fork,
genesisValidatorsRoot, head.root)
msg =
block:
let res = await signSyncCommitteeMessage(validator, slot, fork,
genesisValidatorsRoot,
head.root)
if res.isErr():
error "Unable to sign committee message using remote signer",
validator = shortLog(validator), slot = slot,
block_root = shortLog(head.root)
return
res.get()
let res = await node.sendSyncCommitteeMessage(
msg, subcommitteeIdx, checkSignature = false)
@ -707,9 +770,14 @@ proc signAndSendContribution(node: BeaconNode,
contribution: contribution,
selection_proof: selectionProof))
await validator.sign(msg,
node.dag.forkAtEpoch(contribution.slot.epoch),
node.dag.genesisValidatorsRoot)
let res = await validator.sign(
msg, node.dag.forkAtEpoch(contribution.slot.epoch),
node.dag.genesisValidatorsRoot)
if res.isErr():
error "Unable to sign sync committee contribution usign remote signer",
validator = shortLog(validator), error_msg = res.error()
return
# Failures logged in sendSyncCommitteeContribution
discard await node.sendSyncCommitteeContribution(msg[], false)
@ -733,7 +801,7 @@ proc handleSyncCommitteeContributions(node: BeaconNode,
subcommitteeIdx: SyncSubcommitteeIndex
var candidateAggregators: seq[AggregatorCandidate]
var selectionProofs: seq[Future[ValidatorSig]]
var selectionProofs: seq[Future[SignatureResult]]
var time = timeIt:
for subcommitteeIdx in allSyncSubcommittees():
@ -760,11 +828,17 @@ proc handleSyncCommitteeContributions(node: BeaconNode,
var contributionsSent = 0
time = timeIt:
for i in 0 ..< selectionProofs.len:
if not selectionProofs[i].completed:
for i, proof in selectionProofs.pairs():
if not proof.completed:
continue
let selectionProof = selectionProofs[i].read
let selectionProofRes = proof.read()
if selectionProofRes.isErr():
error "Unable to sign selection proof using remote signer",
validator = shortLog(candidateAggregators[i].validator),
slot, head, subnet_id = candidateAggregators[i].subcommitteeIdx
continue
let selectionProof = selectionProofRes.get()
if not is_sync_committee_aggregator(selectionProof):
continue
@ -852,7 +926,7 @@ proc sendAggregatedAttestations(
committees_per_slot = get_committee_count_per_slot(epochRef)
var
slotSigs: seq[Future[ValidatorSig]] = @[]
slotSigs: seq[Future[SignatureResult]] = @[]
slotSigsData: seq[tuple[committee_index: uint64,
validator_idx: ValidatorIndex,
v: AttachedValidator]] = @[]
@ -872,16 +946,29 @@ proc sendAggregatedAttestations(
await allFutures(slotSigs)
for curr in zip(slotSigsData, slotSigs):
let slotSig = curr[1].read()
if slotSig.isErr():
error "Unable to create slot signature using remote signer",
validator = shortLog(curr[0].v),
aggregation_slot = aggregationSlot, error_msg = slotSig.error()
continue
let aggregateAndProof =
makeAggregateAndProof(node.attestationPool[], epochRef, aggregationSlot,
curr[0].committee_index.CommitteeIndex,
curr[0].validator_idx,
curr[1].read)
curr[0].validator_idx,
slotSig.get())
# Don't broadcast when, e.g., this node isn't aggregator
if aggregateAndProof.isSome:
let sig = await signAggregateAndProof(curr[0].v,
aggregateAndProof.get, fork, genesis_validators_root)
let sig =
block:
let res = await signAggregateAndProof(curr[0].v,
aggregateAndProof.get, fork, genesis_validators_root)
if res.isErr():
error "Unable to sign aggregated attestation using remote signer",
validator = shortLog(curr[0].v), error_msg = res.error()
return
res.get()
var signedAP = SignedAggregateAndProof(
message: aggregateAndProof.get,
signature: sig)
@ -1210,8 +1297,13 @@ proc registerDuties*(node: BeaconNode, wallSlot: Slot) {.async.} =
let
subnet_id = compute_subnet_for_attestation(
committees_per_slot, slot, committee_index.CommitteeIndex)
slotSig = await getSlotSig(
validator, fork, genesis_validators_root, slot)
isAggregator = is_aggregator(committee.lenu64, slotSig)
let slotSigRes = await getSlotSig(validator, fork,
genesis_validators_root, slot)
if slotSigRes.isErr():
error "Unable to create slot signature using remote signer",
validator = shortLog(validator),
error_msg = slotSigRes.error()
continue
let isAggregator = is_aggregator(committee.lenu64, slotSigRes.get())
node.registerDuty(slot, subnet_id, validatorIdx, isAggregator)

View File

@ -8,15 +8,20 @@
{.push raises: [Defect].}
import
std/[options, tables, json, streams],
std/[options, tables, json, streams, uri],
chronos, chronicles, metrics,
json_serialization/std/net,
../spec/[keystore, signatures, helpers],
presto, presto/client,
../spec/[keystore, signatures, helpers, crypto],
../spec/datatypes/[phase0, altair],
../spec/eth2_apis/[rest_types, eth2_rest_serialization,
rest_remote_signer_calls],
./slashing_protection
export
streams, options, keystore, phase0, altair, tables
streams, options, keystore, phase0, altair, tables, uri, crypto,
rest_types, eth2_rest_serialization, rest_remote_signer_calls
declareGauge validators,
"Number of validators attached to the beacon node"
@ -25,25 +30,29 @@ type
ValidatorKind* {.pure.} = enum
Local, Remote
ValidatorConnection* = object
inStream*: Stream
outStream*: Stream
pubKeyStr*: string
ValidatorConnection* = RestClientRef
ValidatorPrivateItem* = object
privateKey*: ValidatorPrivKey
description*: Option[string]
path*: Option[KeyPath]
uuid*: Option[string]
version*: Option[uint64]
description*: Option[string]
case kind*: ValidatorKind
of ValidatorKind.Local:
privateKey*: ValidatorPrivKey
path*: Option[KeyPath]
uuid*: Option[string]
of ValidatorKind.Remote:
publicKey*: CookedPubKey
remoteUrl*: Uri
flags*: set[RemoteKeystoreFlag]
AttachedValidator* = ref object
pubKey*: ValidatorPubKey
data*: ValidatorPrivateItem
case kind*: ValidatorKind
of ValidatorKind.Local:
data*: ValidatorPrivateItem
discard
of ValidatorKind.Remote:
connection*: ValidatorConnection
client*: RestClientRef
# The index at which this validator has been observed in the chain -
# it does not change as long as there are no reorgs on eth1 - however, the
@ -55,11 +64,22 @@ type
# if the validator will be aggregating (in the near future)
slotSignature*: Option[tuple[slot: Slot, signature: ValidatorSig]]
SignResponse* = Web3SignerDataResponse
SignatureResult* = Result[ValidatorSig, string]
AttestationResult* = Result[Attestation, string]
SyncCommitteeMessageResult* = Result[SyncCommitteeMessage, string]
ValidatorPool* = object
validators*: Table[ValidatorPubKey, AttachedValidator]
slashingProtection*: SlashingProtectionDB
func shortLog*(v: AttachedValidator): string = shortLog(v.pubKey)
func shortLog*(v: AttachedValidator): string =
case v.kind
of ValidatorKind.Local:
shortLog(v.pubKey)
of ValidatorKind.Remote:
shortLog(v.pubKey) & "@" & $v.client.address.getUri()
func init*(T: type ValidatorPool,
slashingProtectionDB: SlashingProtectionDB): T =
@ -84,10 +104,14 @@ proc addLocalValidator*(pool: var ValidatorPool, item: ValidatorPrivateItem,
proc addLocalValidator*(pool: var ValidatorPool, item: ValidatorPrivateItem) =
addLocalValidator(pool, item, none[ValidatorIndex]())
proc addRemoteValidator*(pool: var ValidatorPool, pubKey: ValidatorPubKey,
v: AttachedValidator) =
proc addRemoteValidator*(pool: var ValidatorPool, item: ValidatorPrivateItem,
client: RestClientRef, index: Option[ValidatorIndex]) =
let pubKey = item.publicKey.toPubKey()
let v = AttachedValidator(kind: ValidatorKind.Remote, pubKey: pubKey,
index: index, data: item, client: client)
pool.validators[pubKey] = v
notice "Remote validator attached", pubKey, validator = shortLog(v)
notice "Remote validator attached", pubKey, validator = shortLog(v),
remote_signer = $item.remoteUrl
validators.set(pool.count().int64)
proc getValidator*(pool: ValidatorPool,
@ -131,129 +155,237 @@ iterator items*(pool: ValidatorPool): AttachedValidator =
for item in pool.validators.values():
yield item
proc signWithRemoteValidator(v: AttachedValidator,
data: Eth2Digest): Future[ValidatorSig] {.async.} =
v.connection.inStream.writeLine(v.connection.pubKeyStr, " ", $data)
v.connection.inStream.flush()
var line = newStringOfCap(120).TaintedString
discard v.connection.outStream.readLine(line)
return ValidatorSig.fromHex(line).get()
proc signWithRemoteValidator*(v: AttachedValidator, fork: Fork,
genesis_validators_root: Eth2Digest,
blck: ForkedBeaconBlock): Future[SignResponse] {.
async.} =
let request = Web3SignerRequest.init(fork, genesis_validators_root, blck)
debug "Signing block proposal using remote signer",
validator = shortLog(v)
return await v.client.signData(v.pubKey, request)
proc signWithRemoteValidator*(v: AttachedValidator, fork: Fork,
genesis_validators_root: Eth2Digest,
adata: AttestationData): Future[SignResponse] {.
async.} =
let request = Web3SignerRequest.init(fork, genesis_validators_root, adata)
debug "Signing block proposal using remote signer",
validator = shortLog(v)
return await v.client.signData(v.pubKey, request)
proc signWithRemoteValidator*(v: AttachedValidator, fork: Fork,
genesis_validators_root: Eth2Digest,
epoch: Epoch): Future[SignResponse] {.
async.} =
let request = Web3SignerRequest.init(fork, genesis_validators_root, epoch)
debug "Generating randao reveal signature using remote signer",
validator = shortLog(v)
return await v.client.signData(v.pubKey, request)
proc signWithRemoteValidator*(v: AttachedValidator, fork: Fork,
genesis_validators_root: Eth2Digest,
proof: AggregateAndProof): Future[SignResponse] {.
async.} =
let request = Web3SignerRequest.init(fork, genesis_validators_root, proof)
debug "Signing aggregate and proof using remote signer",
validator = shortLog(v)
return await v.client.signData(v.pubKey, request)
proc signWithRemoteValidator*(v: AttachedValidator, fork: Fork,
genesis_validators_root: Eth2Digest,
slot: Slot): Future[SignResponse] {.
async.} =
let request = Web3SignerRequest.init(fork, genesis_validators_root, slot)
debug "Signing aggregate slot using remote signer",
validator = shortLog(v)
return await v.client.signData(v.pubKey, request)
proc signWithRemoteValidator*(v: AttachedValidator, fork: Fork,
genesis_validators_root: Eth2Digest,
slot: Slot,
blockRoot: Eth2Digest): Future[SignResponse] {.
async.} =
let request = Web3SignerRequest.init(fork, genesis_validators_root, blockRoot,
slot)
debug "Signing sync committee message using remote signer",
validator = shortLog(v)
return await v.client.signData(v.pubKey, request)
proc signWithRemoteValidator*(v: AttachedValidator, fork: Fork,
genesis_validators_root: Eth2Digest,
slot: Slot,
subIndex: uint64): Future[SignResponse] {.
async.} =
let request = Web3SignerRequest.init(
fork, genesis_validators_root,
SyncAggregatorSelectionData(slot: slot, subcommittee_index: subIndex),
)
debug "Signing sync aggregator selection data using remote signer",
validator = shortLog(v)
return await v.client.signData(v.pubKey, request)
proc signWithRemoteValidator*(v: AttachedValidator, fork: Fork,
genesis_validators_root: Eth2Digest,
contribution: ContributionAndProof
): Future[SignResponse] {.
async.} =
let request = Web3SignerRequest.init(
fork, genesis_validators_root, contribution
)
debug "Signing sync contribution and proof message using remote signer",
validator = shortLog(v)
return await v.client.signData(v.pubKey, request)
# https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/phase0/validator.md#signature
proc signBlockProposal*(v: AttachedValidator, fork: Fork,
genesis_validators_root: Eth2Digest, slot: Slot,
blockRoot: Eth2Digest): Future[ValidatorSig] {.async.} =
blockRoot: Eth2Digest, blck: ForkedBeaconBlock
): Future[SignatureResult] {.async.} =
return
case v.kind
of ValidatorKind.Local:
get_block_signature(fork, genesis_validators_root, slot, blockRoot,
v.data.privateKey).toValidatorSig()
SignatureResult.ok(
get_block_signature(fork, genesis_validators_root, slot, blockRoot,
v.data.privateKey).toValidatorSig()
)
of ValidatorKind.Remote:
let root = compute_block_root(fork, genesis_validators_root, slot,
blockRoot)
await signWithRemoteValidator(v, root)
let res = await signWithRemoteValidator(v, fork, genesis_validators_root,
blck)
if res.isErr():
SignatureResult.err(res.error())
else:
SignatureResult.ok(res.get().toValidatorSig())
proc signAttestation*(v: AttachedValidator,
data: AttestationData,
fork: Fork, genesis_validators_root: Eth2Digest):
Future[ValidatorSig] {.async.} =
Future[SignatureResult] {.async.} =
return
case v.kind
of ValidatorKind.Local:
get_attestation_signature(fork, genesis_validators_root, data,
v.data.privateKey).toValidatorSig()
SignatureResult.ok(
get_attestation_signature(fork, genesis_validators_root, data,
v.data.privateKey).toValidatorSig()
)
of ValidatorKind.Remote:
let root = compute_attestation_root(fork, genesis_validators_root, data)
await signWithRemoteValidator(v, root)
let res = await signWithRemoteValidator(v, fork, genesis_validators_root,
data)
if res.isErr():
SignatureResult.err(res.error())
else:
SignatureResult.ok(res.get().toValidatorSig())
proc produceAndSignAttestation*(validator: AttachedValidator,
attestationData: AttestationData,
committeeLen: int, indexInCommittee: Natural,
fork: Fork,
genesis_validators_root: Eth2Digest):
Future[Attestation] {.async.} =
Future[AttestationResult] {.async.} =
let validatorSignature =
await validator.signAttestation(attestationData, fork,
genesis_validators_root)
block:
let res = await validator.signAttestation(attestationData, fork,
genesis_validators_root)
if res.isErr():
return AttestationResult.err(res.error())
res.get()
var aggregationBits = CommitteeValidatorsBits.init(committeeLen)
aggregationBits.setBit indexInCommittee
return Attestation(data: attestationData, signature: validatorSignature,
aggregation_bits: aggregationBits)
return AttestationResult.ok(
Attestation(data: attestationData, signature: validatorSignature,
aggregation_bits: aggregationBits)
)
proc signAggregateAndProof*(v: AttachedValidator,
aggregate_and_proof: AggregateAndProof,
fork: Fork, genesis_validators_root: Eth2Digest):
Future[ValidatorSig] {.async.} =
Future[SignatureResult] {.async.} =
return
case v.kind
of ValidatorKind.Local:
get_aggregate_and_proof_signature(fork, genesis_validators_root,
aggregate_and_proof,
v.data.privateKey).toValidatorSig()
SignatureResult.ok(
get_aggregate_and_proof_signature(fork, genesis_validators_root,
aggregate_and_proof,
v.data.privateKey).toValidatorSig()
)
of ValidatorKind.Remote:
let root = compute_aggregate_and_proof_root(fork, genesis_validators_root,
aggregate_and_proof)
await signWithRemoteValidator(v, root)
let res = await signWithRemoteValidator(v, fork, genesis_validators_root,
aggregate_and_proof)
if res.isErr():
SignatureResult.err(res.error())
else:
SignatureResult.ok(res.get().toValidatorSig())
# https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/altair/validator.md#prepare-sync-committee-message
proc signSyncCommitteeMessage*(v: AttachedValidator,
slot: Slot,
fork: Fork,
slot: Slot, fork: Fork,
genesis_validators_root: Eth2Digest,
block_root: Eth2Digest): Future[SyncCommitteeMessage] {.async.} =
let
signing_root = sync_committee_msg_signing_root(
fork, slot.epoch, genesis_validators_root, block_root)
block_root: Eth2Digest
): Future[SyncCommitteeMessageResult] {.async.} =
let signature =
case v.kind
of ValidatorKind.Local:
blsSign(v.data.privateKey, signing_root.data).toValidatorSig
let signing_root = sync_committee_msg_signing_root(
fork, slot.epoch, genesis_validators_root, block_root)
blsSign(v.data.privateKey, signing_root.data).toValidatorSig()
of ValidatorKind.Remote:
await signWithRemoteValidator(v, signing_root)
let res = await signWithRemoteValidator(v, fork, genesis_validators_root,
slot, block_root)
if res.isErr():
return SyncCommitteeMessageResult.err(res.error())
res.get().toValidatorSig()
return SyncCommitteeMessage(
slot: slot,
beacon_block_root: block_root,
validator_index: v.index.get.uint64,
signature: signature)
# https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/altair/validator.md#aggregation-selection
proc getSyncCommitteeSelectionProof*(
v: AttachedValidator,
fork: Fork,
genesis_validators_root: Eth2Digest,
slot: Slot,
subcommittee_index: uint64): Future[ValidatorSig] {.async.} =
let
signing_root = sync_committee_selection_proof_signing_root(
fork, genesis_validators_root, slot, subcommittee_index)
return
SyncCommitteeMessageResult.ok(
SyncCommitteeMessage(
slot: slot,
beacon_block_root: block_root,
validator_index: uint64(v.index.get()),
signature: signature
)
)
# https://github.com/ethereum/consensus-specs/blob/v1.1.2/specs/altair/validator.md#aggregation-selection
proc getSyncCommitteeSelectionProof*(v: AttachedValidator,
fork: Fork,
genesis_validators_root: Eth2Digest,
slot: Slot,
subcommittee_index: uint64
): Future[SignatureResult] {.async.} =
return
case v.kind
of ValidatorKind.Local:
blsSign(v.data.privateKey, signing_root.data).toValidatorSig
let signing_root = sync_committee_selection_proof_signing_root(
fork, genesis_validators_root, slot, subcommittee_index)
SignatureResult.ok(
blsSign(v.data.privateKey, signing_root.data).toValidatorSig()
)
of ValidatorKind.Remote:
await signWithRemoteValidator(v, signing_root)
# https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/altair/validator.md#signature
proc sign*(
v: AttachedValidator,
msg: ref SignedContributionAndProof,
fork: Fork,
genesis_validators_root: Eth2Digest) {.async.} =
let
signing_root = contribution_and_proof_signing_root(
fork, genesis_validators_root, msg.message)
let res = await signWithRemoteValidator(v, fork, genesis_validators_root,
slot, subcommittee_index)
if res.isErr():
SignatureResult.err(res.error())
else:
SignatureResult.ok(res.get().toValidatorSig())
# https://github.com/ethereum/consensus-specs/blob/v1.1.0-beta.4/specs/altair/validator.md#signature
proc sign*(v: AttachedValidator, msg: ref SignedContributionAndProof,
fork: Fork, genesis_validators_root: Eth2Digest
): Future[SignatureResult] {.async.} =
msg.signature =
case v.kind
of ValidatorKind.Local:
blsSign(v.data.privateKey, signing_root.data).toValidatorSig
let signing_root = contribution_and_proof_signing_root(
fork, genesis_validators_root, msg.message)
blsSign(v.data.privateKey, signing_root.data).toValidatorSig()
of ValidatorKind.Remote:
await signWithRemoteValidator(v, signing_root)
let res = await signWithRemoteValidator(v, fork, genesis_validators_root,
msg.message)
if res.isErr():
return SignatureResult.err(res.error())
res.get().toValidatorSig()
return SignatureResult.ok(msg.signature)
# https://github.com/ethereum/consensus-specs/blob/v1.1.5/specs/phase0/validator.md#randao-reveal
func genRandaoReveal*(k: ValidatorPrivKey, fork: Fork,
@ -264,30 +396,38 @@ func genRandaoReveal*(k: ValidatorPrivKey, fork: Fork,
proc genRandaoReveal*(v: AttachedValidator, fork: Fork,
genesis_validators_root: Eth2Digest, slot: Slot):
Future[ValidatorSig] {.async.} =
Future[SignatureResult] {.async.} =
return
case v.kind
of ValidatorKind.Local:
genRandaoReveal(v.data.privateKey, fork, genesis_validators_root,
slot).toValidatorSig()
SignatureResult.ok(genRandaoReveal(v.data.privateKey, fork,
genesis_validators_root,
slot).toValidatorSig())
of ValidatorKind.Remote:
let root = compute_epoch_root(fork, genesis_validators_root,
slot.compute_epoch_at_slot)
await signWithRemoteValidator(v, root)
let res = await signWithRemoteValidator(v, fork, genesis_validators_root,
slot.compute_epoch_at_slot())
if res.isErr():
SignatureResult.err(res.error())
else:
SignatureResult.ok(res.get().toValidatorSig())
proc getSlotSig*(v: AttachedValidator, fork: Fork,
genesis_validators_root: Eth2Digest, slot: Slot
): Future[ValidatorSig] {.async.} =
): Future[SignatureResult] {.async.} =
if v.slotSignature.isSome() and v.slotSignature.get().slot == slot:
return v.slotSignature.get().signature
return SignatureResult.ok(v.slotSignature.get().signature)
let signature =
case v.kind
of ValidatorKind.Local:
get_slot_signature(fork, genesis_validators_root, slot,
v.data.privateKey).toValidatorSig()
v.data.privateKey).toValidatorSig()
of ValidatorKind.Remote:
let root = compute_slot_root(fork, genesis_validators_root, slot)
await signWithRemoteValidator(v, root)
let res = await signWithRemoteValidator(v, fork, genesis_validators_root,
slot)
if res.isErr():
return SignatureResult.err(res.error())
res.get().toValidatorSig()
v.slotSignature = some((slot, signature))
return signature
return SignatureResult.ok(signature)

View File

@ -223,7 +223,7 @@ if [[ "${HAVE_LSOF}" == "1" ]]; then
fi
# Build the binaries
BINARIES="nimbus_beacon_node nimbus_signing_process nimbus_validator_client deposit_contract"
BINARIES="nimbus_beacon_node nimbus_signing_node nimbus_validator_client deposit_contract"
if [[ "$ENABLE_LOGTRACE" == "1" ]]; then
BINARIES="${BINARIES} logtrace"
fi

View File

@ -210,12 +210,14 @@ suite "Gossip validation - Extra": # Not based on preset config
index = subcommittee[0]
expectedCount = subcommittee.count(index)
pubkey = state[].data.validators[index].pubkey
privateItem = ValidatorPrivateItem(privateKey: MockPrivKeys[index])
privateItem = ValidatorPrivateItem(kind: ValidatorKind.Local,
privateKey: MockPrivKeys[index])
validator = AttachedValidator(pubKey: pubkey,
kind: ValidatorKind.Local, data: privateItem, index: some(index))
msg = waitFor signSyncCommitteeMessage(
resMsg = waitFor signSyncCommitteeMessage(
validator, slot,
state[].data.fork, state[].data.genesis_validators_root, state[].root)
msg = resMsg.get()
syncCommitteeMsgPool = newClone(SyncCommitteeMsgPool.init())
res = validateSyncCommitteeMessage(
@ -239,8 +241,9 @@ suite "Gossip validation - Extra": # Not based on preset config
contribution.message.contribution)
syncCommitteeMsgPool[].addContribution(
contribution[], contribution.message.contribution.signature.load.get)
waitFor validator.sign(
let signRes = waitFor validator.sign(
contribution, state[].data.fork, state[].data.genesis_validators_root)
doAssert(signRes.isOk())
contribution
aggregate = syncCommitteeMsgPool[].produceSyncAggregate(state[].root)