Skip to content
Open
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
33 changes: 10 additions & 23 deletions packages/beacon-node/src/api/impl/beacon/state/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@ import {routes} from "@lodestar/api";
import {CheckpointWithHex, IForkChoice} from "@lodestar/fork-choice";
import {GENESIS_SLOT} from "@lodestar/params";
import {BeaconStateAllForks, CachedBeaconStateAllForks} from "@lodestar/state-transition";
import {BLSPubkey, Epoch, RootHex, Slot, ValidatorIndex, getValidatorStatus, phase0} from "@lodestar/types";
import {
BLSPubkey,
Epoch,
RootHex,
Slot,
ValidatorIndex,
getValidatorStatus,
mapToGeneralStatus,
phase0,
} from "@lodestar/types";
import {fromHex} from "@lodestar/utils";
import {IBeaconChain} from "../../../../chain/index.js";
import {ApiError, ValidationError} from "../../errors.js";
Expand Down Expand Up @@ -65,28 +74,6 @@ export async function getStateResponseWithRegen(
return res;
}

type GeneralValidatorStatus = "active" | "pending" | "exited" | "withdrawal";

function mapToGeneralStatus(subStatus: routes.beacon.ValidatorStatus): GeneralValidatorStatus {
switch (subStatus) {
case "active_ongoing":
case "active_exiting":
case "active_slashed":
return "active";
case "pending_initialized":
case "pending_queued":
return "pending";
case "exited_slashed":
case "exited_unslashed":
return "exited";
case "withdrawal_possible":
case "withdrawal_done":
return "withdrawal";
default:
throw new Error(`Unknown substatus: ${subStatus}`);
}
}

