@lodestar/fork-choice 1.43.0 → 1.44.0-dev.055b83cb3d
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/forkChoice/forkChoice.d.ts +6 -4
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +40 -30
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +7 -8
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/forkChoice/interface.js.map +1 -1
- package/lib/protoArray/interface.d.ts +1 -0
- package/lib/protoArray/interface.d.ts.map +1 -1
- package/lib/protoArray/protoArray.d.ts +44 -19
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +107 -30
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +7 -7
- package/src/forkChoice/forkChoice.ts +49 -31
- package/src/forkChoice/interface.ts +22 -7
- package/src/protoArray/interface.ts +8 -0
- package/src/protoArray/protoArray.ts +109 -33
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import {DataAvailabilityStatus, EffectiveBalanceIncrements, IBeaconStateView} from "@lodestar/state-transition";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
AttesterSlashing,
|
|
4
|
+
BeaconBlock,
|
|
5
|
+
Epoch,
|
|
6
|
+
IndexedAttestation,
|
|
7
|
+
Root,
|
|
8
|
+
RootHex,
|
|
9
|
+
Slot,
|
|
10
|
+
ValidatorIndex,
|
|
11
|
+
} from "@lodestar/types";
|
|
3
12
|
import {
|
|
4
13
|
BlockExecutionStatus,
|
|
5
14
|
LVHExecResponse,
|
|
@@ -145,7 +154,8 @@ export interface IForkChoice {
|
|
|
145
154
|
blockDelaySec: number,
|
|
146
155
|
currentSlot: Slot,
|
|
147
156
|
executionStatus: BlockExecutionStatus,
|
|
148
|
-
dataAvailabilityStatus: DataAvailabilityStatus
|
|
157
|
+
dataAvailabilityStatus: DataAvailabilityStatus,
|
|
158
|
+
expectedProposerIndex: ValidatorIndex | null
|
|
149
159
|
): ProtoBlock;
|
|
150
160
|
/**
|
|
151
161
|
* Register `attestation` with the fork choice DAG so that it may influence future calls to `getHead`.
|
|
@@ -181,12 +191,13 @@ export interface IForkChoice {
|
|
|
181
191
|
* ## Specification
|
|
182
192
|
*
|
|
183
193
|
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.0/specs/gloas/fork-choice.md#new-notify_ptc_messages
|
|
184
|
-
*
|
|
185
|
-
* @param blockRoot - The beacon block root being attested
|
|
186
|
-
* @param ptcIndices - Array of PTC committee indices that voted
|
|
187
|
-
* @param payloadPresent - Whether validators attest the payload is present
|
|
188
194
|
*/
|
|
189
|
-
notifyPtcMessages(
|
|
195
|
+
notifyPtcMessages(
|
|
196
|
+
blockRoot: RootHex,
|
|
197
|
+
ptcIndices: number[],
|
|
198
|
+
payloadPresent: boolean,
|
|
199
|
+
blobDataAvailable: boolean
|
|
200
|
+
): void;
|
|
190
201
|
/**
|
|
191
202
|
* Notify fork choice that an execution payload has arrived (Gloas fork)
|
|
192
203
|
* Creates the FULL variant of a Gloas block when the payload becomes available
|
|
@@ -198,11 +209,13 @@ export interface IForkChoice {
|
|
|
198
209
|
* @param blockRoot - The beacon block root for which the payload arrived
|
|
199
210
|
* @param executionPayloadBlockHash - The block hash of the execution payload
|
|
200
211
|
* @param executionPayloadNumber - The block number of the execution payload
|
|
212
|
+
* @param executionPayloadGasLimit - The gas limit of the execution payload
|
|
201
213
|
*/
|
|
202
214
|
onExecutionPayload(
|
|
203
215
|
blockRoot: RootHex,
|
|
204
216
|
executionPayloadBlockHash: RootHex,
|
|
205
217
|
executionPayloadNumber: number,
|
|
218
|
+
executionPayloadGasLimit: number,
|
|
206
219
|
executionStatus: PayloadExecutionStatus,
|
|
207
220
|
dataAvailabilityStatus: DataAvailabilityStatus
|
|
208
221
|
): void;
|
|
@@ -242,6 +255,8 @@ export interface IForkChoice {
|
|
|
242
255
|
getBlockHexDefaultStatus(blockRoot: RootHex): ProtoBlock | null;
|
|
243
256
|
getBlockHexAndBlockHash(blockRoot: RootHex, blockHash: RootHex): ProtoBlock | null;
|
|
244
257
|
shouldExtendPayload(blockRoot: RootHex): boolean;
|
|
258
|
+
/** Spec: should_build_on_full(store, head) */
|
|
259
|
+
shouldBuildOnFull(head: ProtoBlock): boolean;
|
|
245
260
|
getFinalizedBlock(): ProtoBlock;
|
|
246
261
|
getJustifiedBlock(): ProtoBlock;
|
|
247
262
|
getFinalizedCheckpointSlot(): Slot;
|
|
@@ -87,6 +87,14 @@ export type BlockExtraMeta =
|
|
|
87
87
|
// - payload block hash for FULL variant
|
|
88
88
|
executionPayloadBlockHash: RootHex;
|
|
89
89
|
executionPayloadNumber: UintNum64;
|
|
90
|
+
// Gas limit of the executed payload identified by executionPayloadBlockHash. Set on
|
|
91
|
+
// pre-Gloas blocks (from block.body.executionPayload.gasLimit) and on Gloas variants:
|
|
92
|
+
// - PENDING/EMPTY: inherited from the parent payload that the bid commits to extend
|
|
93
|
+
// (matches executionPayloadBlockHash, which also points to that parent payload)
|
|
94
|
+
// - FULL: the actual delivered payload's gasLimit (set in onExecutionPayload)
|
|
95
|
+
// Consumers (e.g. Gloas bid gas-limit validation) can read this without re-deriving from
|
|
96
|
+
// state.
|
|
97
|
+
executionPayloadGasLimit: UintNum64;
|
|
90
98
|
executionStatus: Exclude<ExecutionStatus, ExecutionStatus.PreMerge>;
|
|
91
99
|
dataAvailabilityStatus: DataAvailabilityStatus;
|
|
92
100
|
}
|
|
@@ -21,6 +21,29 @@ import {
|
|
|
21
21
|
* Spec: gloas/fork-choice.md (PAYLOAD_TIMELY_THRESHOLD = PTC_SIZE // 2)
|
|
22
22
|
*/
|
|
23
23
|
const PAYLOAD_TIMELY_THRESHOLD = Math.floor(PTC_SIZE / 2);
|
|
24
|
+
/**
|
|
25
|
+
* Threshold for blob data availability via PTC vote
|
|
26
|
+
* Spec: gloas/fork-choice.md (DATA_AVAILABILITY_TIMELY_THRESHOLD = PTC_SIZE // 2)
|
|
27
|
+
*/
|
|
28
|
+
const DATA_AVAILABILITY_TIMELY_THRESHOLD = Math.floor(PTC_SIZE / 2);
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* popcount(attended AND NOT yes) — explicit False-vote count.
|
|
32
|
+
* Excludes PTC members who didn't attest (the None state).
|
|
33
|
+
*/
|
|
34
|
+
export function countNoVotes(attended: BitArray, yes: BitArray): number {
|
|
35
|
+
const a = attended.uint8Array;
|
|
36
|
+
const y = yes.uint8Array;
|
|
37
|
+
let count = 0;
|
|
38
|
+
for (let i = 0; i < a.length; i++) {
|
|
39
|
+
let byte = a[i] & ~y[i] & 0xff;
|
|
40
|
+
while (byte) {
|
|
41
|
+
byte &= byte - 1;
|
|
42
|
+
count++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return count;
|
|
46
|
+
}
|
|
24
47
|
|
|
25
48
|
export const DEFAULT_PRUNE_THRESHOLD = 0;
|
|
26
49
|
type ProposerBoost = {root: RootHex; score: number};
|
|
@@ -64,12 +87,25 @@ export class ProtoArray {
|
|
|
64
87
|
/**
|
|
65
88
|
* PTC (Payload Timeliness Committee) votes per block as bitvectors
|
|
66
89
|
* Maps block root to BitArray of PTC_SIZE bits (512 mainnet, 2 minimal)
|
|
67
|
-
* Spec: gloas/fork-choice.md#modified-store (
|
|
90
|
+
* Spec: gloas/fork-choice.md#modified-store (payload_timeliness_vote)
|
|
68
91
|
*
|
|
69
|
-
* Bit i
|
|
70
|
-
* Used by is_payload_timely() to determine if payload is timely
|
|
92
|
+
* Bit i = PTC member i voted payloadPresent=true (timeliness YES vote)
|
|
71
93
|
*/
|
|
72
94
|
private ptcVotes = new Map<RootHex, BitArray>();
|
|
95
|
+
/**
|
|
96
|
+
* Blob data availability votes per block.
|
|
97
|
+
* Spec: gloas/fork-choice.md#modified-store (payload_data_availability_vote)
|
|
98
|
+
*
|
|
99
|
+
* Bit i = PTC member i voted blobDataAvailable=true (DA YES vote)
|
|
100
|
+
*/
|
|
101
|
+
private daVotes = new Map<RootHex, BitArray>();
|
|
102
|
+
/**
|
|
103
|
+
* Tracks which PTC members have attested at all (any payload_status).
|
|
104
|
+
* Without this, we cannot tell "didn't vote" (None) from "voted false" —
|
|
105
|
+
* a distinction required by payload_timeliness/payload_data_availability
|
|
106
|
+
* when called with the negative parameter value.
|
|
107
|
+
*/
|
|
108
|
+
private ptcAttested = new Map<RootHex, BitArray>();
|
|
73
109
|
|
|
74
110
|
constructor({
|
|
75
111
|
pruneThreshold,
|
|
@@ -514,9 +550,11 @@ export class ProtoArray {
|
|
|
514
550
|
// Update bestChild for PENDING → EMPTY edge
|
|
515
551
|
this.maybeUpdateBestChildAndDescendant(pendingIndex, emptyIndex, currentSlot, proposerBoostRoot);
|
|
516
552
|
|
|
517
|
-
// Initialize PTC
|
|
518
|
-
// Spec: gloas/fork-choice.md#modified-on_block
|
|
553
|
+
// Initialize PTC vote bitvectors for this block.
|
|
554
|
+
// Spec: gloas/fork-choice.md#modified-on_block
|
|
519
555
|
this.ptcVotes.set(block.blockRoot, BitArray.fromBitLen(PTC_SIZE));
|
|
556
|
+
this.ptcAttested.set(block.blockRoot, BitArray.fromBitLen(PTC_SIZE));
|
|
557
|
+
this.daVotes.set(block.blockRoot, BitArray.fromBitLen(PTC_SIZE));
|
|
520
558
|
} else {
|
|
521
559
|
// Pre-Gloas: Only create FULL node (payload embedded in block)
|
|
522
560
|
const node: ProtoNode = {
|
|
@@ -558,6 +596,7 @@ export class ProtoArray {
|
|
|
558
596
|
currentSlot: Slot,
|
|
559
597
|
executionPayloadBlockHash: RootHex,
|
|
560
598
|
executionPayloadNumber: number,
|
|
599
|
+
executionPayloadGasLimit: number,
|
|
561
600
|
proposerBoostRoot: RootHex | null,
|
|
562
601
|
executionStatus: PayloadExecutionStatus,
|
|
563
602
|
dataAvailabilityStatus: DataAvailabilityStatus
|
|
@@ -613,6 +652,7 @@ export class ProtoArray {
|
|
|
613
652
|
executionStatus,
|
|
614
653
|
executionPayloadBlockHash,
|
|
615
654
|
executionPayloadNumber,
|
|
655
|
+
executionPayloadGasLimit,
|
|
616
656
|
dataAvailabilityStatus,
|
|
617
657
|
};
|
|
618
658
|
|
|
@@ -634,15 +674,18 @@ export class ProtoArray {
|
|
|
634
674
|
|
|
635
675
|
/**
|
|
636
676
|
* Update PTC votes for multiple validators attesting to a block
|
|
637
|
-
* Spec: gloas/fork-choice.md#new-
|
|
638
|
-
*
|
|
639
|
-
* @param blockRoot - The beacon block root being attested
|
|
640
|
-
* @param ptcIndices - Array of PTC committee indices that voted (0..PTC_SIZE-1)
|
|
641
|
-
* @param payloadPresent - Whether the validators attest the payload is present
|
|
677
|
+
* Spec: gloas/fork-choice.md#new-notify_ptc_messages
|
|
642
678
|
*/
|
|
643
|
-
notifyPtcMessages(
|
|
679
|
+
notifyPtcMessages(
|
|
680
|
+
blockRoot: RootHex,
|
|
681
|
+
ptcIndices: number[],
|
|
682
|
+
payloadPresent: boolean,
|
|
683
|
+
blobDataAvailable: boolean
|
|
684
|
+
): void {
|
|
644
685
|
const votes = this.ptcVotes.get(blockRoot);
|
|
645
|
-
|
|
686
|
+
const attended = this.ptcAttested.get(blockRoot);
|
|
687
|
+
const daVotes = this.daVotes.get(blockRoot);
|
|
688
|
+
if (votes === undefined || attended === undefined || daVotes === undefined) {
|
|
646
689
|
// Block not found or not a Gloas block, ignore
|
|
647
690
|
return;
|
|
648
691
|
}
|
|
@@ -651,8 +694,9 @@ export class ProtoArray {
|
|
|
651
694
|
if (ptcIndex < 0 || ptcIndex >= PTC_SIZE) {
|
|
652
695
|
throw new Error(`Invalid PTC index: ${ptcIndex}, must be 0..${PTC_SIZE - 1}`);
|
|
653
696
|
}
|
|
654
|
-
|
|
655
697
|
votes.set(ptcIndex, payloadPresent);
|
|
698
|
+
daVotes.set(ptcIndex, blobDataAvailable);
|
|
699
|
+
attended.set(ptcIndex, true);
|
|
656
700
|
}
|
|
657
701
|
}
|
|
658
702
|
|
|
@@ -667,31 +711,61 @@ export class ProtoArray {
|
|
|
667
711
|
}
|
|
668
712
|
|
|
669
713
|
/**
|
|
670
|
-
*
|
|
671
|
-
* Spec: gloas/fork-choice.md#new-is_payload_timely
|
|
672
|
-
*
|
|
673
|
-
* Returns true if:
|
|
674
|
-
* 1. Block has PTC votes tracked
|
|
675
|
-
* 2. Payload is locally available (FULL variant exists in proto array)
|
|
676
|
-
* 3. More than PAYLOAD_TIMELY_THRESHOLD (>50% of PTC) members voted payload_present=true
|
|
677
|
-
*
|
|
678
|
-
* @param blockRoot - The beacon block root to check
|
|
714
|
+
* Spec: payload_timeliness(store, root, timely=True)
|
|
679
715
|
*/
|
|
680
716
|
isPayloadTimely(blockRoot: RootHex): boolean {
|
|
681
717
|
const votes = this.ptcVotes.get(blockRoot);
|
|
682
|
-
if (votes === undefined)
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
718
|
+
if (votes === undefined) return false;
|
|
719
|
+
if (!this.hasPayload(blockRoot)) return false;
|
|
720
|
+
return bitCount(votes.uint8Array) > PAYLOAD_TIMELY_THRESHOLD;
|
|
721
|
+
}
|
|
686
722
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
723
|
+
/**
|
|
724
|
+
* Spec: payload_timeliness(store, root, timely=False)
|
|
725
|
+
*/
|
|
726
|
+
isPayloadNotTimely(blockRoot: RootHex): boolean {
|
|
727
|
+
const votes = this.ptcVotes.get(blockRoot);
|
|
728
|
+
const attended = this.ptcAttested.get(blockRoot);
|
|
729
|
+
if (votes === undefined || attended === undefined) return false;
|
|
730
|
+
// Spec: not verified locally → returns `not False = True`
|
|
731
|
+
if (!this.hasPayload(blockRoot)) return true;
|
|
732
|
+
return countNoVotes(attended, votes) > PAYLOAD_TIMELY_THRESHOLD;
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Spec: payload_data_availability(store, root, available=True)
|
|
737
|
+
*/
|
|
738
|
+
isPayloadDataAvailable(blockRoot: RootHex): boolean {
|
|
739
|
+
const daVotes = this.daVotes.get(blockRoot);
|
|
740
|
+
if (daVotes === undefined) return false;
|
|
741
|
+
if (!this.hasPayload(blockRoot)) return false;
|
|
742
|
+
return bitCount(daVotes.uint8Array) > DATA_AVAILABILITY_TIMELY_THRESHOLD;
|
|
743
|
+
}
|
|
691
744
|
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
745
|
+
/**
|
|
746
|
+
* Spec: payload_data_availability(store, root, available=False)
|
|
747
|
+
*/
|
|
748
|
+
isPayloadDataNotAvailable(blockRoot: RootHex): boolean {
|
|
749
|
+
const daVotes = this.daVotes.get(blockRoot);
|
|
750
|
+
const attended = this.ptcAttested.get(blockRoot);
|
|
751
|
+
if (daVotes === undefined || attended === undefined) return false;
|
|
752
|
+
// Spec: not verified locally → returns `not False = True`
|
|
753
|
+
if (!this.hasPayload(blockRoot)) return true;
|
|
754
|
+
return countNoVotes(attended, daVotes) > DATA_AVAILABILITY_TIMELY_THRESHOLD;
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
/**
|
|
758
|
+
* Spec: should_build_on_full(store, head)
|
|
759
|
+
*
|
|
760
|
+
* The proposer is forced to build on the EMPTY variant (effectively reorging)
|
|
761
|
+
* when the PTC majority voted that the blob data is not available.
|
|
762
|
+
*/
|
|
763
|
+
shouldBuildOnFull(head: ProtoBlock): boolean {
|
|
764
|
+
if (head.payloadStatus === PayloadStatus.PENDING) {
|
|
765
|
+
throw new Error("shouldBuildOnFull called with PENDING head");
|
|
766
|
+
}
|
|
767
|
+
if (head.payloadStatus === PayloadStatus.EMPTY) return false;
|
|
768
|
+
return !this.isPayloadDataNotAvailable(head.blockRoot);
|
|
695
769
|
}
|
|
696
770
|
|
|
697
771
|
/**
|
|
@@ -1132,6 +1206,8 @@ export class ProtoArray {
|
|
|
1132
1206
|
// Prune PTC votes for this block to prevent memory leak
|
|
1133
1207
|
// Spec: gloas/fork-choice.md (implicit - finalized blocks don't need PTC votes)
|
|
1134
1208
|
this.ptcVotes.delete(root);
|
|
1209
|
+
this.ptcAttested.delete(root);
|
|
1210
|
+
this.daVotes.delete(root);
|
|
1135
1211
|
}
|
|
1136
1212
|
|
|
1137
1213
|
// Store nodes prior to finalization
|