Skip to content
Closed
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/beacon-node/src/api/impl/beacon/pool/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,8 @@ export function getBeaconPoolApi({
chain.forkChoice.notifyPtcMessages(
toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
[validatorCommitteeIndex],
payloadAttestationMessage.data.payloadPresent
payloadAttestationMessage.data.payloadPresent,
payloadAttestationMessage.data.blobDataAvailable
);

await network.publishPayloadAttestationMessage(payloadAttestationMessage);
Expand Down
3 changes: 2 additions & 1 deletion packages/beacon-node/src/chain/blocks/importBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,8 @@ export async function importBlock(
this.forkChoice.notifyPtcMessages(
toRootHex(payloadAttestation.data.beaconBlockRoot),
ptcIndices,
payloadAttestation.data.payloadPresent
payloadAttestation.data.payloadPresent,
payloadAttestation.data.blobDataAvailable
);
}
} catch (e) {
Expand Down
11 changes: 11 additions & 0 deletions packages/beacon-node/src/chain/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ export enum ChainEvent {
* This event is guaranteed to be triggered whenever the fork choice justified checkpoint is updated. This is in response to a newly processed block.
*/
forkChoiceFinalized = "forkChoice:finalized",
/**
* This event signals that the PTC quorum for payload timeliness has been reached.
*/
forkChoicePTCQuorumPayloadTimely = "forkChoice:PTCQuorumPayloadTimely",
/**
* This event signals that the PTC quorum for data availability has been reached.
*/
forkChoicePTCQuorumDataAvailable = "forkChoice:PTCQuorumDataAvailable",
/**
* This event signals that dependent services (e.g. custody sampling) should update to account for the new target group count.
*/
Expand Down Expand Up @@ -113,6 +121,9 @@ export type IChainEvents = ApiEvents & {
[ChainEvent.forkChoiceJustified]: (checkpoint: CheckpointWithHex) => void;
[ChainEvent.forkChoiceFinalized]: (checkpoint: CheckpointWithHex) => void;

[ChainEvent.forkChoicePTCQuorumPayloadTimely]: (blockRoot: RootHex, payloadTimely: boolean) => void;
[ChainEvent.forkChoicePTCQuorumDataAvailable]: (blockRoot: RootHex, dataAvailable: boolean) => void;

[ChainEvent.updateTargetCustodyGroupCount]: (targetGroupCount: number) => void;

[ChainEvent.publishDataColumns]: (sidecars: DataColumnSidecar[]) => void;
Expand Down
8 changes: 8 additions & 0 deletions packages/beacon-node/src/chain/forkChoice/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ export function initializeForkChoiceFromFinalizedState(
{
onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
onPTCQuorumPayloadTimely: (blockRoot, payloadTimely) =>
emitter.emit(ChainEvent.forkChoicePTCQuorumPayloadTimely, blockRoot, payloadTimely),
onPTCQuorumDataAvailable: (blockRoot, dataAvailable) =>
emitter.emit(ChainEvent.forkChoicePTCQuorumDataAvailable, blockRoot, dataAvailable),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

do we really need two separate events, is there ever a reason (at least right now) to consume these separately?

}
),

Expand Down Expand Up @@ -204,6 +208,10 @@ export function initializeForkChoiceFromUnfinalizedState(
{
onJustified: (cp) => emitter.emit(ChainEvent.forkChoiceJustified, cp),
onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
onPTCQuorumPayloadTimely: (blockRoot, payloadTimely) =>
emitter.emit(ChainEvent.forkChoicePTCQuorumPayloadTimely, blockRoot, payloadTimely),
onPTCQuorumDataAvailable: (blockRoot, dataAvailable) =>
emitter.emit(ChainEvent.forkChoicePTCQuorumDataAvailable, blockRoot, dataAvailable),
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1149,7 +1149,8 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
chain.forkChoice.notifyPtcMessages(
toRootHex(payloadAttestationMessage.data.beaconBlockRoot),
[validationResult.validatorCommitteeIndex],
payloadAttestationMessage.data.payloadPresent
payloadAttestationMessage.data.payloadPresent,
payloadAttestationMessage.data.blobDataAvailable
);
},
[GossipType.execution_payload_bid]: async ({
Expand Down
7 changes: 5 additions & 2 deletions packages/beacon-node/test/e2e/sync/finalizedSync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,9 +159,12 @@ describe("sync / finalized sync for gloas", () => {
`Node B missing FULL payload variant for gloas block slot=${block.slot} root=${block.blockRoot}`
);
if (block.slot > gloasFirstSlot) {
const ptcVotes = bn2.chain.forkChoice.getPTCVotes(block.blockRoot) ?? [];
const ptcVotes = bn2.chain.forkChoice.getPTCVotes(block.blockRoot);
if (ptcVotes === null) {
expect.fail("Block not found or not a Gloas block");
}

expect(ptcVotes.some(Boolean)).toBeWithMessage(
expect(ptcVotes.payloadTimelyYea + ptcVotes.payloadTimelyNay > 0).toBeWithMessage(
true,
`Node A missing PTC votes for gloas block slot=${block.slot} root=${block.blockRoot}`
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,8 @@ describe.skip(`getAttestationsForBlock vc=${vc}`, () => {
},
justifiedBalancesGetter: () => originalState.epochCtx.effectiveBalanceIncrements,
equivocatingIndices: new Set(),
setPtcQuorumPayloadTimely: () => {},
setPtcQuorumDataAvailable: () => {},
};
forkchoice = new ForkChoice(originalState.config, fcStore, protoArray, originalState.validators.length, null);
},
Expand Down
51 changes: 44 additions & 7 deletions packages/fork-choice/src/forkChoice/forkChoice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ import {
HEX_ZERO_HASH,
LVHExecResponse,
NULL_VOTE_INDEX,
PTCVotes,
PayloadExecutionStatus,
PayloadStatus,
ProtoBlock,
ProtoNode,
VoteIndex,
isGloasBlock,
} from "../protoArray/interface.js";
import {ProtoArray} from "../protoArray/protoArray.js";
import {DATA_AVAILABILITY_TIMELY_THRESHOLD, PAYLOAD_TIMELY_THRESHOLD, ProtoArray} from "../protoArray/protoArray.js";
import {ForkChoiceError, ForkChoiceErrorCode, InvalidAttestationCode, InvalidBlockCode} from "./errors.js";
import {
AncestorResult,
Expand Down Expand Up @@ -944,8 +945,46 @@ export class ForkChoice implements IForkChoice {
* Updates the PTC votes for multiple validators attesting to a block
* Spec: gloas/fork-choice.md#new-on_payload_attestation_message
*/
notifyPtcMessages(blockRoot: RootHex, ptcIndices: number[], payloadPresent: boolean): void {
this.protoArray.notifyPtcMessages(blockRoot, ptcIndices, payloadPresent);
notifyPtcMessages(blockRoot: RootHex, ptcIndices: number[], payloadPresent: boolean, dataAvailable: boolean): void {
const votes = this.protoArray.getPTCVotes(blockRoot);
if (votes === null) {
return;
}
const {payloadTimelyYea, payloadTimelyNay, dataAvailableYea, dataAvailableNay} = votes;

const newVotes = this.protoArray.notifyPtcMessages(blockRoot, ptcIndices, payloadPresent, dataAvailable);
if (newVotes === null) {
return;
}

const {
payloadTimelyYea: newPayloadTimelyYea,
payloadTimelyNay: newPayloadTimelyNay,
dataAvailableYea: newDataAvailableYea,
dataAvailableNay: newDataAvailableNay,
} = newVotes;

if (payloadTimelyYea <= PAYLOAD_TIMELY_THRESHOLD && newPayloadTimelyYea > PAYLOAD_TIMELY_THRESHOLD) {
this.fcStore.setPtcQuorumPayloadTimely(blockRoot, true);
}

if (payloadTimelyNay <= PAYLOAD_TIMELY_THRESHOLD && newPayloadTimelyNay > PAYLOAD_TIMELY_THRESHOLD) {
this.fcStore.setPtcQuorumPayloadTimely(blockRoot, false);
}

if (
dataAvailableYea <= DATA_AVAILABILITY_TIMELY_THRESHOLD &&
newDataAvailableYea > DATA_AVAILABILITY_TIMELY_THRESHOLD
) {
this.fcStore.setPtcQuorumDataAvailable(blockRoot, true);
}

if (
dataAvailableNay <= DATA_AVAILABILITY_TIMELY_THRESHOLD &&
newDataAvailableNay > DATA_AVAILABILITY_TIMELY_THRESHOLD
) {
this.fcStore.setPtcQuorumDataAvailable(blockRoot, false);
}
}

/**
Expand Down Expand Up @@ -1053,10 +1092,8 @@ export class ForkChoice implements IForkChoice {
return this.protoArray.hasPayload(blockRoot);
}

getPTCVotes(blockRootHex: RootHex): (boolean | null)[] | null {
const votes = this.protoArray.getPTCVotes(blockRootHex);
if (votes === null) return null;
return votes.toBoolArray().map((v) => v ?? null);
getPTCVotes(blockRootHex: RootHex): PTCVotes | null {
return this.protoArray.getPTCVotes(blockRootHex);
}

/**
Expand Down
6 changes: 4 additions & 2 deletions packages/fork-choice/src/forkChoice/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {AttesterSlashing, BeaconBlock, Epoch, IndexedAttestation, Root, RootHex,
import {
BlockExecutionStatus,
LVHExecResponse,
PTCVotes,
PayloadExecutionStatus,
PayloadStatus,
ProtoBlock,
Expand Down Expand Up @@ -185,8 +186,9 @@ export interface IForkChoice {
* @param blockRoot - The beacon block root being attested
* @param ptcIndices - Array of PTC committee indices that voted
* @param payloadPresent - Whether validators attest the payload is present
* @param dataAvailable - Whether validators attest the data is available
*/
notifyPtcMessages(blockRoot: RootHex, ptcIndices: number[], payloadPresent: boolean): void;
notifyPtcMessages(blockRoot: RootHex, ptcIndices: number[], payloadPresent: boolean, dataAvailable: boolean): void;
/**
* Notify fork choice that an execution payload has arrived (Gloas fork)
* Creates the FULL variant of a Gloas block when the payload becomes available
Expand Down Expand Up @@ -232,7 +234,7 @@ export interface IForkChoice {
hasPayloadUnsafe(blockRoot: Root): boolean;
hasPayloadHexUnsafe(blockRoot: RootHex): boolean;
getSlotsPresent(windowStart: number): number;
getPTCVotes(blockRootHex: RootHex): (boolean | null)[] | null;
getPTCVotes(blockRootHex: RootHex): PTCVotes | null;
/**
* Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root.
*/
Expand Down
12 changes: 12 additions & 0 deletions packages/fork-choice/src/forkChoice/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ export interface IForkChoiceStore {
unrealizedFinalizedCheckpoint: CheckpointWithHex;
justifiedBalancesGetter: JustifiedBalancesGetter;
equivocatingIndices: Set<ValidatorIndex>;
setPtcQuorumPayloadTimely(blockRoot: RootHex, payloadTimely: boolean): void;
setPtcQuorumDataAvailable(blockRoot: RootHex, dataAvailable: boolean): void;
}

/**
Expand All @@ -67,6 +69,8 @@ export class ForkChoiceStore implements IForkChoiceStore {
private readonly events?: {
onJustified: (cp: CheckpointWithHex) => void;
onFinalized: (cp: CheckpointWithHex) => void;
onPTCQuorumPayloadTimely: (blockRoot: RootHex, payloadTimely: boolean) => void;
onPTCQuorumDataAvailable: (blockRoot: RootHex, dataAvailable: boolean) => void;
}
) {
this.justifiedBalancesGetter = justifiedBalancesGetter;
Expand Down Expand Up @@ -98,6 +102,14 @@ export class ForkChoiceStore implements IForkChoiceStore {
this._finalizedCheckpoint = cp;
this.events?.onFinalized(cp);
}

setPtcQuorumPayloadTimely(blockRoot: RootHex, payloadTimely: boolean) {
this.events?.onPTCQuorumPayloadTimely(blockRoot, payloadTimely);
}

setPtcQuorumDataAvailable(blockRoot: RootHex, dataAvailable: boolean) {
this.events?.onPTCQuorumDataAvailable(blockRoot, dataAvailable);
}
}

export function toCheckpointWithHex(checkpoint: phase0.Checkpoint): CheckpointWithHex {
Expand Down
1 change: 1 addition & 0 deletions packages/fork-choice/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export type {
BlockExtraMeta,
LVHInvalidResponse,
LVHValidResponse,
PTCVotes,
PayloadExecutionStatus,
ProtoBlock,
ProtoNode,
Expand Down
17 changes: 17 additions & 0 deletions packages/fork-choice/src/protoArray/interface.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {BitArray} from "@chainsafe/ssz";
import {DataAvailabilityStatus} from "@lodestar/state-transition";
import {Epoch, RootHex, Slot, UintNum64} from "@lodestar/types";

Expand Down Expand Up @@ -155,3 +156,19 @@ export type ProtoNode = ProtoBlock & {
bestChild?: number;
bestDescendant?: number;
};

/**
* type to track PTC votes
*
* true means quorum of yea,
* false means quorum of nay,
* null means not enough votes
*/
export type PTCQuorum = boolean | null;
export type PTCVotes = {
votes: BitArray;
payloadTimelyYea: number;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Not a fan of the yea/nay notation.

Maybe payloadTimelyTrue/payloadTimelyFalse or payloadTimelyCount/payloadUntimelyCount or payloadPresentCount/payloadAbsentCount?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Also looks like this votes compare to the old BitArray is only to track if a validator has voted yet. We lose the info about whether a validator has voted for timely (and data available) or not because we aggregate them into numbers.
Not sure if we need this info though

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

yes this is a tradeoff to minimize memory usage here. if we need attribution we can revisit, but i don't think it matters considering there's no carrot or stick incentives here.

payloadTimelyNay: number;
dataAvailableYea: number;
dataAvailableNay: number;
};
Loading
Loading