Remote signing client/server. (#3077)
This commit is contained in:
parent
3aa804035f
commit
e62c7c7c37
8
Makefile
8
Makefile
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
|
@ -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"
|
|
@ -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()
|
|
@ -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"
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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]())
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Reference in New Issue