Skip to content

Commit

Permalink
Merge pull request #208 from dappnode/pablo/split-validators
Browse files Browse the repository at this point in the history
Split validators functions into multiple files
  • Loading branch information
pablomendezroyo authored Mar 20, 2023
2 parents 67e5ac5 + 1faa559 commit fff0df2
Show file tree
Hide file tree
Showing 8 changed files with 540 additions and 524 deletions.
54 changes: 54 additions & 0 deletions packages/brain/src/calls/deleteValidators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
Web3signerDeleteRequest,
Web3signerDeleteResponse,
} from "@stakingbrain/common";
import { cron, validatorApi, signerApi, brainDb } from "../index.js";
import logger from "../modules/logger/index.js";

/**
* Delete keystores:
* 1. Write on db
* 2. Delete keystores on web3signer API
* 3. Delete pubkeys on validator API
* 4. Delete feeRecipient on Validator API
* @param deleteRequest
* @returns
*/
export async function deleteValidators(
deleteRequest: Web3signerDeleteRequest
): Promise<Web3signerDeleteResponse> {
try {
// IMPORTANT: stop the cron. This removes the scheduled cron task from the task queue
// and prevents the cron from running while we are deleting validators
cron.stop();

// Delete feeRecipient on Validator API
for (const pubkey of deleteRequest.pubkeys)
await validatorApi
.deleteFeeRecipient(pubkey)
.then(() => logger.debug(`Deleted fee recipient in validator API`))
.catch((err) =>
logger.error(`Error deleting validator feeRecipient`, err)
);
// Delete pubkeys on validator API
await validatorApi
.deleteRemoteKeys(deleteRequest)
.then(() => logger.debug(`Deleted pubkeys in validator API`))
.catch((err) => logger.error(`Error deleting validator pubkeys`, err));

// Delete keystores on web3signer API
const web3signerDeleteResponse = await signerApi.deleteKeystores(
deleteRequest
);

// Write on db
brainDb.deleteValidators(deleteRequest.pubkeys);

// IMPORTANT: start the cron
cron.start();
return web3signerDeleteResponse;
} catch (e) {
cron.restart();
throw e;
}
}
148 changes: 148 additions & 0 deletions packages/brain/src/calls/exitValidators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import {
BeaconchainPoolVoluntaryExitsPostRequest,
ValidatorExitExecute,
ValidatorExitGet,
} from "@stakingbrain/common";
import { beaconchainApi, validatorApi, signerApi, brainDb } from "../index.js";
import logger from "../modules/logger/index.js";

/**
* Get exit validators info signed
* @param pubkeys The public keys of the validators to exit
* @returns The exit data signed of each validator
*/
export async function getExitValidators({
pubkeys,
}: {
pubkeys: string[];
}): Promise<BeaconchainPoolVoluntaryExitsPostRequest[]> {
const validatorsExit = await _getExitValidators(pubkeys);
logger.debug(validatorsExit);
return validatorsExit;
}

/**
* Performs a voluntary exit for a given set of validators as identified via `pubkeys`
* @param pubkeys The public keys of the validators to exit
* @returns The exit status of each validator
*/
export async function exitValidators({
pubkeys,
}: {
pubkeys: string[];
}): Promise<ValidatorExitExecute[]> {
const validatorsToExit = await _getExitValidators(pubkeys);
const exitValidatorsResponses: ValidatorExitExecute[] = [];
for (const validatorToExit of validatorsToExit) {
try {
await beaconchainApi.postVoluntaryExits({
postVoluntaryExitsRequest: {
message: {
epoch: validatorToExit.message.epoch,
validator_index: validatorToExit.message.validator_index,
},
signature: validatorToExit.signature,
},
});
exitValidatorsResponses.push({
pubkey: validatorToExit.pubkey,
status: {
exited: true,
message: "Successfully exited validator",
},
});
} catch (e) {
exitValidatorsResponses.push({
pubkey: validatorToExit.pubkey,
status: {
exited: false,
message: `Error exiting validator ${e.message}`,
},
});
}
}

const exitedValidatorsPubkeys = exitValidatorsResponses
.filter((validator) => validator.status.exited === true)
.map((validator) => validator.pubkey);

// Delete the validator from the validator API
await validatorApi
.deleteRemoteKeys({ pubkeys: exitedValidatorsPubkeys })
.then(() => logger.debug(`Deleted pubkeys in validator API`))
.catch((err) => logger.error(`Error deleting validator pubkeys`, err));

// Delete the validator from the web3signer API
await signerApi
.deleteKeystores({ pubkeys: exitedValidatorsPubkeys })
.then(() => logger.debug(`Deleted pubkeys in web3signer API`));

// Delete the validator from the brain db
brainDb.deleteValidators(exitedValidatorsPubkeys);

return exitValidatorsResponses;
}

