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
name: "rest-url" .}: string
printData* {.
desc: "Print signed exit message instead of publishing it"
defaultValue: false
name: "print" .}: bool
of BNStartUpCmd.record:
case recordCmd* {.command.}: RecordCmd
of RecordCmd.create:

View File

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

View File

@ -1531,6 +1531,50 @@ proc resetAttributesNoError() =
try: stdout.resetAttributes()
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,
importedDir, validatorsDir, secretsDir: string) =
var password: string # TODO consider using a SecretString type