@lodestar/fork-choice 1.47.0-dev.da8dfeabda → 1.47.0-dev.e73f1f5165
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 +31 -0
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +126 -27
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/protoArray/interface.d.ts +3 -1
- package/lib/protoArray/interface.d.ts.map +1 -1
- package/lib/protoArray/protoArray.d.ts +11 -1
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +25 -0
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +7 -7
- package/src/forkChoice/forkChoice.ts +147 -31
- package/src/protoArray/interface.ts +8 -1
- package/src/protoArray/protoArray.ts +27 -1
|
@@ -591,32 +591,48 @@ export class ForkChoice implements IForkChoice {
|
|
|
591
591
|
computeDeltasMetrics?.newVoteValidators.set(newVoteValidators);
|
|
592
592
|
|
|
593
593
|
this.balances = newBalances;
|
|
594
|
-
/**
|
|
595
|
-
* The structure in line with deltas to propagate boost up the branch
|
|
596
|
-
* starting from the proposerIndex
|
|
597
|
-
*/
|
|
598
|
-
let proposerBoost: {root: RootHex; score: bigint} | null = null;
|
|
599
|
-
if (this.opts?.proposerBoost && this.proposerBoostRoot) {
|
|
600
|
-
const proposerBoostScore =
|
|
601
|
-
this.justifiedProposerBoostScore ??
|
|
602
|
-
getCommitteeFraction(this.fcStore.justified.totalBalance, {
|
|
603
|
-
slotsPerEpoch: SLOTS_PER_EPOCH,
|
|
604
|
-
committeePercent: this.config.PROPOSER_SCORE_BOOST,
|
|
605
|
-
});
|
|
606
|
-
proposerBoost = {root: this.proposerBoostRoot, score: proposerBoostScore};
|
|
607
|
-
this.justifiedProposerBoostScore = proposerBoostScore;
|
|
608
|
-
}
|
|
609
594
|
|
|
610
595
|
const currentSlot = this.fcStore.currentSlot;
|
|
611
|
-
|
|
612
|
-
attestationDeltas,
|
|
613
|
-
proposerBoost,
|
|
596
|
+
const checkpoints = {
|
|
614
597
|
justifiedEpoch: this.fcStore.justified.checkpoint.epoch,
|
|
615
598
|
justifiedRoot: this.fcStore.justified.checkpoint.rootHex,
|
|
616
599
|
finalizedEpoch: this.fcStore.finalizedCheckpoint.epoch,
|
|
617
600
|
finalizedRoot: this.fcStore.finalizedCheckpoint.rootHex,
|
|
618
601
|
currentSlot,
|
|
619
|
-
}
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
const boostedBlock =
|
|
605
|
+
this.opts?.proposerBoost && this.proposerBoostRoot ? this.getBlockHexDefaultStatus(this.proposerBoostRoot) : null;
|
|
606
|
+
|
|
607
|
+
if (boostedBlock && isGloasBlock(boostedBlock)) {
|
|
608
|
+
// should_apply_proposer_boost judges the parent against the attestations known to the store,
|
|
609
|
+
// via is_head_weak (which also assumes equivocators' votes were already discounted before
|
|
610
|
+
// their balance is added back). Apply the attestation deltas first so the decision reads
|
|
611
|
+
// post-delta scores, then apply the boost in a second pass.
|
|
612
|
+
// TODO GLOAS: applyScoreChanges() updates weights and best child/descendant in one call;
|
|
613
|
+
// splitting them would let the two passes update weights and recompute best child/descendant
|
|
614
|
+
// once at the end.
|
|
615
|
+
// https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.14/specs/gloas/fork-choice.md#new-should_apply_proposer_boost
|
|
616
|
+
this.protoArray.applyScoreChanges({attestationDeltas, proposerBoost: null, ...checkpoints});
|
|
617
|
+
const proposerBoost = this.shouldApplyProposerBoost() ? this.getProposerBoost() : null;
|
|
618
|
+
// The first pass already rolled back the previous boost and left a coherent tree, so a
|
|
619
|
+
// withheld boost needs no second pass
|
|
620
|
+
if (proposerBoost !== null) {
|
|
621
|
+
this.protoArray.applyScoreChanges({
|
|
622
|
+
attestationDeltas: new Array<number>(this.protoArray.nodes.length).fill(0),
|
|
623
|
+
proposerBoost,
|
|
624
|
+
...checkpoints,
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
} else {
|
|
628
|
+
// Pre-gloas the boost is unconditional, so attestation deltas and boost deltas can propagate
|
|
629
|
+
// up the branch in a single pass
|
|
630
|
+
this.protoArray.applyScoreChanges({
|
|
631
|
+
attestationDeltas,
|
|
632
|
+
proposerBoost: boostedBlock ? this.getProposerBoost() : null,
|
|
633
|
+
...checkpoints,
|
|
634
|
+
});
|
|
635
|
+
}
|
|
620
636
|
|
|
621
637
|
// findHead returns the ProtoNode representing the head
|
|
622
638
|
const head = this.protoArray.findHead(this.fcStore.justified.checkpoint.rootHex, currentSlot);
|
|
@@ -826,6 +842,8 @@ export class ForkChoice implements IForkChoice {
|
|
|
826
842
|
targetRoot: toRootHex(targetRoot),
|
|
827
843
|
stateRoot: toRootHex(block.stateRoot),
|
|
828
844
|
timeliness: isTimely,
|
|
845
|
+
ptcTimeliness: this.isBlockPtcTimely(block, blockDelaySec),
|
|
846
|
+
proposerIndex: block.proposerIndex,
|
|
829
847
|
|
|
830
848
|
justifiedEpoch: stateJustifiedEpoch,
|
|
831
849
|
justifiedRoot: toRootHex(state.currentJustifiedCheckpoint.root),
|
|
@@ -1078,11 +1096,20 @@ export class ForkChoice implements IForkChoice {
|
|
|
1078
1096
|
while (this.fcStore.currentSlot < currentSlot) {
|
|
1079
1097
|
const previousSlot = this.fcStore.currentSlot;
|
|
1080
1098
|
// Note: we are relying upon `onTick` to update `fcStore.time` to ensure we don't get stuck in a loop.
|
|
1081
|
-
this.onTick(previousSlot + 1);
|
|
1099
|
+
const didUpdateCheckpoints = this.onTick(previousSlot + 1);
|
|
1082
1100
|
this.queuedAttestationsPreviousSlot = 0;
|
|
1083
1101
|
// Process any attestations that might now be eligible before running FCR for this slot.
|
|
1084
1102
|
this.processAttestationQueue();
|
|
1085
|
-
this.runFastConfirmation();
|
|
1103
|
+
const didRecomputeHead = this.runFastConfirmation();
|
|
1104
|
+
|
|
1105
|
+
// An epoch-boundary checkpoint pull-up can move the head's dependent root and stale the cached
|
|
1106
|
+
// head before block 0 of the new epoch is imported, making isProposerBoostSameDependentRoot()
|
|
1107
|
+
// wrong for that block. Recompute the head so it reflects the new checkpoint and the queued
|
|
1108
|
+
// votes — unless fast confirmation already did, to avoid a redundant head calculation.
|
|
1109
|
+
if (didUpdateCheckpoints && !didRecomputeHead) {
|
|
1110
|
+
this.updateHead();
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1086
1113
|
this.validatedAttestationDatas = new Set();
|
|
1087
1114
|
}
|
|
1088
1115
|
}
|
|
@@ -1593,7 +1620,8 @@ export class ForkChoice implements IForkChoice {
|
|
|
1593
1620
|
// Only ever called on a block already in fork choice, so a miss is a broken invariant.
|
|
1594
1621
|
const node = this.protoArray.getNodeDefaultStatus(blockRoot);
|
|
1595
1622
|
if (node === undefined) {
|
|
1596
|
-
// this is called for head
|
|
1623
|
+
// this is called for head or the boosted block's parent, both of which should always be
|
|
1624
|
+
// in forkchoice, otherwise we have a serious error
|
|
1597
1625
|
throw new ForkChoiceError({code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK, root: blockRoot});
|
|
1598
1626
|
}
|
|
1599
1627
|
|
|
@@ -1613,7 +1641,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
1613
1641
|
// always 0. Return before fetching the state and walking the block's committees.
|
|
1614
1642
|
if (equivocatingIndices.size > 0) {
|
|
1615
1643
|
const state = this.fcStore.stateGetter({stateRoot: node.stateRoot});
|
|
1616
|
-
// Only ever called on the head, so the state is always cached.
|
|
1644
|
+
// Only ever called on the head or the boosted block's parent, so the state is always cached.
|
|
1617
1645
|
// A miss is a broken invariant, not a recoverable state.
|
|
1618
1646
|
if (state === null) {
|
|
1619
1647
|
throw new ForkChoiceError({
|
|
@@ -1676,6 +1704,81 @@ export class ForkChoice implements IForkChoice {
|
|
|
1676
1704
|
return this.fcStore.currentSlot === block.slot && isBeforeLateBlockCutoff;
|
|
1677
1705
|
}
|
|
1678
1706
|
|
|
1707
|
+
/**
|
|
1708
|
+
* Return true if the block arrived before the PTC (payload-timeliness committee) deadline,
|
|
1709
|
+
* ie. block_timeliness[PTC_TIMELINESS_INDEX]. should_apply_proposer_boost uses this as the
|
|
1710
|
+
* definition of an "early" proposer equivocation: only a sibling seen by the PTC deadline
|
|
1711
|
+
* proves the proposer equivocated soon enough that honest nodes could have withheld the boost.
|
|
1712
|
+
*
|
|
1713
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.14/specs/gloas/fork-choice.md#modified-record_block_timeliness
|
|
1714
|
+
*
|
|
1715
|
+
* Child class can overwrite this for testing purpose.
|
|
1716
|
+
*/
|
|
1717
|
+
protected isBlockPtcTimely(block: BeaconBlock, blockDelaySec: number): boolean {
|
|
1718
|
+
const ptcThresholdMs = this.config.getSlotComponentDurationMs(this.config.PAYLOAD_ATTESTATION_DUE_BPS);
|
|
1719
|
+
return this.fcStore.currentSlot === block.slot && blockDelaySec * 1000 < ptcThresholdMs;
|
|
1720
|
+
}
|
|
1721
|
+
|
|
1722
|
+
/**
|
|
1723
|
+
* Determine whether proposer boost should be applied to `proposerBoostRoot`.
|
|
1724
|
+
*
|
|
1725
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.14/specs/gloas/fork-choice.md#new-should_apply_proposer_boost
|
|
1726
|
+
*
|
|
1727
|
+
* Pre-gloas blocks always receive the boost (unconditional, backward compatible). For gloas
|
|
1728
|
+
* blocks the boost is withheld when the parent is a weak block from the previous slot and the
|
|
1729
|
+
* proposer of that parent equivocated (published another PTC-timely block at the same slot).
|
|
1730
|
+
*/
|
|
1731
|
+
private shouldApplyProposerBoost(): boolean {
|
|
1732
|
+
if (!this.proposerBoostRoot) {
|
|
1733
|
+
return false;
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
const boostedBlock = this.getBlockHexDefaultStatus(this.proposerBoostRoot);
|
|
1737
|
+
// Pre-gloas blocks always get boost
|
|
1738
|
+
if (!boostedBlock || !isGloasBlock(boostedBlock)) {
|
|
1739
|
+
return true;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
const parentBlock = this.getBlockHexDefaultStatus(boostedBlock.parentRoot);
|
|
1743
|
+
if (!parentBlock) {
|
|
1744
|
+
return true;
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
// Apply proposer boost if parent is not from the previous slot
|
|
1748
|
+
if (parentBlock.slot + 1 < boostedBlock.slot) {
|
|
1749
|
+
return true;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
// Apply proposer boost if parent is not weak
|
|
1753
|
+
if (!this.isHeadWeak(parentBlock.blockRoot)) {
|
|
1754
|
+
return true;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
// Parent is weak and from the previous slot: apply boost only if there are no early
|
|
1758
|
+
// equivocations, ie. no other PTC-timely block at the parent's slot from the same proposer.
|
|
1759
|
+
return !this.protoArray.hasEquivocatingBlock(parentBlock.proposerIndex, parentBlock.slot, parentBlock.blockRoot);
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1762
|
+
/**
|
|
1763
|
+
* The proposer boost to apply to `proposerBoostRoot`, propagated up the branch by
|
|
1764
|
+
* applyScoreChanges() together with the deltas.
|
|
1765
|
+
*/
|
|
1766
|
+
private getProposerBoost(): {root: RootHex; score: bigint} | null {
|
|
1767
|
+
if (!this.proposerBoostRoot) {
|
|
1768
|
+
return null;
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
const proposerBoostScore =
|
|
1772
|
+
this.justifiedProposerBoostScore ??
|
|
1773
|
+
getCommitteeFraction(this.fcStore.justified.totalBalance, {
|
|
1774
|
+
slotsPerEpoch: SLOTS_PER_EPOCH,
|
|
1775
|
+
committeePercent: this.config.PROPOSER_SCORE_BOOST,
|
|
1776
|
+
});
|
|
1777
|
+
this.justifiedProposerBoostScore = proposerBoostScore;
|
|
1778
|
+
|
|
1779
|
+
return {root: this.proposerBoostRoot, score: proposerBoostScore};
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1679
1782
|
/**
|
|
1680
1783
|
* https://github.com/ethereum/consensus-specs/blob/v1.5.0/specs/phase0/fork-choice.md#is_proposing_on_time
|
|
1681
1784
|
*/
|
|
@@ -1728,23 +1831,31 @@ export class ForkChoice implements IForkChoice {
|
|
|
1728
1831
|
* May need the justified balances of:
|
|
1729
1832
|
* - unrealizedJustified: Already available in `CheckpointWithBalance`
|
|
1730
1833
|
* Since this balances are already available the getter is just `() => balances`, without cache interaction
|
|
1834
|
+
*
|
|
1835
|
+
* @returns Whether either checkpoint was updated.
|
|
1731
1836
|
*/
|
|
1732
1837
|
private updateCheckpoints(
|
|
1733
1838
|
justifiedCheckpoint: CheckpointWithHex,
|
|
1734
1839
|
finalizedCheckpoint: CheckpointWithHex,
|
|
1735
1840
|
getJustifiedBalances: () => JustifiedBalances
|
|
1736
|
-
):
|
|
1841
|
+
): boolean {
|
|
1842
|
+
let updated = false;
|
|
1843
|
+
|
|
1737
1844
|
// Update justified checkpoint.
|
|
1738
1845
|
if (justifiedCheckpoint.epoch > this.fcStore.justified.checkpoint.epoch) {
|
|
1739
1846
|
this.fcStore.justified = {checkpoint: justifiedCheckpoint, balances: getJustifiedBalances()};
|
|
1740
1847
|
this.justifiedProposerBoostScore = null;
|
|
1848
|
+
updated = true;
|
|
1741
1849
|
}
|
|
1742
1850
|
|
|
1743
1851
|
// Update finalized checkpoint.
|
|
1744
1852
|
if (finalizedCheckpoint.epoch > this.fcStore.finalizedCheckpoint.epoch) {
|
|
1745
1853
|
this.fcStore.finalizedCheckpoint = finalizedCheckpoint;
|
|
1746
1854
|
this.justifiedProposerBoostScore = null;
|
|
1855
|
+
updated = true;
|
|
1747
1856
|
}
|
|
1857
|
+
|
|
1858
|
+
return updated;
|
|
1748
1859
|
}
|
|
1749
1860
|
|
|
1750
1861
|
/**
|
|
@@ -2030,8 +2141,10 @@ export class ForkChoice implements IForkChoice {
|
|
|
2030
2141
|
* Equivalent to:
|
|
2031
2142
|
*
|
|
2032
2143
|
* https://github.com/ethereum/consensus-specs/blob/v1.1.10/specs/phase0/fork-choice.md#on_tick
|
|
2144
|
+
*
|
|
2145
|
+
* @returns Whether an epoch-boundary checkpoint was updated.
|
|
2033
2146
|
*/
|
|
2034
|
-
private onTick(time: Slot):
|
|
2147
|
+
private onTick(time: Slot): boolean {
|
|
2035
2148
|
const previousSlot = this.fcStore.currentSlot;
|
|
2036
2149
|
|
|
2037
2150
|
if (time > previousSlot + 1) {
|
|
@@ -2052,11 +2165,11 @@ export class ForkChoice implements IForkChoice {
|
|
|
2052
2165
|
|
|
2053
2166
|
// Not a new epoch, return.
|
|
2054
2167
|
if (computeSlotsSinceEpochStart(time) !== 0) {
|
|
2055
|
-
return;
|
|
2168
|
+
return false;
|
|
2056
2169
|
}
|
|
2057
2170
|
|
|
2058
|
-
// If a new epoch, pull-up justification and finalization from previous epoch
|
|
2059
|
-
this.updateCheckpoints(
|
|
2171
|
+
// If a new epoch, pull-up justification and finalization from previous epoch.
|
|
2172
|
+
return this.updateCheckpoints(
|
|
2060
2173
|
this.fcStore.unrealizedJustified.checkpoint,
|
|
2061
2174
|
this.fcStore.unrealizedFinalizedCheckpoint,
|
|
2062
2175
|
() => this.fcStore.unrealizedJustified.balances
|
|
@@ -2115,10 +2228,11 @@ export class ForkChoice implements IForkChoice {
|
|
|
2115
2228
|
return {prelimProposerHead};
|
|
2116
2229
|
}
|
|
2117
2230
|
|
|
2118
|
-
|
|
2231
|
+
/** Returns whether it recomputed the head, so the caller can avoid a redundant `updateHead()`. */
|
|
2232
|
+
private runFastConfirmation(): boolean {
|
|
2119
2233
|
const fastConfirmationRule = this.fastConfirmationRule;
|
|
2120
2234
|
const fastConfirmationContext = this.fastConfirmationContext;
|
|
2121
|
-
if (!fastConfirmationRule || !fastConfirmationContext) return;
|
|
2235
|
+
if (!fastConfirmationRule || !fastConfirmationContext) return false;
|
|
2122
2236
|
|
|
2123
2237
|
if (this.fastConfirmationPaused) {
|
|
2124
2238
|
// Keep consumers on a safe, available root while the rule is paused
|
|
@@ -2129,7 +2243,7 @@ export class ForkChoice implements IForkChoice {
|
|
|
2129
2243
|
// Runs outside the timed try/catch below; a throw would escape to the clock listener
|
|
2130
2244
|
this.logger?.debug("Fast confirmation notify failed", {slot: this.fcStore.currentSlot}, err as Error);
|
|
2131
2245
|
}
|
|
2132
|
-
return;
|
|
2246
|
+
return false;
|
|
2133
2247
|
}
|
|
2134
2248
|
|
|
2135
2249
|
withObservedDuration(this.metrics?.fastConfirmation.totalDuration.startTimer(), () => {
|
|
@@ -2152,6 +2266,8 @@ export class ForkChoice implements IForkChoice {
|
|
|
2152
2266
|
);
|
|
2153
2267
|
}
|
|
2154
2268
|
});
|
|
2269
|
+
|
|
2270
|
+
return true;
|
|
2155
2271
|
}
|
|
2156
2272
|
|
|
2157
2273
|
private createFastConfirmationContext(): FastConfirmationContext {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {DataAvailabilityStatus} from "@lodestar/state-transition";
|
|
2
|
-
import {Epoch, RootHex, Slot, UintNum64} from "@lodestar/types";
|
|
2
|
+
import {Epoch, RootHex, Slot, UintNum64, ValidatorIndex} from "@lodestar/types";
|
|
3
3
|
|
|
4
4
|
// RootHex is a root as a hex string
|
|
5
5
|
// Used for lightweight and easy comparison
|
|
@@ -144,6 +144,13 @@ export type ProtoBlock = BlockExtraMeta & {
|
|
|
144
144
|
// Indicate whether block arrives in a timely manner ie. before the 4 second mark
|
|
145
145
|
timeliness: boolean;
|
|
146
146
|
|
|
147
|
+
// Indicate whether block arrives before the PTC deadline
|
|
148
|
+
// Spec: gloas/fork-choice.md#modified-record_block_timeliness (block_timeliness[PTC_TIMELINESS_INDEX])
|
|
149
|
+
ptcTimeliness: boolean;
|
|
150
|
+
|
|
151
|
+
// The index of the block proposer. Used by should_apply_proposer_boost to detect proposer equivocations
|
|
152
|
+
proposerIndex: ValidatorIndex;
|
|
153
|
+
|
|
147
154
|
/** Payload status for this node (Gloas fork). Always FULL in pre-gloas */
|
|
148
155
|
payloadStatus: PayloadStatus;
|
|
149
156
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {BitArray} from "@chainsafe/ssz";
|
|
2
2
|
import {EFFECTIVE_BALANCE_INCREMENT, GENESIS_EPOCH, GENESIS_SLOT, PTC_SIZE} from "@lodestar/params";
|
|
3
3
|
import {DataAvailabilityStatus, computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
4
|
-
import {Epoch, RootHex, Slot} from "@lodestar/types";
|
|
4
|
+
import {Epoch, RootHex, Slot, ValidatorIndex} from "@lodestar/types";
|
|
5
5
|
import {bitCount, toRootHex} from "@lodestar/utils";
|
|
6
6
|
import {ForkChoiceError, ForkChoiceErrorCode} from "../forkChoice/errors.js";
|
|
7
7
|
import {LVHExecError, LVHExecErrorCode, ProtoArrayError, ProtoArrayErrorCode} from "./errors.js";
|
|
@@ -2007,6 +2007,32 @@ export class ProtoArray {
|
|
|
2007
2007
|
return this.getNodeByIndex(nodeIndex);
|
|
2008
2008
|
}
|
|
2009
2009
|
|
|
2010
|
+
/**
|
|
2011
|
+
* Return true if a block other than `excludeRoot` at `slot` was proposed by `proposerIndex` and
|
|
2012
|
+
* is PTC-timely. Used by `should_apply_proposer_boost` to detect proposer equivocations.
|
|
2013
|
+
*
|
|
2014
|
+
* https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.14/specs/gloas/fork-choice.md#new-should_apply_proposer_boost
|
|
2015
|
+
*
|
|
2016
|
+
* Iterates unique block roots (via the canonical variant) since `slot`, `proposerIndex` and
|
|
2017
|
+
* `ptcTimeliness` are block-level properties identical across payload-status variants.
|
|
2018
|
+
*/
|
|
2019
|
+
hasEquivocatingBlock(proposerIndex: ValidatorIndex, slot: Slot, excludeRoot: RootHex): boolean {
|
|
2020
|
+
for (const root of this.indices.keys()) {
|
|
2021
|
+
if (root === excludeRoot) {
|
|
2022
|
+
continue;
|
|
2023
|
+
}
|
|
2024
|
+
const nodeIndex = this.getDefaultNodeIndex(root);
|
|
2025
|
+
if (nodeIndex === undefined) {
|
|
2026
|
+
continue;
|
|
2027
|
+
}
|
|
2028
|
+
const node = this.nodes[nodeIndex];
|
|
2029
|
+
if (node !== undefined && node.slot === slot && node.proposerIndex === proposerIndex && node.ptcTimeliness) {
|
|
2030
|
+
return true;
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
return false;
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2010
2036
|
/**
|
|
2011
2037
|
* Return MUTABLE ProtoBlock for blockRoot with explicit payload status
|
|
2012
2038
|
*
|