/**
* Get exit validators info
* @param pubkeys The public keys of the validators to exit
* @returns The exit validators info signed
*/
async function _getExitValidators(
pubkeys: string[]
): Promise<ValidatorExitGet[]> {
// Get the current epoch from the beaconchain API to exit the validators
const currentEpoch = await beaconchainApi.getCurrentEpoch();

// Get the fork from the beaconchain API to sign the voluntary exit
const fork = await beaconchainApi.getForkFromState({ state_id: "head" });

// Get the genesis from the beaconchain API to sign the voluntary exit
const genesis = await beaconchainApi.getGenesis();

// Get the validators indexes from the validator API
const validatorPubkeysIndexes = (
await Promise.all(
pubkeys.map((pubkey) =>
beaconchainApi.getValidatorFromState({ state: "head", pubkey })
)
)
).map((validator) => {
return {
pubkey: validator.data.validator.pubkey,
index: validator.data.index,
};
});

const validatorsExit: ValidatorExitGet[] = [];
for (const validatorIndex of validatorPubkeysIndexes) {
const validatorSignature = await signerApi.signVoluntaryExit({
signerVoluntaryExitRequest: {
type: "VOLUNTARY_EXIT",
fork_info: {
fork: {
previous_version: fork.data.previous_version,
current_version: fork.data.current_version,
epoch: fork.data.epoch,
},
genesis_validators_root: genesis.data.genesis_validators_root,
},
voluntary_exit: {
epoch: currentEpoch.toString(),
validator_index: validatorIndex.index,
},
},
pubkey: validatorIndex.pubkey,
});
validatorsExit.push({
pubkey: validatorIndex.pubkey,
message: {
epoch: currentEpoch.toString(),
validator_index: validatorIndex.index,
},
signature: validatorSignature,
});
}

return validatorsExit;
}
79 changes: 79 additions & 0 deletions packages/brain/src/calls/getValidators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import {
CustomValidatorGetResponse,
WithdrawalCredentialsFormat,
isValidEcdsaPubkey,
isValidBlsPubkey,
} from "@stakingbrain/common";
import { brainDb, validatorApi, signerApi, beaconchainApi } from "../index.js";
import logger from "../modules/logger/index.js";

/**
* Get all validators from db
* If running in development mode (NODE_ENV === "development") it will returns booleans for
* validatorImported and validatorFeeRecipientCorrect checks from the validator API
* @returns
*/
export async function getValidators(): Promise<CustomValidatorGetResponse[]> {
const data = brainDb.data;
if (!data) return [];

const validatorPubkeys = (
await validatorApi.getRemoteKeys().catch((e) => {
logger.error(e);
return { data: [] };
})
).data.map((validator) => validator.pubkey);

const signerPubkeys = (
await signerApi.getKeystores().catch((e) => {
logger.error(e);
return { data: [] };
})
).data.map((key) => key.validating_pubkey);

const validatorsFeeRecipients = await Promise.all(
validatorPubkeys.map((pubkey) => validatorApi.getFeeRecipient(pubkey))
).catch((e) => {
logger.error(e);
return [];
});

const validators: CustomValidatorGetResponse[] = [];
for (const [pubkey, { tag, feeRecipient }] of Object.entries(data)) {
let format: WithdrawalCredentialsFormat,
withdrawalAddress = "";
try {
withdrawalAddress = (
await beaconchainApi.getValidatorFromState({
state: "head",
pubkey,
})
).data.validator.withdrawal_credentials;
format = isValidEcdsaPubkey(withdrawalAddress)
? "ecdsa"
: isValidBlsPubkey(pubkey)
? "bls"
: "unknown";
} catch (e) {
logger.error(e);
format = "error";
}

validators.push({
pubkey,
tag,
feeRecipient,
withdrawalCredentials: {
address: withdrawalAddress,
format,
},
validatorImported: validatorPubkeys.includes(pubkey),
signerImported: signerPubkeys.includes(pubkey),
validatorFeeRecipientCorrect: validatorsFeeRecipients.some(
(feeRecipient) => feeRecipient === feeRecipient
),
});
}

return validators;
}
Loading

0 comments on commit fff0df2

Please sign in to comment.