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

feat: implement getStateRandao #6072

Merged
merged 10 commits into from
Nov 4, 2023
26 changes: 26 additions & 0 deletions packages/api/src/beacon/routes/beacon/state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ export type Api = {
>
>;

getStateRandao(
scorbajio marked this conversation as resolved.
Show resolved Hide resolved
stateId: StateId,
epoch?: Epoch
): Promise<
ApiClientResponse<
{[HttpStatusCode.OK]: {data: {randao: Root}; executionOptimistic: ExecutionOptimistic}},
HttpStatusCode.BAD_REQUEST | HttpStatusCode.NOT_FOUND
>
>;

/**
* Get validator from state by id
* Returns validator specified by state and id or public key along with status and balance.
Expand Down Expand Up @@ -218,6 +228,7 @@ export const routesData: RoutesData<Api> = {
getStateRoot: {url: "/eth/v1/beacon/states/{state_id}/root", method: "GET"},
getStateValidator: {url: "/eth/v1/beacon/states/{state_id}/validators/{validator_id}", method: "GET"},
getStateValidators: {url: "/eth/v1/beacon/states/{state_id}/validators", method: "GET"},
getStateRandao: {url: "/eth/v1/beacon/states/{state_id}/randao", method: "GET"},
getStateValidatorBalances: {url: "/eth/v1/beacon/states/{state_id}/validator_balances", method: "GET"},
};

Expand All @@ -233,6 +244,7 @@ export type ReqTypes = {
getStateRoot: StateIdOnlyReq;
getStateValidator: {params: {state_id: StateId; validator_id: ValidatorId}};
getStateValidators: {params: {state_id: StateId}; query: {id?: ValidatorId[]; status?: ValidatorStatus[]}};
getStateRandao: {params: {state_id: StateId}; query: {epoch?: number}};
getStateValidatorBalances: {params: {state_id: StateId}; query: {id?: ValidatorId[]}};
};

Expand Down Expand Up @@ -283,6 +295,15 @@ export function getReqSerializers(): ReqSerializers<Api, ReqTypes> {
},
},

getStateRandao: {
writeReq: (state_id, epoch) => ({params: {state_id}, query: {epoch}}),
parseReq: ({params, query}) => [params.state_id, query.epoch],
schema: {
params: {state_id: Schema.StringRequired},
query: {epoch: Schema.Uint},
},
},

getStateValidatorBalances: {
writeReq: (state_id, id) => ({params: {state_id}, query: {id}}),
parseReq: ({params, query}) => [params.state_id, query.id],
Expand All @@ -299,6 +320,10 @@ export function getReturnTypes(): ReturnTypes<Api> {
root: ssz.Root,
});

const RandaoContainer = new ContainerType({
randao: ssz.Root,
});

const FinalityCheckpoints = new ContainerType(
{
previousJustified: ssz.phase0.Checkpoint,
Expand Down Expand Up @@ -349,6 +374,7 @@ export function getReturnTypes(): ReturnTypes<Api> {
getStateFinalityCheckpoints: ContainerDataExecutionOptimistic(FinalityCheckpoints),
getStateValidators: ContainerDataExecutionOptimistic(ArrayOf(ValidatorResponse)),
getStateValidator: ContainerDataExecutionOptimistic(ValidatorResponse),
getStateRandao: ContainerDataExecutionOptimistic(RandaoContainer),
getStateValidatorBalances: ContainerDataExecutionOptimistic(ArrayOf(ValidatorBalance)),
getEpochCommittees: ContainerDataExecutionOptimistic(ArrayOf(EpochCommitteeResponse)),
getEpochSyncCommittees: ContainerDataExecutionOptimistic(EpochSyncCommitteesResponse),
Expand Down
40 changes: 40 additions & 0 deletions packages/beacon-node/src/api/impl/beacon/state/index.ts
nflaig marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
computeEpochAtSlot,
computeStartSlotAtEpoch,
getCurrentEpoch,
getRandaoMix,
} from "@lodestar/state-transition";
import {ApiError} from "../../errors.js";
import {ApiModules} from "../../types.js";
Expand All @@ -16,6 +17,9 @@ import {
toValidatorResponse,
} from "./utils.js";

import {EPOCHS_PER_HISTORICAL_VECTOR} from "@lodestar/params";
import { bytesToHex } from "../../../../eth1/provider/utils.js";

export function getBeaconStateApi({
chain,
config,
Expand Down Expand Up @@ -127,6 +131,42 @@ export function getBeaconStateApi({
};
},

/**
* Retrieves the sync committees for the given state.
* @param epoch Fetch sync committees for the given epoch. If not present then the sync committees for the epoch of the state will be obtained.
*/
scorbajio marked this conversation as resolved.
Show resolved Hide resolved
async getStateRandao(stateId, epoch) {
const {state, executionOptimistic} = await resolveStateId(chain, stateId);
const stateEpoch = computeEpochAtSlot(state.slot);
const usedEpoch = epoch ?? stateEpoch;

// TODO is there a way to calculate this by at a specific epoch?
const epochsPerHistoricalVector = EPOCHS_PER_HISTORICAL_VECTOR;
scorbajio marked this conversation as resolved.
Show resolved Hide resolved

const ret = {
executionOptimistic,
// TODO how to compute finalized?
scorbajio marked this conversation as resolved.
Show resolved Hide resolved
// finalized: state.finalized,
data: {
randao: "", // return empty value to denote out-of-bound lookup
},
nflaig marked this conversation as resolved.
Show resolved Hide resolved
};

if (usedEpoch > stateEpoch) {
return ret;
} else if (
usedEpoch < stateEpoch &&
Math.abs(stateEpoch - epochsPerHistoricalVector) > 0 &&
Math.abs(stateEpoch - epochsPerHistoricalVector) >= usedEpoch
) {
return ret;
}
Copy link
Member

@nflaig nflaig Nov 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure about this part, I think we should follow similar implementation as Lighthouse, seems simple and straight forward, see PR diff

As per spec randao.yaml:

  • 400 for invalid state id or epoch, mostly handled by json schema already (needs to be double checked)
  • 400 if epoch is out of range, see spec note
  • 404 if state not found

You can also check with other routes that load a state + have epoch as optional param for additional examples

Copy link
Contributor Author

@scorbajio scorbajio Nov 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was using teku's implementation as a reference. After taking a look at lighthouse, it seems like they do range checking in a more simple way in a separate get-index helper, which is what I used to rework the range-checking logic.


ret.data.randao = bytesToHex(getRandaoMix(state, usedEpoch));
scorbajio marked this conversation as resolved.
Show resolved Hide resolved

return ret;
},

async getStateValidatorBalances(stateId, indices) {
const {state, executionOptimistic} = await resolveStateId(chain, stateId);

Expand Down
Loading