Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add missing endpoints to validator and beaconchain API clients required to calculate validator performance #322

Merged
merged 2 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/brain/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
}
2 changes: 1 addition & 1 deletion packages/brain/src/calls/exitValidators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ async function _getExitValidators(pubkeys: string[]): Promise<ValidatorExitGet[]
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" });
const fork = await beaconchainApi.getForkFromState({ stateId: "head" });

// Get the genesis from the beaconchain API to sign the voluntary exit
const genesis = await beaconchainApi.getGenesis();
Expand Down
2 changes: 1 addition & 1 deletion packages/brain/src/modules/apiClients/beaconcha.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<BeaconchaGetResponse[]> {
Expand Down
32 changes: 29 additions & 3 deletions packages/brain/src/modules/apiClients/beaconchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
BeaconchainPoolVoluntaryExitsPostRequest,
BeaconchainForkFromStateGetResponse,
BeaconchainGenesisGetResponse,
BeaconchainStateFinalityCheckpointsPostResponse,
Network,
ApiParams
} from "@stakingbrain/common";
Expand Down Expand Up @@ -60,20 +61,45 @@ 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", <slot>, <hex encoded stateRoot with 0x prefix>.
* @param stateId - State identifier. Can be one of: "head" (canonical head in node's view), "genesis", "finalized", <slot>, <hex encoded stateRoot with 0x prefix>.
*/
public async getForkFromState({ state_id }: { state_id: string }): Promise<BeaconchainForkFromStateGetResponse> {
public async getForkFromState({
stateId
}: {
stateId: "head" | "genesis" | "finalized";
}): Promise<BeaconchainForkFromStateGetResponse> {
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. `;
throw e;
}
}

/**
* 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", <slot>, <hex encoded stateRoot with 0x prefix>.
*/
public async getStateFinalityCheckpoints({
stateId
}: {
stateId: "head" | "genesis" | "finalized";
}): Promise<BeaconchainStateFinalityCheckpointsPostResponse> {
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
Expand Down
71 changes: 65 additions & 6 deletions packages/brain/src/modules/apiClients/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
ValidatorGetRemoteKeysResponse,
ValidatorPostRemoteKeysRequest,
ValidatorPostRemoteKeysResponse,
ValidatorProposerDutiesGetResponse,
ValidatorAttesterDutiesPostResponse,
prefix0xPubkey
} from "@stakingbrain/common";
import { StandardApi } from "./standard.js";
Expand All @@ -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.
Expand All @@ -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. `;
Expand All @@ -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) {
Expand All @@ -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. `;
Expand Down Expand Up @@ -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<ValidatorAttesterDutiesPostResponse> {
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<ValidatorProposerDutiesGetResponse> {
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
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
},
"devDependencies": {
"@types/sinon": "^10.0.13",
"sinon": "^15.0.1"
"sinon": "^15.0.1",
"typescript": "^5.5.4"
}
}
19 changes: 19 additions & 0 deletions packages/common/src/types/api/beaconchain/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
24 changes: 24 additions & 0 deletions packages/common/src/types/api/validator/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down
2 changes: 2 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down