@lodestar/fork-choice 1.41.0-dev.20f622cc52 → 1.41.0-dev.241ad7952a
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/errors.js +6 -3
- package/lib/forkChoice/errors.js.map +1 -1
- package/lib/forkChoice/forkChoice.d.ts +12 -11
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +29 -22
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +12 -11
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/forkChoice/interface.js +6 -3
- package/lib/forkChoice/interface.js.map +1 -1
- package/lib/forkChoice/safeBlocks.js.map +1 -1
- package/lib/forkChoice/store.d.ts +10 -10
- package/lib/forkChoice/store.d.ts.map +1 -1
- package/lib/forkChoice/store.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/metrics.d.ts.map +1 -1
- package/lib/metrics.js.map +1 -1
- package/lib/protoArray/computeDeltas.js.map +1 -1
- package/lib/protoArray/errors.js +4 -2
- package/lib/protoArray/errors.js.map +1 -1
- package/lib/protoArray/interface.js +4 -2
- package/lib/protoArray/interface.js.map +1 -1
- package/lib/protoArray/protoArray.d.ts +17 -11
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +49 -56
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +9 -9
- package/src/forkChoice/forkChoice.ts +47 -32
- package/src/forkChoice/interface.ts +19 -10
- package/src/forkChoice/store.ts +11 -11
- package/src/index.ts +1 -1
- package/src/protoArray/protoArray.ts +61 -62
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
import {BitArray} from "@chainsafe/ssz";
|
|
1
2
|
import {GENESIS_EPOCH, PTC_SIZE} from "@lodestar/params";
|
|
2
3
|
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
3
4
|
import {Epoch, RootHex, Slot} from "@lodestar/types";
|
|
4
|
-
import {toRootHex} from "@lodestar/utils";
|
|
5
|
+
import {bitCount, toRootHex} from "@lodestar/utils";
|
|
5
6
|
import {ForkChoiceError, ForkChoiceErrorCode} from "../forkChoice/errors.js";
|
|
6
7
|
import {LVHExecError, LVHExecErrorCode, ProtoArrayError, ProtoArrayErrorCode} from "./errors.js";
|
|
7
8
|
import {
|
|
@@ -60,14 +61,14 @@ export class ProtoArray {
|
|
|
60
61
|
private previousProposerBoost: ProposerBoost | null = null;
|
|
61
62
|
|
|
62
63
|
/**
|
|
63
|
-
* PTC (Payload Timeliness Committee) votes per block
|
|
64
|
-
* Maps block root to
|
|
64
|
+
* PTC (Payload Timeliness Committee) votes per block as bitvectors
|
|
65
|
+
* Maps block root to BitArray of PTC_SIZE bits (512 mainnet, 2 minimal)
|
|
65
66
|
* Spec: gloas/fork-choice.md#modified-store (line 148)
|
|
66
67
|
*
|
|
67
|
-
*
|
|
68
|
+
* Bit i is set if PTC member i voted payload_present=true
|
|
68
69
|
* Used by is_payload_timely() to determine if payload is timely
|
|
69
70
|
*/
|
|
70
|
-
private ptcVotes = new Map<RootHex,
|
|
71
|
+
private ptcVotes = new Map<RootHex, BitArray>();
|
|
71
72
|
|
|
72
73
|
constructor({
|
|
73
74
|
pruneThreshold,
|
|
@@ -168,6 +169,26 @@ export class ProtoArray {
|
|
|
168
169
|
return PayloadStatus.PENDING;
|
|
169
170
|
}
|
|
170
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Get the node index for the default/canonical variant in a single hash lookup.
|
|
174
|
+
* - Pre-Gloas blocks: returns the FULL variant index
|
|
175
|
+
* - Gloas blocks: returns the PENDING variant index
|
|
176
|
+
*/
|
|
177
|
+
getDefaultNodeIndex(blockRoot: RootHex): number | undefined {
|
|
178
|
+
const variantOrArr = this.indices.get(blockRoot);
|
|
179
|
+
if (variantOrArr == null) {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Pre-Gloas: value is the index directly
|
|
184
|
+
if (!Array.isArray(variantOrArr)) {
|
|
185
|
+
return variantOrArr;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Gloas: PENDING is the canonical variant
|
|
189
|
+
return variantOrArr[PayloadStatus.PENDING];
|
|
190
|
+
}
|
|
191
|
+
|
|
171
192
|
/**
|
|
172
193
|
* Determine which parent payload status a block extends
|
|
173
194
|
* Spec: gloas/fork-choice.md#new-get_parent_payload_status
|
|
@@ -370,6 +391,7 @@ export class ProtoArray {
|
|
|
370
391
|
// We _must_ perform these functions separate from the weight-updating loop above to ensure
|
|
371
392
|
// that we have a fully coherent set of weights before updating parent
|
|
372
393
|
// best-child/descendant.
|
|
394
|
+
const proposerBoostRoot = proposerBoost?.root ?? null;
|
|
373
395
|
for (let nodeIndex = this.nodes.length - 1; nodeIndex >= 0; nodeIndex--) {
|
|
374
396
|
const node = this.nodes[nodeIndex];
|
|
375
397
|
if (node === undefined) {
|
|
@@ -382,7 +404,7 @@ export class ProtoArray {
|
|
|
382
404
|
// If the node has a parent, try to update its best-child and best-descendant.
|
|
383
405
|
const parentIndex = node.parent;
|
|
384
406
|
if (parentIndex !== undefined) {
|
|
385
|
-
this.maybeUpdateBestChildAndDescendant(parentIndex, nodeIndex, currentSlot,
|
|
407
|
+
this.maybeUpdateBestChildAndDescendant(parentIndex, nodeIndex, currentSlot, proposerBoostRoot);
|
|
386
408
|
}
|
|
387
409
|
}
|
|
388
410
|
// Update the previous proposer boost
|
|
@@ -476,7 +498,7 @@ export class ProtoArray {
|
|
|
476
498
|
|
|
477
499
|
// Initialize PTC votes for this block (all false initially)
|
|
478
500
|
// Spec: gloas/fork-choice.md#modified-on_block (line 645)
|
|
479
|
-
this.ptcVotes.set(block.blockRoot,
|
|
501
|
+
this.ptcVotes.set(block.blockRoot, BitArray.fromBitLen(PTC_SIZE));
|
|
480
502
|
} else {
|
|
481
503
|
// Pre-Gloas: Only create FULL node (payload embedded in block)
|
|
482
504
|
const node: ProtoNode = {
|
|
@@ -605,8 +627,7 @@ export class ProtoArray {
|
|
|
605
627
|
throw new Error(`Invalid PTC index: ${ptcIndex}, must be 0..${PTC_SIZE - 1}`);
|
|
606
628
|
}
|
|
607
629
|
|
|
608
|
-
|
|
609
|
-
votes[ptcIndex] = payloadPresent;
|
|
630
|
+
votes.set(ptcIndex, payloadPresent);
|
|
610
631
|
}
|
|
611
632
|
}
|
|
612
633
|
|
|
@@ -636,7 +657,7 @@ export class ProtoArray {
|
|
|
636
657
|
}
|
|
637
658
|
|
|
638
659
|
// Count votes for payload_present=true
|
|
639
|
-
const yesVotes = votes.
|
|
660
|
+
const yesVotes = bitCount(votes.uint8Array);
|
|
640
661
|
return yesVotes > PAYLOAD_TIMELY_THRESHOLD;
|
|
641
662
|
}
|
|
642
663
|
|
|
@@ -676,8 +697,8 @@ export class ProtoArray {
|
|
|
676
697
|
|
|
677
698
|
// Get proposer boost block
|
|
678
699
|
// We don't care about variant here, just need proposer boost block info
|
|
679
|
-
const
|
|
680
|
-
const proposerBoostBlock =
|
|
700
|
+
const proposerBoostIndex = this.getDefaultNodeIndex(proposerBoostRoot);
|
|
701
|
+
const proposerBoostBlock = proposerBoostIndex !== undefined ? this.getNodeByIndex(proposerBoostIndex) : undefined;
|
|
681
702
|
if (!proposerBoostBlock) {
|
|
682
703
|
// Proposer boost block not found, default to extending payload
|
|
683
704
|
return true;
|
|
@@ -745,12 +766,8 @@ export class ProtoArray {
|
|
|
745
766
|
// if its in fcU.
|
|
746
767
|
//
|
|
747
768
|
const {invalidateFromParentBlockRoot, latestValidExecHash} = execResponse;
|
|
748
|
-
// TODO GLOAS: verify if getting default
|
|
749
|
-
const
|
|
750
|
-
const invalidateFromParentIndex =
|
|
751
|
-
defaultStatus !== undefined
|
|
752
|
-
? this.getNodeIndexByRootAndStatus(invalidateFromParentBlockRoot, defaultStatus)
|
|
753
|
-
: undefined;
|
|
769
|
+
// TODO GLOAS: verify if getting the default/canonical node index is correct here
|
|
770
|
+
const invalidateFromParentIndex = this.getDefaultNodeIndex(invalidateFromParentBlockRoot);
|
|
754
771
|
if (invalidateFromParentIndex === undefined) {
|
|
755
772
|
throw Error(`Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} in forkChoice`);
|
|
756
773
|
}
|
|
@@ -963,9 +980,7 @@ export class ProtoArray {
|
|
|
963
980
|
}
|
|
964
981
|
|
|
965
982
|
// Get canonical node: FULL for pre-Gloas, PENDING for Gloas
|
|
966
|
-
const
|
|
967
|
-
const justifiedIndex =
|
|
968
|
-
defaultStatus !== undefined ? this.getNodeIndexByRootAndStatus(justifiedRoot, defaultStatus) : undefined;
|
|
983
|
+
const justifiedIndex = this.getDefaultNodeIndex(justifiedRoot);
|
|
969
984
|
if (justifiedIndex === undefined) {
|
|
970
985
|
throw new ProtoArrayError({
|
|
971
986
|
code: ProtoArrayErrorCode.JUSTIFIED_NODE_UNKNOWN,
|
|
@@ -1477,11 +1492,8 @@ export class ProtoArray {
|
|
|
1477
1492
|
* For Gloas blocks: returns EMPTY/FULL variants (not PENDING) based on parent payload status
|
|
1478
1493
|
* For pre-Gloas blocks: returns FULL variants
|
|
1479
1494
|
*/
|
|
1480
|
-
*iterateAncestorNodes(blockRoot: RootHex): IterableIterator<ProtoNode> {
|
|
1481
|
-
|
|
1482
|
-
const defaultStatus = this.getDefaultVariant(blockRoot);
|
|
1483
|
-
const startIndex =
|
|
1484
|
-
defaultStatus !== undefined ? this.getNodeIndexByRootAndStatus(blockRoot, defaultStatus) : undefined;
|
|
1495
|
+
*iterateAncestorNodes(blockRoot: RootHex, payloadStatus: PayloadStatus): IterableIterator<ProtoNode> {
|
|
1496
|
+
const startIndex = this.getNodeIndexByRootAndStatus(blockRoot, payloadStatus);
|
|
1485
1497
|
if (startIndex === undefined) {
|
|
1486
1498
|
return;
|
|
1487
1499
|
}
|
|
@@ -1520,11 +1532,8 @@ export class ProtoArray {
|
|
|
1520
1532
|
* For Gloas blocks: returns EMPTY/FULL variants (not PENDING) based on parent payload status
|
|
1521
1533
|
* For pre-Gloas blocks: returns FULL variants
|
|
1522
1534
|
*/
|
|
1523
|
-
getAllAncestorNodes(blockRoot: RootHex): ProtoNode[] {
|
|
1524
|
-
|
|
1525
|
-
const defaultStatus = this.getDefaultVariant(blockRoot);
|
|
1526
|
-
const startIndex =
|
|
1527
|
-
defaultStatus !== undefined ? this.getNodeIndexByRootAndStatus(blockRoot, defaultStatus) : undefined;
|
|
1535
|
+
getAllAncestorNodes(blockRoot: RootHex, payloadStatus: PayloadStatus): ProtoNode[] {
|
|
1536
|
+
const startIndex = this.getNodeIndexByRootAndStatus(blockRoot, payloadStatus);
|
|
1528
1537
|
if (startIndex === undefined) {
|
|
1529
1538
|
return [];
|
|
1530
1539
|
}
|
|
@@ -1537,12 +1546,10 @@ export class ProtoArray {
|
|
|
1537
1546
|
});
|
|
1538
1547
|
}
|
|
1539
1548
|
|
|
1540
|
-
//
|
|
1541
|
-
// Reason why we exclude post-gloas is because node is always default variant (PENDING)
|
|
1542
|
-
// which we want to exclude.
|
|
1549
|
+
// Exclude PENDING variant from returned ancestors.
|
|
1543
1550
|
const nodes: ProtoNode[] = [];
|
|
1544
1551
|
|
|
1545
|
-
if (
|
|
1552
|
+
if (node.payloadStatus !== PayloadStatus.PENDING) {
|
|
1546
1553
|
nodes.push(node);
|
|
1547
1554
|
}
|
|
1548
1555
|
|
|
@@ -1567,13 +1574,8 @@ export class ProtoArray {
|
|
|
1567
1574
|
* For Gloas blocks: returns EMPTY/FULL variants (not PENDING) based on parent payload status
|
|
1568
1575
|
* For pre-Gloas blocks: returns FULL variants
|
|
1569
1576
|
*/
|
|
1570
|
-
getAllNonAncestorNodes(blockRoot: RootHex): ProtoNode[] {
|
|
1571
|
-
|
|
1572
|
-
const defaultStatus = this.getDefaultVariant(blockRoot);
|
|
1573
|
-
if (defaultStatus === undefined) {
|
|
1574
|
-
return [];
|
|
1575
|
-
}
|
|
1576
|
-
const startIndex = this.getNodeIndexByRootAndStatus(blockRoot, defaultStatus);
|
|
1577
|
+
getAllNonAncestorNodes(blockRoot: RootHex, payloadStatus: PayloadStatus): ProtoNode[] {
|
|
1578
|
+
const startIndex = this.getNodeIndexByRootAndStatus(blockRoot, payloadStatus);
|
|
1577
1579
|
if (startIndex === undefined) {
|
|
1578
1580
|
return [];
|
|
1579
1581
|
}
|
|
@@ -1613,11 +1615,11 @@ export class ProtoArray {
|
|
|
1613
1615
|
* For Gloas blocks: returns EMPTY/FULL variants (not PENDING) based on parent payload status
|
|
1614
1616
|
* For pre-Gloas blocks: returns FULL variants
|
|
1615
1617
|
*/
|
|
1616
|
-
getAllAncestorAndNonAncestorNodes(
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1618
|
+
getAllAncestorAndNonAncestorNodes(
|
|
1619
|
+
blockRoot: RootHex,
|
|
1620
|
+
payloadStatus: PayloadStatus
|
|
1621
|
+
): {ancestors: ProtoNode[]; nonAncestors: ProtoNode[]} {
|
|
1622
|
+
const startIndex = this.getNodeIndexByRootAndStatus(blockRoot, payloadStatus);
|
|
1621
1623
|
if (startIndex === undefined) {
|
|
1622
1624
|
return {ancestors: [], nonAncestors: []};
|
|
1623
1625
|
}
|
|
@@ -1667,12 +1669,7 @@ export class ProtoArray {
|
|
|
1667
1669
|
* Uses default variant (PENDING for Gloas, FULL for pre-Gloas)
|
|
1668
1670
|
*/
|
|
1669
1671
|
hasBlock(blockRoot: RootHex): boolean {
|
|
1670
|
-
|
|
1671
|
-
if (defaultVariant === undefined) {
|
|
1672
|
-
return false;
|
|
1673
|
-
}
|
|
1674
|
-
const index = this.getNodeIndexByRootAndStatus(blockRoot, defaultVariant);
|
|
1675
|
-
return index !== undefined;
|
|
1672
|
+
return this.getDefaultNodeIndex(blockRoot) !== undefined;
|
|
1676
1673
|
}
|
|
1677
1674
|
|
|
1678
1675
|
/**
|
|
@@ -1735,26 +1732,28 @@ export class ProtoArray {
|
|
|
1735
1732
|
/**
|
|
1736
1733
|
* Returns `true` if the `descendantRoot` has an ancestor with `ancestorRoot`.
|
|
1737
1734
|
* Always returns `false` if either input roots are unknown.
|
|
1738
|
-
* Still returns `true` if `ancestorRoot` === `descendantRoot`
|
|
1735
|
+
* Still returns `true` if `ancestorRoot` === `descendantRoot` and payload statuses match.
|
|
1739
1736
|
*/
|
|
1740
|
-
isDescendant(
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1737
|
+
isDescendant(
|
|
1738
|
+
ancestorRoot: RootHex,
|
|
1739
|
+
ancestorPayloadStatus: PayloadStatus,
|
|
1740
|
+
descendantRoot: RootHex,
|
|
1741
|
+
descendantPayloadStatus: PayloadStatus
|
|
1742
|
+
): boolean {
|
|
1743
|
+
const ancestorNode = this.getNode(ancestorRoot, ancestorPayloadStatus);
|
|
1745
1744
|
if (!ancestorNode) {
|
|
1746
1745
|
return false;
|
|
1747
1746
|
}
|
|
1748
1747
|
|
|
1749
|
-
if (ancestorRoot === descendantRoot) {
|
|
1748
|
+
if (ancestorRoot === descendantRoot && ancestorPayloadStatus === descendantPayloadStatus) {
|
|
1750
1749
|
return true;
|
|
1751
1750
|
}
|
|
1752
1751
|
|
|
1753
|
-
for (const node of this.iterateAncestorNodes(descendantRoot)) {
|
|
1752
|
+
for (const node of this.iterateAncestorNodes(descendantRoot, descendantPayloadStatus)) {
|
|
1754
1753
|
if (node.slot < ancestorNode.slot) {
|
|
1755
1754
|
return false;
|
|
1756
1755
|
}
|
|
1757
|
-
if (node.blockRoot === ancestorNode.blockRoot) {
|
|
1756
|
+
if (node.blockRoot === ancestorNode.blockRoot && node.payloadStatus === ancestorNode.payloadStatus) {
|
|
1758
1757
|
return true;
|
|
1759
1758
|
}
|
|
1760
1759
|
}
|