mirror of
https://github.com/ChainSafe/lodestar.git
synced 2026-01-10 08:08:16 -05:00
feat: add proposer duties v2 endpoint (#8597)
Adds proposer duties v2 endpoint which works the same as v1 but uses previous epoch to determine dependent root to account for deterministic proposer lookahead changes in fulu. https://github.com/ethereum/beacon-APIs/pull/563
This commit is contained in:
@@ -285,6 +285,33 @@ export type Endpoints = {
|
||||
ExecutionOptimisticAndDependentRootMeta
|
||||
>;
|
||||
|
||||
/**
|
||||
* Get block proposers duties
|
||||
* 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. After Fulu, different checks
|
||||
* need to be performed as the dependent root changes due to deterministic proposer lookahead.
|
||||
*
|
||||
* Before Fulu:
|
||||
* - event.current_duty_dependent_root when `compute_epoch_at_slot(event.slot) == epoch`
|
||||
* - event.block otherwise
|
||||
* - dependent_root value is `get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch) - 1)`
|
||||
*
|
||||
* After Fulu:
|
||||
* - event.previous_duty_dependent_root when `compute_epoch_at_slot(event.slot) == epoch`
|
||||
* - event.block otherwise
|
||||
* - dependent_root value is `get_block_root_at_slot(state, compute_start_slot_at_epoch(epoch - 1) - 1)`
|
||||
*
|
||||
* The dependent_root value is the genesis block root in the case of underflow."
|
||||
*/
|
||||
getProposerDutiesV2: Endpoint<
|
||||
"GET",
|
||||
{epoch: Epoch},
|
||||
{params: {epoch: Epoch}},
|
||||
ProposerDutyList,
|
||||
ExecutionOptimisticAndDependentRootMeta
|
||||
>;
|
||||
|
||||
getSyncCommitteeDuties: Endpoint<
|
||||
"POST",
|
||||
{
|
||||
@@ -565,6 +592,21 @@ export function getDefinitions(config: ChainForkConfig): RouteDefinitions<Endpoi
|
||||
meta: ExecutionOptimisticAndDependentRootCodec,
|
||||
},
|
||||
},
|
||||
getProposerDutiesV2: {
|
||||
url: "/eth/v2/validator/duties/proposer/{epoch}",
|
||||
method: "GET",
|
||||
req: {
|
||||
writeReq: ({epoch}) => ({params: {epoch}}),
|
||||
parseReq: ({params}) => ({epoch: params.epoch}),
|
||||
schema: {
|
||||
params: {epoch: Schema.UintRequired},
|
||||
},
|
||||
},
|
||||
resp: {
|
||||
data: ProposerDutyListType,
|
||||
meta: ExecutionOptimisticAndDependentRootCodec,
|
||||
},
|
||||
},
|
||||
getSyncCommitteeDuties: {
|
||||
url: "/eth/v1/validator/duties/sync/{epoch}",
|
||||
method: "POST",
|
||||
|
||||
@@ -35,6 +35,13 @@ export const testData: GenericServerTestCases<Endpoints> = {
|
||||
meta: {executionOptimistic: true, dependentRoot: ZERO_HASH_HEX},
|
||||
},
|
||||
},
|
||||
getProposerDutiesV2: {
|
||||
args: {epoch: 1000},
|
||||
res: {
|
||||
data: [{slot: 1, validatorIndex: 2, pubkey: new Uint8Array(48).fill(3)}],
|
||||
meta: {executionOptimistic: true, dependentRoot: ZERO_HASH_HEX},
|
||||
},
|
||||
},
|
||||
getSyncCommitteeDuties: {
|
||||
args: {epoch: 1000, indices: [1, 2, 3]},
|
||||
res: {
|
||||
|
||||
@@ -1002,7 +1002,7 @@ export function getValidatorApi(
|
||||
return {data: contribution};
|
||||
},
|
||||
|
||||
async getProposerDuties({epoch}) {
|
||||
async getProposerDuties({epoch}, _context, opts?: {v2?: boolean}) {
|
||||
notWhileSyncing();
|
||||
|
||||
// Early check that epoch is no more than current_epoch + 1, or allow for pre-genesis
|
||||
@@ -1106,7 +1106,10 @@ export function getValidatorApi(
|
||||
|
||||
// Returns `null` on the one-off scenario where the genesis block decides its own shuffling.
|
||||
// It should be set to the latest block applied to `self` or the genesis block root.
|
||||
const dependentRoot = proposerShufflingDecisionRoot(state) || (await getGenesisBlockRoot(state));
|
||||
const dependentRoot =
|
||||
// In v2 the dependent root is different after fulu due to deterministic proposer lookahead
|
||||
proposerShufflingDecisionRoot(opts?.v2 ? config.getForkName(startSlot) : ForkName.phase0, state) ||
|
||||
(await getGenesisBlockRoot(state));
|
||||
|
||||
return {
|
||||
data: duties,
|
||||
@@ -1117,6 +1120,10 @@ export function getValidatorApi(
|
||||
};
|
||||
},
|
||||
|
||||
async getProposerDutiesV2(args, context) {
|
||||
return this.getProposerDuties(args, context, {v2: true});
|
||||
},
|
||||
|
||||
async getAttesterDuties({epoch, indices}) {
|
||||
notWhileSyncing();
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {ForkName, isForkPostFulu} from "@lodestar/params";
|
||||
import {Epoch, Root, Slot} from "@lodestar/types";
|
||||
import {CachedBeaconStateAllForks} from "../types.js";
|
||||
import {getBlockRootAtSlot} from "./blockRoot.js";
|
||||
@@ -10,8 +11,8 @@ import {computeStartSlotAtEpoch} from "./epoch.js";
|
||||
* Returns `null` on the one-off scenario where the genesis block decides its own shuffling.
|
||||
* It should be set to the latest block applied to this `state` or the genesis block root.
|
||||
*/
|
||||
export function proposerShufflingDecisionRoot(state: CachedBeaconStateAllForks): Root | null {
|
||||
const decisionSlot = proposerShufflingDecisionSlot(state);
|
||||
export function proposerShufflingDecisionRoot(fork: ForkName, state: CachedBeaconStateAllForks): Root | null {
|
||||
const decisionSlot = proposerShufflingDecisionSlot(fork, state);
|
||||
if (state.slot === decisionSlot) {
|
||||
return null;
|
||||
}
|
||||
@@ -22,8 +23,10 @@ export function proposerShufflingDecisionRoot(state: CachedBeaconStateAllForks):
|
||||
* Returns the slot at which the proposer shuffling was decided. The block root at this slot
|
||||
* can be used to key the proposer shuffling for the current epoch.
|
||||
*/
|
||||
function proposerShufflingDecisionSlot(state: CachedBeaconStateAllForks): Slot {
|
||||
const startSlot = computeStartSlotAtEpoch(state.epochCtx.epoch);
|
||||
function proposerShufflingDecisionSlot(fork: ForkName, state: CachedBeaconStateAllForks): Slot {
|
||||
// After fulu, the decision slot is in previous epoch due to deterministic proposer lookahead
|
||||
const epoch = isForkPostFulu(fork) ? state.epochCtx.epoch - 1 : state.epochCtx.epoch;
|
||||
const startSlot = computeStartSlotAtEpoch(epoch);
|
||||
return Math.max(startSlot - 1, 0);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user