Workaround for issue #4216.

This commit is contained in:
cheatfate 2023-03-20 13:58:54 +02:00 committed by Zahary Karadjov
parent 769ed00203
commit 596006be08
No known key found for this signature in database
GPG Key ID: C1F42EAFF38D570F
3 changed files with 194 additions and 85 deletions

View File

@ -711,6 +711,11 @@ type
defaultValueDesc: $defaultBeaconNodeDesc defaultValueDesc: $defaultBeaconNodeDesc
name: "rest-url" .}: string name: "rest-url" .}: string
printData* {.
desc: "Print signed exit message instead of publishing it"
defaultValue: false
name: "print" .}: bool
of BNStartUpCmd.record: of BNStartUpCmd.record:
case recordCmd* {.command.}: RecordCmd case recordCmd* {.command.}: RecordCmd
of RecordCmd.create: of RecordCmd.create:

View File

@ -8,50 +8,74 @@
import import
std/[os, sequtils, times], std/[os, sequtils, times],
stew/byteutils,
chronicles, chronicles,
./spec/eth2_apis/rest_beacon_client, ./spec/eth2_apis/rest_beacon_client,
./spec/signatures, ./spec/signatures,
./validators/keystore_management, ./validators/keystore_management,
"."/[conf, beacon_clock, filepath] "."/[conf, beacon_clock, filepath]
proc getSignedExitMessage(config: BeaconNodeConf, type
validatorKeyAsStr: string, ValidatorStorageKind* {.pure.} = enum
exitAtEpoch: Epoch, Keystore, Identifier
validatorIdx: uint64 ,
fork: Fork,
genesis_validators_root: Eth2Digest): SignedVoluntaryExit =
let
validatorsDir = config.validatorsDir
keystoreDir = validatorsDir / validatorKeyAsStr
if not dirExists(keystoreDir): ValidatorStorage* = object
echo "The validator keystores directory '" & validatorsDir & case kind: ValidatorStorageKind
"' does not contain a keystore for the selected validator with public " & of ValidatorStorageKind.Keystore:
"key '" & validatorKeyAsStr & "'." privateKey: ValidatorPrivKey
quit 1 of ValidatorStorageKind.Identifier:
ident: ValidatorIdent
let signingItem = loadKeystore( proc getSignedExitMessage(
validatorsDir, config: BeaconNodeConf,
config.secretsDir, storage: ValidatorStorage,
validatorKeyAsStr, validatorKeyAsStr: string,
config.nonInteractive, exitAtEpoch: Epoch,
nil) validatorIdx: uint64,
fork: Fork,
genesis_validators_root: Eth2Digest
): SignedVoluntaryExit =
if signingItem.isNone: let signingKey =
fatal "Unable to continue without decrypted signing key" case storage.kind
quit 1 of ValidatorStorageKind.Identifier:
let
validatorsDir = config.validatorsDir
keystoreDir = validatorsDir / validatorKeyAsStr
if not dirExists(keystoreDir):
echo "The validator keystores directory '" & validatorsDir &
"' does not contain a keystore for the selected validator " &
"with public key '" & validatorKeyAsStr & "'."
quit 1
let signingItem = loadKeystore(
validatorsDir,
config.secretsDir,
validatorKeyAsStr,
config.nonInteractive,
nil)
if signingItem.isNone:
fatal "Unable to continue without decrypted signing key"
quit 1
signingItem.get().privateKey
of ValidatorStorageKind.Keystore:
storage.privateKey
var signedExit = SignedVoluntaryExit( var signedExit = SignedVoluntaryExit(
message: VoluntaryExit( message: VoluntaryExit(
epoch: exitAtEpoch, epoch: exitAtEpoch,
validator_index: validatorIdx)) validator_index: validatorIdx
)
)
signedExit.signature = signedExit.signature =
block: get_voluntary_exit_signature(fork, genesis_validators_root,
let key = signingItem.get.privateKey signedExit.message,
get_voluntary_exit_signature(fork, genesis_validators_root, signingKey).toValidatorSig()
signedExit.message, key).toValidatorSig()
signedExit signedExit
type type
@ -109,6 +133,28 @@ proc askForExitConfirmation(): ClientExitAction =
else: else:
ClientExitAction.quiting ClientExitAction.quiting
proc getValidator*(name: string): Result[ValidatorStorage, string] =
let ident = ValidatorIdent.decodeString(name)
if ident.isErr():
if not(isFile(name)):
return err($ident.error)
let key = importKeystoreFromFile(name)
if key.isErr():
return err(key.error())
ok(ValidatorStorage(kind: ValidatorStorageKind.Keystore,
privateKey: key.get()))
else:
ok(ValidatorStorage(kind: ValidatorStorageKind.Identifier,
ident: ident.get()))
proc getIdent*(storage: ValidatorStorage): ValidatorIdent =
case storage.kind
of ValidatorStorageKind.Keystore:
ValidatorIdent(kind: ValidatorQueryKind.Key,
key: storage.privateKey.toPubKey().toPubKey())
of ValidatorStorageKind.Identifier:
storage.ident
proc restValidatorExit(config: BeaconNodeConf) {.async.} = proc restValidatorExit(config: BeaconNodeConf) {.async.} =
let let
client = RestClientRef.new(config.restUrlForExit).valueOr: client = RestClientRef.new(config.restUrlForExit).valueOr:
@ -118,32 +164,29 @@ proc restValidatorExit(config: BeaconNodeConf) {.async.} =
value: StateIdentType.Head) value: StateIdentType.Head)
blockIdentHead = BlockIdent(kind: BlockQueryKind.Named, blockIdentHead = BlockIdent(kind: BlockQueryKind.Named,
value: BlockIdentType.Head) value: BlockIdentType.Head)
validatorIdent = ValidatorIdent.decodeString(config.exitedValidator) validator = getValidator(config.exitedValidator).valueOr:
fatal "Incorrect validator index, key or keystore path specified",
if validatorIdent.isErr(): value = config.exitedValidator, reason = error
fatal "Incorrect validator index or key specified", quit 1
err = $validatorIdent.error()
quit 1
let restValidator = try: let restValidator = try:
let response = await client.getStateValidatorPlain(stateIdHead, let response = await client.getStateValidatorPlain(stateIdHead,
validatorIdent.get()) validator.getIdent())
if response.status == 200: if response.status == 200:
let validator = decodeBytes(GetStateValidatorResponse, let validatorInfo = decodeBytes(GetStateValidatorResponse,
response.data, response.data, response.contentType)
response.contentType) if validatorInfo.isErr():
if validator.isErr(): raise newException(RestError, $validatorInfo.error)
raise newException(RestError, $validator.error) validatorInfo.get().data
validator.get().data
else: else:
raiseGenericError(response) raiseGenericError(response)
except CatchableError as err: except CatchableError as exc:
fatal "Failed to obtain information for validator", err = err.msg fatal "Failed to obtain information for validator", reason = exc.msg
quit 1 quit 1
let let
validator = restValidator.validator
validatorIdx = restValidator.index.uint64 validatorIdx = restValidator.index.uint64
validatorKey = restValidator.validator.pubkey
let genesis = try: let genesis = try:
let response = await client.getGenesisPlain() let response = await client.getGenesisPlain()
@ -156,9 +199,9 @@ proc restValidatorExit(config: BeaconNodeConf) {.async.} =
genesis.get().data genesis.get().data
else: else:
raiseGenericError(response) raiseGenericError(response)
except CatchableError as err: except CatchableError as exc:
fatal "Failed to obtain the genesis validators root of the network", fatal "Failed to obtain the genesis validators root of the network",
err = err.msg reason = exc.msg
quit 1 quit 1
let exitAtEpoch = if config.exitAtEpoch.isSome: let exitAtEpoch = if config.exitAtEpoch.isSome:
@ -183,56 +226,72 @@ proc restValidatorExit(config: BeaconNodeConf) {.async.} =
fork.get().data fork.get().data
else: else:
raiseGenericError(response) raiseGenericError(response)
except CatchableError as err: except CatchableError as exc:
fatal "Failed to obtain the fork id of the head state", fatal "Failed to obtain the fork id of the head state",
err = err.msg reason = exc.msg
quit 1 quit 1
let let
genesis_validators_root = genesis.genesis_validators_root genesis_validators_root = genesis.genesis_validators_root
validatorKeyAsStr = "0x" & $validator.pubkey validatorKeyAsStr = "0x" & $validatorKey
signedExit = getSignedExitMessage(config, signedExit = getSignedExitMessage(config,
validator,
validatorKeyAsStr, validatorKeyAsStr,
exitAtEpoch, exitAtEpoch,
validatorIdx, validatorIdx,
fork, fork,
genesis_validators_root) genesis_validators_root)
try: if config.printData:
let choice = askForExitConfirmation() let bytes = encodeBytes(signedExit, "application/json").valueOr:
if choice == ClientExitAction.quiting: fatal "Unable to serialize signed exit message", reason = error
quit 0 quit 1
elif choice == ClientExitAction.confirmation:
let echoP "You can use following command to send voluntary exit message to " &
response = await client.submitPoolVoluntaryExit(signedExit) "remote beacon node host:\n"
success = response.status == 200
if success: echo "curl -X 'POST' \\"
echo "Successfully published voluntary exit for validator " & echo " '" & config.restUrlForExit &
$validatorIdx & "(" & validatorKeyAsStr[0..9] & ")." "/eth/v1/beacon/pool/voluntary_exits' \\"
echo " -H 'Accept: */*' \\"
echo " -H 'Content-Type: application/json' \\"
echo " -d '" & string.fromBytes(bytes) & "'"
quit 0
else:
try:
let choice = askForExitConfirmation()
if choice == ClientExitAction.quiting:
quit 0 quit 0
else: elif choice == ClientExitAction.confirmation:
let responseError = try: let
Json.decode(response.data, RestErrorMessage) response = await client.submitPoolVoluntaryExit(signedExit)
except CatchableError as err: success = response.status == 200
fatal "Failed to decode invalid error server response on `submitPoolVoluntaryExit` request", if success:
err = err.msg echo "Successfully published voluntary exit for validator " &
$validatorIdx & "(" & validatorKeyAsStr[0..9] & ")."
quit 0
else:
let responseError = try:
Json.decode(response.data, RestErrorMessage)
except CatchableError as exc:
fatal "Failed to decode invalid error server response on " &
"`submitPoolVoluntaryExit` request", reason = exc.msg
quit 1
let
responseMessage = responseError.message
responseStacktraces = responseError.stacktraces
echo "The voluntary exit was not submitted successfully."
echo responseMessage & ":"
for el in responseStacktraces.get():
echo el
echoP "Please try again."
quit 1 quit 1
let except CatchableError as err:
responseMessage = responseError.message fatal "Failed to send the signed exit message", reason = err.msg
responseStacktraces = responseError.stacktraces quit 1
echo "The voluntary exit was not submitted successfully."
echo responseMessage & ":"
for el in responseStacktraces.get():
echo el
echoP "Please try again."
quit 1
except CatchableError as err:
fatal "Failed to send the signed exit message",
err = err.msg
quit 1
proc handleValidatorExitCommand(config: BeaconNodeConf) {.async.} = proc handleValidatorExitCommand(config: BeaconNodeConf) {.async.} =
await restValidatorExit(config) await restValidatorExit(config)
@ -339,9 +398,10 @@ proc doDeposits*(config: BeaconNodeConf, rng: var HmacDrbgContext) {.
InputDir(cwd / "validator_keys") InputDir(cwd / "validator_keys")
else: else:
echo "The default search path for validator keys is a sub-directory " & echo "The default search path for validator keys is a sub-directory " &
"named 'validator_keys' in the current working directory. Since " & "named 'validator_keys' in the current working directory. " &
"no such directory exists, please either provide the correct path" & " Since no such directory exists, please either provide the " &
"as an argument or copy the imported keys in the expected location." "correct path as an argument or copy the imported keys in the " &
"expected location."
quit 1 quit 1
importKeystoresFromDir( importKeystoresFromDir(

View File

@ -1531,6 +1531,50 @@ proc resetAttributesNoError() =
try: stdout.resetAttributes() try: stdout.resetAttributes()
except IOError: discard except IOError: discard
proc importKeystoreFromFile*(
fileName: string
): Result[ValidatorPrivKey, string] =
var password: string # TODO consider using a SecretString type
defer: burnMem(password)
let
data = readAllChars(fileName).valueOr:
return err("Unable to read keystore file [" & ioErrorMsg(error) & "]")
keystore =
try:
parseKeystore(data)
except SerializationError as e:
return err("Invalid keystore file format [" &
e.formatMsg(fileName) & "]")
var firstDecryptionAttempt = true
while true:
var secret: seq[byte]
let status = decryptCryptoField(keystore.crypto,
KeystorePass.init(password),
secret)
case status
of DecryptionStatus.Success:
let privateKey = ValidatorPrivKey.fromRaw(secret).valueOr:
return err("Keystore holds invalid private key [" & $error & "]")
return ok(privateKey)
of DecryptionStatus.InvalidKeystore:
return err("Invalid keystore format")
of DecryptionStatus.InvalidPassword:
if firstDecryptionAttempt:
try:
const msg = "Please enter the password for decrypting '$1'"
echo msg % [fileName]
except ValueError:
raiseAssert "The format string above is correct"
firstDecryptionAttempt = false
else:
echo "The entered password was incorrect. Please try again."
if not(readPasswordInput("Password: ", password)):
echo "System error while entering password. Please try again."
if len(password) == 0: break
proc importKeystoresFromDir*(rng: var HmacDrbgContext, meth: ImportMethod, proc importKeystoresFromDir*(rng: var HmacDrbgContext, meth: ImportMethod,
importedDir, validatorsDir, secretsDir: string) = importedDir, validatorsDir, secretsDir: string) =
var password: string # TODO consider using a SecretString type var password: string # TODO consider using a SecretString type