@lodestar/fork-choice 1.43.0-dev.4fb05c546d → 1.43.0-dev.549a5b8115
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 +21 -18
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +60 -66
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +26 -8
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/forkChoice/store.d.ts +16 -40
- package/lib/forkChoice/store.d.ts.map +1 -1
- package/lib/forkChoice/store.js +4 -22
- package/lib/forkChoice/store.js.map +1 -1
- package/lib/index.d.ts +3 -3
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/protoArray/protoArray.d.ts +4 -1
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +14 -21
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +7 -7
- package/src/forkChoice/forkChoice.ts +75 -110
- package/src/forkChoice/interface.ts +27 -9
- package/src/forkChoice/store.ts +20 -52
- package/src/index.ts +3 -9
- package/src/protoArray/protoArray.ts +18 -33
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
ProtoNode,
|
|
10
10
|
} from "../protoArray/interface.js";
|
|
11
11
|
import {UpdateAndGetHeadOpt} from "./forkChoice.js";
|
|
12
|
-
import {CheckpointWithHex
|
|
12
|
+
import {CheckpointWithHex} from "./store.js";
|
|
13
13
|
|
|
14
14
|
export type CheckpointHex = {
|
|
15
15
|
epoch: Epoch;
|
|
@@ -21,12 +21,12 @@ export type CheckpointsWithHex = {
|
|
|
21
21
|
finalizedCheckpoint: CheckpointWithHex;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
export type
|
|
25
|
-
checkpoint:
|
|
24
|
+
export type CheckpointWithBalance = {
|
|
25
|
+
checkpoint: CheckpointWithHex;
|
|
26
26
|
balances: EffectiveBalanceIncrements;
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
export type
|
|
29
|
+
export type CheckpointWithTotalBalance = CheckpointWithBalance & {
|
|
30
30
|
totalBalance: number;
|
|
31
31
|
};
|
|
32
32
|
|
|
@@ -121,8 +121,8 @@ export interface IForkChoice {
|
|
|
121
121
|
* Retrieve all nodes for the debug API.
|
|
122
122
|
*/
|
|
123
123
|
getAllNodes(): ProtoNode[];
|
|
124
|
-
getFinalizedCheckpoint():
|
|
125
|
-
getJustifiedCheckpoint():
|
|
124
|
+
getFinalizedCheckpoint(): CheckpointWithHex;
|
|
125
|
+
getJustifiedCheckpoint(): CheckpointWithHex;
|
|
126
126
|
/**
|
|
127
127
|
* Add `block` to the fork choice DAG.
|
|
128
128
|
*
|
|
@@ -198,14 +198,13 @@ export interface IForkChoice {
|
|
|
198
198
|
* @param blockRoot - The beacon block root for which the payload arrived
|
|
199
199
|
* @param executionPayloadBlockHash - The block hash of the execution payload
|
|
200
200
|
* @param executionPayloadNumber - The block number of the execution payload
|
|
201
|
-
* @param executionPayloadStateRoot - The execution payload state root ie. the root of post-state after processExecutionPayloadEnvelope()
|
|
202
201
|
*/
|
|
203
202
|
onExecutionPayload(
|
|
204
203
|
blockRoot: RootHex,
|
|
205
204
|
executionPayloadBlockHash: RootHex,
|
|
206
205
|
executionPayloadNumber: number,
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
executionStatus: PayloadExecutionStatus,
|
|
207
|
+
dataAvailabilityStatus: DataAvailabilityStatus
|
|
209
208
|
): void;
|
|
210
209
|
/**
|
|
211
210
|
* Call `onTick` for all slots between `fcStore.getCurrentSlot()` and the provided `currentSlot`.
|
|
@@ -233,6 +232,7 @@ export interface IForkChoice {
|
|
|
233
232
|
hasPayloadUnsafe(blockRoot: Root): boolean;
|
|
234
233
|
hasPayloadHexUnsafe(blockRoot: RootHex): boolean;
|
|
235
234
|
getSlotsPresent(windowStart: number): number;
|
|
235
|
+
getPTCVotes(blockRootHex: RootHex): (boolean | null)[] | null;
|
|
236
236
|
/**
|
|
237
237
|
* Returns a `ProtoBlock` if the block is known **and** a descendant of the finalized root.
|
|
238
238
|
*/
|
|
@@ -273,11 +273,23 @@ export interface IForkChoice {
|
|
|
273
273
|
getAllNonAncestorBlocks(blockRoot: RootHex, payloadStatus: PayloadStatus): ProtoBlock[];
|
|
274
274
|
/**
|
|
275
275
|
* Returns both ancestor and non-ancestor blocks in a single traversal.
|
|
276
|
+
*
|
|
277
|
+
* `ancestors` is the raw walk and includes the previous finalized block as its last element —
|
|
278
|
+
* callers that don't want the boundary should slice it off themselves.
|
|
276
279
|
*/
|
|
277
280
|
getAllAncestorAndNonAncestorBlocks(
|
|
278
281
|
blockRoot: RootHex,
|
|
279
282
|
payloadStatus: PayloadStatus
|
|
280
283
|
): {ancestors: ProtoBlock[]; nonAncestors: ProtoBlock[]};
|
|
284
|
+
/**
|
|
285
|
+
* Same as `getAllAncestorAndNonAncestorBlocks` but resolves the default payload-status variant
|
|
286
|
+
* (FULL pre-Gloas, PENDING for Gloas) for the given root. Use when the caller holds a
|
|
287
|
+
* `CheckpointWithHex` / finalized root without a specific payload-status variant in mind.
|
|
288
|
+
*/
|
|
289
|
+
getAllAncestorAndNonAncestorBlocksDefaultStatus(blockRoot: RootHex): {
|
|
290
|
+
ancestors: ProtoBlock[];
|
|
291
|
+
nonAncestors: ProtoBlock[];
|
|
292
|
+
};
|
|
281
293
|
getCanonicalBlockByRoot(blockRoot: Root): ProtoBlock | null;
|
|
282
294
|
getCanonicalBlockAtSlot(slot: Slot): ProtoBlock | null;
|
|
283
295
|
getCanonicalBlockClosestLteSlot(slot: Slot): ProtoBlock | null;
|
|
@@ -289,6 +301,12 @@ export interface IForkChoice {
|
|
|
289
301
|
* Iterates forward descendants of blockRoot. Does not yield blockRoot itself
|
|
290
302
|
*/
|
|
291
303
|
forwardIterateDescendants(blockRoot: RootHex, payloadStatus: PayloadStatus): IterableIterator<ProtoBlock>;
|
|
304
|
+
/**
|
|
305
|
+
* Same as `forwardIterateDescendants` but resolves the default payload-status variant
|
|
306
|
+
* (FULL pre-Gloas, PENDING for Gloas) for the given root. Use when the caller holds a
|
|
307
|
+
* `CheckpointWithHex` / finalized root without a specific payload-status variant in mind.
|
|
308
|
+
*/
|
|
309
|
+
forwardIterateDescendantsDefaultStatus(blockRoot: RootHex): IterableIterator<ProtoBlock>;
|
|
292
310
|
getBlockSummariesByParentRoot(parentRoot: RootHex): ProtoBlock[];
|
|
293
311
|
getBlockSummariesAtSlot(slot: Slot): ProtoBlock[];
|
|
294
312
|
/** Returns the distance of common ancestor of nodes to the max of the newNode and the prevNode. */
|
package/src/forkChoice/store.ts
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import {EffectiveBalanceIncrements, IBeaconStateView} from "@lodestar/state-transition";
|
|
2
2
|
import {RootHex, Slot, ValidatorIndex, phase0} from "@lodestar/types";
|
|
3
3
|
import {toRootHex} from "@lodestar/utils";
|
|
4
|
-
import {
|
|
5
|
-
import {CheckpointWithPayloadAndBalance, CheckpointWithPayloadAndTotalBalance} from "./interface.js";
|
|
4
|
+
import {CheckpointWithBalance, CheckpointWithTotalBalance} from "./interface.js";
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Stores checkpoints in a hybrid format:
|
|
@@ -11,15 +10,6 @@ import {CheckpointWithPayloadAndBalance, CheckpointWithPayloadAndTotalBalance} f
|
|
|
11
10
|
*/
|
|
12
11
|
export type CheckpointWithHex = phase0.Checkpoint & {rootHex: RootHex};
|
|
13
12
|
|
|
14
|
-
/**
|
|
15
|
-
* Checkpoint with payload status for Gloas fork choice.
|
|
16
|
-
* Used to track which variant (EMPTY or FULL) of the finalized/justified block to use.
|
|
17
|
-
*
|
|
18
|
-
* Pre-Gloas: payloadStatus is always FULL (payload embedded in block)
|
|
19
|
-
* Gloas: determined by state.execution_payload_availability
|
|
20
|
-
*/
|
|
21
|
-
export type CheckpointWithPayloadStatus = CheckpointWithHex & {payloadStatus: PayloadStatus};
|
|
22
|
-
|
|
23
13
|
export type JustifiedBalances = EffectiveBalanceIncrements;
|
|
24
14
|
|
|
25
15
|
/**
|
|
@@ -29,7 +19,7 @@ export type JustifiedBalances = EffectiveBalanceIncrements;
|
|
|
29
19
|
* @param blockState state that declares justified checkpoint `checkpoint`
|
|
30
20
|
*/
|
|
31
21
|
export type JustifiedBalancesGetter = (
|
|
32
|
-
checkpoint:
|
|
22
|
+
checkpoint: CheckpointWithHex,
|
|
33
23
|
blockState: IBeaconStateView
|
|
34
24
|
) => JustifiedBalances;
|
|
35
25
|
|
|
@@ -47,11 +37,11 @@ export type JustifiedBalancesGetter = (
|
|
|
47
37
|
*/
|
|
48
38
|
export interface IForkChoiceStore {
|
|
49
39
|
currentSlot: Slot;
|
|
50
|
-
get justified():
|
|
51
|
-
set justified(justified:
|
|
52
|
-
unrealizedJustified:
|
|
53
|
-
finalizedCheckpoint:
|
|
54
|
-
unrealizedFinalizedCheckpoint:
|
|
40
|
+
get justified(): CheckpointWithTotalBalance;
|
|
41
|
+
set justified(justified: CheckpointWithBalance);
|
|
42
|
+
unrealizedJustified: CheckpointWithBalance;
|
|
43
|
+
finalizedCheckpoint: CheckpointWithHex;
|
|
44
|
+
unrealizedFinalizedCheckpoint: CheckpointWithHex;
|
|
55
45
|
justifiedBalancesGetter: JustifiedBalancesGetter;
|
|
56
46
|
equivocatingIndices: Set<ValidatorIndex>;
|
|
57
47
|
}
|
|
@@ -60,10 +50,10 @@ export interface IForkChoiceStore {
|
|
|
60
50
|
* IForkChoiceStore implementer which emits forkChoice events on updated justified and finalized checkpoints.
|
|
61
51
|
*/
|
|
62
52
|
export class ForkChoiceStore implements IForkChoiceStore {
|
|
63
|
-
private _justified:
|
|
64
|
-
unrealizedJustified:
|
|
65
|
-
private _finalizedCheckpoint:
|
|
66
|
-
unrealizedFinalizedCheckpoint:
|
|
53
|
+
private _justified: CheckpointWithTotalBalance;
|
|
54
|
+
unrealizedJustified: CheckpointWithBalance;
|
|
55
|
+
private _finalizedCheckpoint: CheckpointWithHex;
|
|
56
|
+
unrealizedFinalizedCheckpoint: CheckpointWithHex;
|
|
67
57
|
equivocatingIndices = new Set<ValidatorIndex>();
|
|
68
58
|
justifiedBalancesGetter: JustifiedBalancesGetter;
|
|
69
59
|
currentSlot: Slot;
|
|
@@ -74,49 +64,37 @@ export class ForkChoiceStore implements IForkChoiceStore {
|
|
|
74
64
|
finalizedCheckpoint: phase0.Checkpoint,
|
|
75
65
|
justifiedBalances: EffectiveBalanceIncrements,
|
|
76
66
|
justifiedBalancesGetter: JustifiedBalancesGetter,
|
|
77
|
-
/**
|
|
78
|
-
* Payload status for justified checkpoint.
|
|
79
|
-
* Pre-Gloas: always FULL
|
|
80
|
-
* Gloas: determined by state.execution_payload_availability
|
|
81
|
-
*/
|
|
82
|
-
justifiedPayloadStatus: PayloadStatus,
|
|
83
|
-
/**
|
|
84
|
-
* Payload status for finalized checkpoint.
|
|
85
|
-
* Pre-Gloas: always FULL
|
|
86
|
-
* Gloas: determined by state.execution_payload_availability
|
|
87
|
-
*/
|
|
88
|
-
finalizedPayloadStatus: PayloadStatus,
|
|
89
67
|
private readonly events?: {
|
|
90
|
-
onJustified: (cp:
|
|
91
|
-
onFinalized: (cp:
|
|
68
|
+
onJustified: (cp: CheckpointWithHex) => void;
|
|
69
|
+
onFinalized: (cp: CheckpointWithHex) => void;
|
|
92
70
|
}
|
|
93
71
|
) {
|
|
94
72
|
this.justifiedBalancesGetter = justifiedBalancesGetter;
|
|
95
73
|
this.currentSlot = currentSlot;
|
|
96
74
|
const justified = {
|
|
97
|
-
checkpoint:
|
|
75
|
+
checkpoint: toCheckpointWithHex(justifiedCheckpoint),
|
|
98
76
|
balances: justifiedBalances,
|
|
99
77
|
totalBalance: computeTotalBalance(justifiedBalances),
|
|
100
78
|
};
|
|
101
79
|
this._justified = justified;
|
|
102
80
|
this.unrealizedJustified = justified;
|
|
103
|
-
this._finalizedCheckpoint =
|
|
81
|
+
this._finalizedCheckpoint = toCheckpointWithHex(finalizedCheckpoint);
|
|
104
82
|
this.unrealizedFinalizedCheckpoint = this._finalizedCheckpoint;
|
|
105
83
|
}
|
|
106
84
|
|
|
107
|
-
get justified():
|
|
85
|
+
get justified(): CheckpointWithTotalBalance {
|
|
108
86
|
return this._justified;
|
|
109
87
|
}
|
|
110
|
-
set justified(justified:
|
|
88
|
+
set justified(justified: CheckpointWithBalance) {
|
|
111
89
|
this._justified = {...justified, totalBalance: computeTotalBalance(justified.balances)};
|
|
112
90
|
this.events?.onJustified(justified.checkpoint);
|
|
113
91
|
}
|
|
114
92
|
|
|
115
|
-
get finalizedCheckpoint():
|
|
93
|
+
get finalizedCheckpoint(): CheckpointWithHex {
|
|
116
94
|
return this._finalizedCheckpoint;
|
|
117
95
|
}
|
|
118
|
-
set finalizedCheckpoint(checkpoint:
|
|
119
|
-
const cp =
|
|
96
|
+
set finalizedCheckpoint(checkpoint: CheckpointWithHex) {
|
|
97
|
+
const cp = toCheckpointWithHex(checkpoint);
|
|
120
98
|
this._finalizedCheckpoint = cp;
|
|
121
99
|
this.events?.onFinalized(cp);
|
|
122
100
|
}
|
|
@@ -133,16 +111,6 @@ export function toCheckpointWithHex(checkpoint: phase0.Checkpoint): CheckpointWi
|
|
|
133
111
|
};
|
|
134
112
|
}
|
|
135
113
|
|
|
136
|
-
export function toCheckpointWithPayload(
|
|
137
|
-
checkpoint: phase0.Checkpoint,
|
|
138
|
-
payloadStatus: PayloadStatus
|
|
139
|
-
): CheckpointWithPayloadStatus {
|
|
140
|
-
return {
|
|
141
|
-
...toCheckpointWithHex(checkpoint),
|
|
142
|
-
payloadStatus,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
114
|
export function equalCheckpointWithHex(a: CheckpointWithHex, b: CheckpointWithHex): boolean {
|
|
147
115
|
return a.epoch === b.epoch && a.rootHex === b.rootHex;
|
|
148
116
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,17 +6,12 @@ export {
|
|
|
6
6
|
type InvalidBlock,
|
|
7
7
|
InvalidBlockCode,
|
|
8
8
|
} from "./forkChoice/errors.js";
|
|
9
|
-
export {
|
|
10
|
-
ForkChoice,
|
|
11
|
-
type ForkChoiceOpts,
|
|
12
|
-
UpdateHeadOpt,
|
|
13
|
-
getCheckpointPayloadStatus,
|
|
14
|
-
} from "./forkChoice/forkChoice.js";
|
|
9
|
+
export {ForkChoice, type ForkChoiceOpts, UpdateHeadOpt} from "./forkChoice/forkChoice.js";
|
|
15
10
|
export {
|
|
16
11
|
type AncestorResult,
|
|
17
12
|
AncestorStatus,
|
|
18
|
-
type
|
|
19
|
-
type
|
|
13
|
+
type CheckpointWithBalance,
|
|
14
|
+
type CheckpointWithTotalBalance,
|
|
20
15
|
EpochDifference,
|
|
21
16
|
type IForkChoice,
|
|
22
17
|
NotReorgedReason,
|
|
@@ -24,7 +19,6 @@ export {
|
|
|
24
19
|
export * from "./forkChoice/safeBlocks.js";
|
|
25
20
|
export {
|
|
26
21
|
type CheckpointWithHex,
|
|
27
|
-
type CheckpointWithPayloadStatus,
|
|
28
22
|
ForkChoiceStore,
|
|
29
23
|
type IForkChoiceStore,
|
|
30
24
|
type JustifiedBalancesGetter,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {BitArray} from "@chainsafe/ssz";
|
|
2
2
|
import {GENESIS_EPOCH, PTC_SIZE} from "@lodestar/params";
|
|
3
|
-
import {computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
3
|
+
import {DataAvailabilityStatus, computeEpochAtSlot, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
4
4
|
import {Epoch, RootHex, Slot} from "@lodestar/types";
|
|
5
5
|
import {bitCount, toRootHex} from "@lodestar/utils";
|
|
6
6
|
import {ForkChoiceError, ForkChoiceErrorCode} from "../forkChoice/errors.js";
|
|
@@ -109,30 +109,6 @@ export class ProtoArray {
|
|
|
109
109
|
null
|
|
110
110
|
);
|
|
111
111
|
|
|
112
|
-
// Anchor block PTC votes must be all-true per spec get_forkchoice_store:
|
|
113
|
-
// payload_timeliness_vote={anchor_root: Vector[boolean, PTC_SIZE](True for _ in range(PTC_SIZE))}
|
|
114
|
-
// Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.4/specs/gloas/fork-choice.md#modified-get_forkchoice_store
|
|
115
|
-
if (protoArray.ptcVotes.has(block.blockRoot)) {
|
|
116
|
-
protoArray.ptcVotes.set(block.blockRoot, BitArray.fromBoolArray(Array.from({length: PTC_SIZE}, () => true)));
|
|
117
|
-
|
|
118
|
-
// In the spec, we have payload_states = {anchor_root: anchor_state.copy()}
|
|
119
|
-
// which means the anchor's "payload" is considered received
|
|
120
|
-
// Without FULL, blocks extending FULL from the anchor would be orphaned.
|
|
121
|
-
// TODO GLOAS: This is a bug in the spec. Keep this to pass the current spec test
|
|
122
|
-
// for now. Need to remove this when we work on v1.7.0-alpha.5
|
|
123
|
-
if (block.executionPayloadBlockHash !== null) {
|
|
124
|
-
protoArray.onExecutionPayload(
|
|
125
|
-
block.blockRoot,
|
|
126
|
-
currentSlot,
|
|
127
|
-
block.executionPayloadBlockHash,
|
|
128
|
-
(block as {executionPayloadNumber?: number}).executionPayloadNumber ?? 0,
|
|
129
|
-
block.stateRoot,
|
|
130
|
-
null,
|
|
131
|
-
ExecutionStatus.Valid
|
|
132
|
-
);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
112
|
return protoArray;
|
|
137
113
|
}
|
|
138
114
|
|
|
@@ -572,9 +548,9 @@ export class ProtoArray {
|
|
|
572
548
|
currentSlot: Slot,
|
|
573
549
|
executionPayloadBlockHash: RootHex,
|
|
574
550
|
executionPayloadNumber: number,
|
|
575
|
-
executionPayloadStateRoot: RootHex,
|
|
576
551
|
proposerBoostRoot: RootHex | null,
|
|
577
|
-
executionStatus: PayloadExecutionStatus
|
|
552
|
+
executionStatus: PayloadExecutionStatus,
|
|
553
|
+
dataAvailabilityStatus: DataAvailabilityStatus
|
|
578
554
|
): void {
|
|
579
555
|
// First check if block exists
|
|
580
556
|
const variants = this.indices.get(blockRoot);
|
|
@@ -616,7 +592,7 @@ export class ProtoArray {
|
|
|
616
592
|
});
|
|
617
593
|
}
|
|
618
594
|
|
|
619
|
-
// Create FULL variant as a child of PENDING (sibling to EMPTY)
|
|
595
|
+
// Create FULL variant as a child of PENDING (sibling to EMPTY).
|
|
620
596
|
const fullNode: ProtoNode = {
|
|
621
597
|
...pendingNode,
|
|
622
598
|
parent: pendingIndex, // Points to own PENDING (same as EMPTY)
|
|
@@ -628,7 +604,7 @@ export class ProtoArray {
|
|
|
628
604
|
executionStatus,
|
|
629
605
|
executionPayloadBlockHash,
|
|
630
606
|
executionPayloadNumber,
|
|
631
|
-
|
|
607
|
+
dataAvailabilityStatus,
|
|
632
608
|
};
|
|
633
609
|
|
|
634
610
|
const fullIndex = this.nodes.length;
|
|
@@ -665,6 +641,16 @@ export class ProtoArray {
|
|
|
665
641
|
}
|
|
666
642
|
}
|
|
667
643
|
|
|
644
|
+
getPTCVotes(blockRootHex: RootHex): BitArray | null {
|
|
645
|
+
const votes = this.ptcVotes.get(blockRootHex);
|
|
646
|
+
if (votes === undefined) {
|
|
647
|
+
// Block not found or not a Gloas block
|
|
648
|
+
return null;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return votes;
|
|
652
|
+
}
|
|
653
|
+
|
|
668
654
|
/**
|
|
669
655
|
* Check if execution payload for a block is timely
|
|
670
656
|
* Spec: gloas/fork-choice.md#new-is_payload_timely
|
|
@@ -1676,10 +1662,9 @@ export class ProtoArray {
|
|
|
1676
1662
|
const ancestors: ProtoNode[] = [];
|
|
1677
1663
|
const nonAncestors: ProtoNode[] = [];
|
|
1678
1664
|
|
|
1679
|
-
//
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
}
|
|
1665
|
+
// caller of this method may pass default status
|
|
1666
|
+
// this is the only node that we accept PENDING
|
|
1667
|
+
ancestors.push(node);
|
|
1683
1668
|
|
|
1684
1669
|
let nodeIndex = startIndex;
|
|
1685
1670
|
while (node.parent !== undefined) {
|