From 07f675565086bb69988881270b4ad741de230ea9 Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Wed, 4 Sep 2024 17:34:17 +0200 Subject: [PATCH 1/2] add missing endpoints --- packages/brain/package.json | 3 +- .../brain/src/modules/apiClients/beaconcha.ts | 2 +- .../src/modules/apiClients/beaconchain.ts | 32 ++++++++- .../brain/src/modules/apiClients/validator.ts | 71 +++++++++++++++++-- packages/common/package.json | 3 +- .../common/src/types/api/beaconchain/types.ts | 19 +++++ .../common/src/types/api/validator/types.ts | 24 +++++++ yarn.lock | 2 + 8 files changed, 144 insertions(+), 12 deletions(-) diff --git a/packages/brain/package.json b/packages/brain/package.json index 16ea39d3..790db028 100644 --- a/packages/brain/package.json +++ b/packages/brain/package.json @@ -32,6 +32,7 @@ "@types/lowdb": "^1.0.11", "@types/sinon": "^10.0.13", "rimraf": "^4.1.1", - "sinon": "^15.0.1" + "sinon": "^15.0.1", + "typescript": "^5.5.4" } } diff --git a/packages/brain/src/modules/apiClients/beaconcha.ts b/packages/brain/src/modules/apiClients/beaconcha.ts index 8f739136..caea3bbe 100644 --- a/packages/brain/src/modules/apiClients/beaconcha.ts +++ b/packages/brain/src/modules/apiClients/beaconcha.ts @@ -4,7 +4,7 @@ import { BeaconchaGetResponse } from "@stakingbrain/common"; const maxValidatorsPerRequest = 100; //For beaconcha.in --> TODO: is it the same for Gnosis? export class BeaconchaApi extends StandardApi { - /* + /** * Fetch info for every validator PK */ public async fetchAllValidatorsInfo({ pubkeys }: { pubkeys: string[] }): Promise { diff --git a/packages/brain/src/modules/apiClients/beaconchain.ts b/packages/brain/src/modules/apiClients/beaconchain.ts index a496e987..09372b1b 100644 --- a/packages/brain/src/modules/apiClients/beaconchain.ts +++ b/packages/brain/src/modules/apiClients/beaconchain.ts @@ -4,6 +4,7 @@ import { BeaconchainPoolVoluntaryExitsPostRequest, BeaconchainForkFromStateGetResponse, BeaconchainGenesisGetResponse, + BeaconchainStateFinalityCheckpointsPostResponse, Network, ApiParams } from "@stakingbrain/common"; @@ -60,13 +61,17 @@ export class Beaconchain extends StandardApi { /** * Get Fork object from requested state. * @see https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFork - * @param state_id - State identifier. Can be one of: "head" (canonical head in node's view), "genesis", "finalized", , . + * @param stateId - State identifier. Can be one of: "head" (canonical head in node's view), "genesis", "finalized", , . */ - public async getForkFromState({ state_id }: { state_id: string }): Promise { + public async getForkFromState({ + stateId + }: { + stateId: "head" | "genesis" | "finalized"; + }): Promise { try { return (await this.request({ method: "GET", - endpoint: path.join(this.beaconchainEndpoint, "states", state_id, "fork") + endpoint: path.join(this.beaconchainEndpoint, "states", stateId, "fork") })) as BeaconchainForkFromStateGetResponse; } catch (e) { e.message += `Error getting (GET) fork from beaconchain. `; @@ -74,6 +79,27 @@ export class Beaconchain extends StandardApi { } } + /** + * Returns finality checkpoints for state with given 'stateId'. In case finality is not yet achieved, checkpoint should return epoch 0 and ZERO_HASH as root. + * @see https://ethereum.github.io/beacon-APIs/#/Beacon/getStateFinalityCheckpoints + * @param stateId - State identifier. Can be one of: "head" (canonical head in node's view), "genesis", "finalized", , . + */ + public async getStateFinalityCheckpoints({ + stateId + }: { + stateId: "head" | "genesis" | "finalized"; + }): Promise { + try { + return await this.request({ + method: "GET", + endpoint: path.join(this.beaconchainEndpoint, "states", stateId, "finality_checkpoints") + }); + } catch (e) { + e.message += `Error getting (GET) state finality checkpoints from beaconchain. `; + throw e; + } + } + /** * Retrieves validator from state and public key. * @see https://ethereum.github.io/beacon-APIs/#/Beacon/getStateValidator diff --git a/packages/brain/src/modules/apiClients/validator.ts b/packages/brain/src/modules/apiClients/validator.ts index f625a904..a0e018a0 100644 --- a/packages/brain/src/modules/apiClients/validator.ts +++ b/packages/brain/src/modules/apiClients/validator.ts @@ -5,6 +5,8 @@ import { ValidatorGetRemoteKeysResponse, ValidatorPostRemoteKeysRequest, ValidatorPostRemoteKeysResponse, + ValidatorProposerDutiesGetResponse, + ValidatorAttesterDutiesPostResponse, prefix0xPubkey } from "@stakingbrain/common"; import { StandardApi } from "./standard.js"; @@ -18,10 +20,10 @@ export class ValidatorApi extends StandardApi { private remoteKeymanagerEndpoint = "/eth/v1/remotekeys"; /** - * Fee recipient endpoint - * @see https://ethereum.github.io/keymanager-APIs/#/Fee%20Recipient + * Validator endpoint + * @see https://ethereum.github.io/beacon-APIs/#/Validator */ - private feeRecipientEndpoint = "/eth/v1/validator"; + private validatorEndpoint = "/eth/v1/validator"; /** * List the validator public key to eth address mapping for fee recipient feature on a specific public key. @@ -31,7 +33,7 @@ export class ValidatorApi extends StandardApi { try { return (await this.request({ method: "GET", - endpoint: path.join(this.feeRecipientEndpoint, prefix0xPubkey(publicKey), "feerecipient") + endpoint: path.join(this.validatorEndpoint, prefix0xPubkey(publicKey), "feerecipient") })) as ValidatorGetFeeResponse; } catch (e) { e.message += `Error getting (GET) fee recipient for pubkey ${publicKey} from validator. `; @@ -47,7 +49,7 @@ export class ValidatorApi extends StandardApi { try { await this.request({ method: "POST", - endpoint: path.join(this.feeRecipientEndpoint, prefix0xPubkey(publicKey), "feerecipient"), + endpoint: path.join(this.validatorEndpoint, prefix0xPubkey(publicKey), "feerecipient"), body: JSON.stringify({ ethaddress: newFeeRecipient }) }); } catch (e) { @@ -64,7 +66,7 @@ export class ValidatorApi extends StandardApi { try { await this.request({ method: "DELETE", - endpoint: path.join(this.feeRecipientEndpoint, prefix0xPubkey(publicKey), "feerecipient") + endpoint: path.join(this.validatorEndpoint, prefix0xPubkey(publicKey), "feerecipient") }); } catch (e) { e.message += `Error deleting (DELETE) fee recipient for pubkey ${publicKey} from validator. `; @@ -131,6 +133,63 @@ export class ValidatorApi extends StandardApi { } } + /** + * Requests the beacon node to provide a set of attestation duties, which should be performed by validators, for a particular epoch. + * Duties should only need to be checked once per epoch, however a chain reorganization (of > MIN_SEED_LOOKAHEAD epochs) could occur, + * resulting in a change of duties. For full safety, you should monitor head events and confirm the dependent root in this response matches: + * + * - event.previous_duty_dependent_root when compute_epoch_at_slot(event.slot) == epoch + * - event.current_duty_dependent_root when compute_epoch_at_slot(event.slot) + 1 == epoch + * - event.block otherwise + * + * The dependent_root value is get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch - 1) - 1) or the genesis block root in the case of underflow. + * + * @param validatorIndices - The indices of the validators to get attester duties for. + * @param epoch - Should only be allowed 1 epoch ahead + * @see https://ethereum.github.io/beacon-APIs/#/Validator/getAttesterDuties + * @returns the attester duties for the given validator indices. + */ + public async getAttesterDuties( + validatorIndices: string[], + epoch: string + ): Promise { + try { + return await this.request({ + method: "POST", + endpoint: path.join(this.validatorEndpoint, "duties", "attester", epoch), + body: JSON.stringify(validatorIndices) + }); + } catch (e) { + e.message += `Error getting (POST) attester duties from validator. `; + throw e; + } + } + + /** + * Request beacon node to provide all validators that are scheduled to propose a block in the given epoch. Duties should only need to be checked once per epoch, however a chain reorganization could occur that results in a change of duties. For full safety, you should monitor head events and confirm the dependent root in this response matches: + * + * event.current_duty_dependent_root when compute_epoch_at_slot(event.slot) == epoch + * event.block otherwise + * The dependent_root value is get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch) - 1) or the genesis block root in the case of underflow. + * + * @see https://ethereum.github.io/beacon-APIs/#/Validator/getProposerDuties + * @param epoch - The epoch to get proposer duties for. + * @returns the proposer duties for the given epoch. + */ + public async getProposerDuties(epoch: string): Promise { + try { + return await this.request({ + method: "GET", + endpoint: path.join(this.validatorEndpoint, "duties", "proposer", epoch) + }); + } catch (e) { + e.message += `Error getting (GET) proposer duties from validator. `; + throw e; + } + } + + // Utils + /** * Converts the status to lowercase for Web3SignerPostResponse and Web3SignerDeleteResponse */ diff --git a/packages/common/package.json b/packages/common/package.json index 9d5ccea8..01499152 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -20,6 +20,7 @@ }, "devDependencies": { "@types/sinon": "^10.0.13", - "sinon": "^15.0.1" + "sinon": "^15.0.1", + "typescript": "^5.5.4" } } diff --git a/packages/common/src/types/api/beaconchain/types.ts b/packages/common/src/types/api/beaconchain/types.ts index b4c17b33..ba54d08b 100644 --- a/packages/common/src/types/api/beaconchain/types.ts +++ b/packages/common/src/types/api/beaconchain/types.ts @@ -52,6 +52,25 @@ export interface BeaconchainForkFromStateGetResponse { }; } +export interface BeaconchainStateFinalityCheckpointsPostResponse { + execution_optimistic: boolean; + finalized: boolean; + data: { + previous_justified: { + epoch: string; + root: string; + }; + current_justified: { + epoch: string; + root: "string"; + }; + finalized: { + epoch: string; + root: "string"; + }; + }; +} + export interface BeaconchainGenesisGetResponse { data: { genesis_time: string; diff --git a/packages/common/src/types/api/validator/types.ts b/packages/common/src/types/api/validator/types.ts index 294dff5a..a9225f69 100644 --- a/packages/common/src/types/api/validator/types.ts +++ b/packages/common/src/types/api/validator/types.ts @@ -41,6 +41,30 @@ export interface ValidatorDeleteRemoteKeysResponse { }[]; } +export interface ValidatorAttesterDutiesPostResponse { + dependent_root: string; + execution_optimistic: boolean; + data: { + pubkey: string; + validator_index: string; + committee_index: string; + committee_length: string; + committees_at_slot: string; + validator_committee_index: string; + slot: string; + }[]; +} + +export interface ValidatorProposerDutiesGetResponse { + dependent_root: string; + execution_optimistic: false; + data: { + pubkey: string; + validator_index: string; + slot: string; + }[]; +} + export interface ValidatorExitGet extends BeaconchainPoolVoluntaryExitsPostRequest { pubkey: string; } diff --git a/yarn.lock b/yarn.lock index f4d00d23..6ae36327 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3055,6 +3055,7 @@ __metadata: rimraf: "npm:^4.1.1" sinon: "npm:^15.0.1" socket.io: "npm:^4.5.4" + typescript: "npm:^5.5.4" languageName: unknown linkType: soft @@ -3064,6 +3065,7 @@ __metadata: dependencies: "@types/sinon": "npm:^10.0.13" sinon: "npm:^15.0.1" + typescript: "npm:^5.5.4" languageName: unknown linkType: soft From f1d3886f30302a9215bc64147ce042e2a5e8c88f Mon Sep 17 00:00:00 2001 From: pablomendezroyo Date: Wed, 4 Sep 2024 17:39:04 +0200 Subject: [PATCH 2/2] fix typo --- packages/brain/src/calls/exitValidators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/brain/src/calls/exitValidators.ts b/packages/brain/src/calls/exitValidators.ts index e36372f2..df689e40 100644 --- a/packages/brain/src/calls/exitValidators.ts +++ b/packages/brain/src/calls/exitValidators.ts @@ -67,7 +67,7 @@ async function _getExitValidators(pubkeys: string[]): Promise