export function toValidatorResponse(
index: ValidatorIndex,
validator: phase0.Validator,
Expand Down
2 changes: 1 addition & 1 deletion packages/state-transition/src/cache/stateCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export function createCachedBeaconState<T extends BeaconStateAllForks>(
* Check loadState() api for more details
* // TODO: rename to loadUnfinalizedCachedBeaconState() due to ELECTRA
*/
export function loadCachedBeaconState<T extends BeaconStateAllForks & BeaconStateCache>(
export function loadCachedBeaconState<T extends CachedBeaconStateAllForks>(
cachedSeedState: T,
stateBytes: Uint8Array,
opts?: EpochCacheOpts,
Expand Down
2 changes: 2 additions & 0 deletions packages/state-transition/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ export {
isStateValidatorsNodesPopulated,
loadCachedBeaconState,
} from "./cache/stateCache.js";
export {type SyncCommitteeCache} from "./cache/syncCommitteeCache.js";
export * from "./constants/index.js";
export type {EpochTransitionStep} from "./epoch/index.js";
export {type BeaconStateTransitionMetrics, getMetrics} from "./metrics.js";
export * from "./rewards/index.js";
export * from "./signatureSets/index.js";
export * from "./stateTransition.js";
export * from "./stateView/index.js";
export type {
BeaconStateAllForks,
BeaconStateAltair,
Expand Down
83 changes: 83 additions & 0 deletions packages/state-transition/src/lightClient/proofs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {Tree} from "@chainsafe/persistent-merkle-tree";
import {
BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX,
FINALIZED_ROOT_GINDEX,
FINALIZED_ROOT_GINDEX_ELECTRA,
ForkName,
ForkPostBellatrix,
isForkPostElectra,
} from "@lodestar/params";
import {BeaconBlockBody, SSZTypesFor, ssz} from "@lodestar/types";
import {BeaconStateAllForks, CachedBeaconStateAllForks} from "../types.js";
import {SyncCommitteeWitness} from "./types.js";

export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllForks): SyncCommitteeWitness {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this was moved from

export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllForks): SyncCommitteeWitness {

although it does not look like it belongs to state-transition, it couples with BeaconStateAllForks and this is the only place we can leave it

const n1 = state.node;
let witness: Uint8Array[];
let currentSyncCommitteeRoot: Uint8Array;
let nextSyncCommitteeRoot: Uint8Array;

if (isForkPostElectra(fork)) {
const n2 = n1.left;
const n5 = n2.right;
const n10 = n5.left;
const n21 = n10.right;
const n43 = n21.right;
Comment on lines +21 to +25
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The direct access to n1.left, n2.right, etc., in getSyncCommitteesWitness relies heavily on the internal structure of the Tree and node properties. This makes the code brittle to future changes in the SSZ tree structure. If the SSZ structure is modified, these direct accesses could break silently or produce incorrect proofs, which is a critical correctness issue for light client proofs. It would be more robust to use explicit GINDEXes or a more abstract way to navigate the tree if available, or at least add comments explaining the gindex for each node access to make it easier to maintain.


currentSyncCommitteeRoot = n43.left.root; // n86
nextSyncCommitteeRoot = n43.right.root; // n87

// Witness branch is sorted by descending gindex
witness = [
n21.left.root, // 42
n10.left.root, // 20
n5.right.root, // 11
n2.left.root, // 4
n1.right.root, // 3
];
} else {
const n3 = n1.right; // [1]0110
const n6 = n3.left; // 1[0]110
const n13 = n6.right; // 10[1]10
const n27 = n13.right; // 101[1]0
currentSyncCommitteeRoot = n27.left.root; // n54 1011[0]
nextSyncCommitteeRoot = n27.right.root; // n55 1011[1]

// Witness branch is sorted by descending gindex
witness = [
n13.left.root, // 26
n6.left.root, // 12
n3.right.root, // 7
n1.left.root, // 2
];
}

return {
witness,
currentSyncCommitteeRoot,
nextSyncCommitteeRoot,
};
}

export function getNextSyncCommitteeBranch(syncCommitteesWitness: SyncCommitteeWitness): Uint8Array[] {
// Witness branch is sorted by descending gindex
return [syncCommitteesWitness.currentSyncCommitteeRoot, ...syncCommitteesWitness.witness];
}

export function getCurrentSyncCommitteeBranch(syncCommitteesWitness: SyncCommitteeWitness): Uint8Array[] {
// Witness branch is sorted by descending gindex
return [syncCommitteesWitness.nextSyncCommitteeRoot, ...syncCommitteesWitness.witness];
}

export function getFinalizedRootProof(state: CachedBeaconStateAllForks): Uint8Array[] {
const finalizedRootGindex = state.epochCtx.isPostElectra() ? FINALIZED_ROOT_GINDEX_ELECTRA : FINALIZED_ROOT_GINDEX;
return new Tree(state.node).getSingleProof(BigInt(finalizedRootGindex));
}

export function getBlockBodyExecutionHeaderProof(
fork: ForkPostBellatrix,
body: BeaconBlockBody<ForkPostBellatrix>
): Uint8Array[] {
const bodyView = (ssz[fork].BeaconBlockBody as SSZTypesFor<ForkPostBellatrix, "BeaconBlockBody">).toView(body);
return new Tree(bodyView.node).getSingleProof(BigInt(BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX));
}
33 changes: 33 additions & 0 deletions packages/state-transition/src/lightClient/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* We aren't creating the sync committee proofs separately because our ssz library automatically adds leaves to composite types,
* so they're already included in the state proof, currently with no way to specify otherwise
*
* remove two offsets so the # of offsets in the state proof will be the # expected
* This is a hack, but properly setting the offsets in the state proof would require either removing witnesses needed for the committees
* or setting the roots of the committees in the state proof
* this will always be 1, syncProofLeavesLength
*
*
* With empty state (minimal)
* - `genesisTime = 0xffffffff`
* - `genesisValidatorsRoot = Buffer.alloc(32, 1)`
*
* Proof:
* ```
* offsets: [ 5, 4, 3, 2, 1 ]
* leaves: [
* '0xffffffff00000000000000000000000000000000000000000000000000000000',
* '0x0101010101010101010101010101010101010101010101010101010101010101',
* '0xb11b8bcf59425d6c99019cca1d2c2e47b51a2f74917a67ad132274f43e13ec43',
* '0x74bd1f2437cdf74b0904ee525d8da070a3fa27570942bf42cbab3dc5939600f0',
* '0x7f06739e5a42360c56e519a511675901c95402ea9877edc0d9a87471b1374a6a',
* '0x9f534204ba3c0b69fcb42a11987bfcbc5aea0463e5b0614312ded4b62cf3a380'
* ]
* ```
*/
export type SyncCommitteeWitness = {
/** Vector[Bytes32, 4] or Vector[Bytes32, 5] depending on the fork */
witness: Uint8Array[];
currentSyncCommitteeRoot: Uint8Array;
nextSyncCommitteeRoot: Uint8Array;
};
Loading
Loading