@lodestar/fork-choice 1.51.0-dev.ec493d509d → 1.51.0-dev.ee7f143944
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.d.ts +6 -1
- package/lib/forkChoice/errors.d.ts.map +1 -1
- package/lib/forkChoice/errors.js +1 -0
- package/lib/forkChoice/errors.js.map +1 -1
- package/lib/forkChoice/forkChoice.d.ts +8 -9
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +59 -18
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +10 -2
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/metrics.d.ts +3 -0
- package/lib/metrics.d.ts.map +1 -1
- package/lib/metrics.js +5 -0
- package/lib/metrics.js.map +1 -1
- package/lib/protoArray/interface.d.ts +2 -0
- package/lib/protoArray/interface.d.ts.map +1 -1
- package/lib/protoArray/interface.js.map +1 -1
- package/lib/protoArray/protoArray.d.ts +20 -4
- package/lib/protoArray/protoArray.d.ts.map +1 -1
- package/lib/protoArray/protoArray.js +115 -51
- package/lib/protoArray/protoArray.js.map +1 -1
- package/package.json +7 -7
- package/src/forkChoice/errors.ts +3 -1
- package/src/forkChoice/forkChoice.ts +63 -17
- package/src/forkChoice/interface.ts +8 -2
- package/src/metrics.ts +5 -0
- package/src/protoArray/interface.ts +2 -0
- package/src/protoArray/protoArray.ts +133 -56
|
@@ -1019,12 +1019,23 @@ export class ProtoArray {
|
|
|
1019
1019
|
}
|
|
1020
1020
|
|
|
1021
1021
|
/**
|
|
1022
|
-
*
|
|
1023
|
-
*
|
|
1024
|
-
*
|
|
1022
|
+
* Apply an execution engine verdict with a latest valid hash (LVH) to the tree. VALID flips Syncing nodes up to the
|
|
1023
|
+
* LVH node. INVALID invalidates the node identified by `invalidateFromParentBlockRoot/Hash` and its ancestors up to
|
|
1024
|
+
* the LVH node (exclusive), then every descendant. Returns whether any status changed.
|
|
1025
|
+
*
|
|
1026
|
+
* A null or unknown LVH means the engine only vouches for the queried payload being invalid: the queried node and
|
|
1027
|
+
* the nodes that inherit that payload (gloas PENDING and EMPTY variants) are invalidated, nothing above them.
|
|
1028
|
+
*
|
|
1029
|
+
* `strict` keeps the consensus failure semantics for verdicts about a payload the engine just processed: a VALID
|
|
1030
|
+
* or PreMerge node on the invalidated chain poisons the tree. Verdicts from forkchoiceUpdated or about an ancestor
|
|
1031
|
+
* chain are applied with `strict: false`: a node the engine already validated is never invalidated and the justified
|
|
1032
|
+
* node is never crossed. If the walk to the LVH meets either, the LVH is treated as unknown.
|
|
1025
1033
|
*/
|
|
1026
|
-
|
|
1027
|
-
|
|
1034
|
+
validateLatestHash(
|
|
1035
|
+
execResponse: LVHExecResponse,
|
|
1036
|
+
currentSlot: Slot,
|
|
1037
|
+
{strict = true}: {strict?: boolean} = {}
|
|
1038
|
+
): boolean {
|
|
1028
1039
|
// Look reverse because its highly likely node with latestValidExecHash is towards the
|
|
1029
1040
|
// the leaves of the forkchoice
|
|
1030
1041
|
//
|
|
@@ -1035,71 +1046,128 @@ export class ProtoArray {
|
|
|
1035
1046
|
// https://github.com/ChainSafe/lodestar/pull/4182#discussion_r914770167
|
|
1036
1047
|
|
|
1037
1048
|
if (execResponse.executionStatus === ExecutionStatus.Valid) {
|
|
1038
|
-
const {latestValidExecHash} = execResponse;
|
|
1049
|
+
const {latestValidExecHash, blockRoot} = execResponse;
|
|
1039
1050
|
// We use -1 for denoting not found
|
|
1040
1051
|
let latestValidHashIndex = -1;
|
|
1041
1052
|
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1053
|
+
if (blockRoot !== undefined) {
|
|
1054
|
+
latestValidHashIndex = this.getNodeIndexByRootAndBlockHash(blockRoot, latestValidExecHash) ?? -1;
|
|
1055
|
+
} else {
|
|
1056
|
+
for (let nodeIndex = this.nodes.length - 1; nodeIndex >= 0; nodeIndex--) {
|
|
1057
|
+
if (this.nodes[nodeIndex].executionPayloadBlockHash === latestValidExecHash) {
|
|
1058
|
+
latestValidHashIndex = nodeIndex;
|
|
1059
|
+
// We found the block corresponding to latestValidHashIndex, exit the loop
|
|
1060
|
+
break;
|
|
1061
|
+
}
|
|
1047
1062
|
}
|
|
1048
1063
|
}
|
|
1049
1064
|
|
|
1050
1065
|
// We are trying to be as forgiving as possible here because ideally latestValidHashIndex
|
|
1051
1066
|
// should be found in the forkchoice
|
|
1052
|
-
if (latestValidHashIndex
|
|
1053
|
-
|
|
1067
|
+
if (latestValidHashIndex < 0) {
|
|
1068
|
+
return false;
|
|
1054
1069
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
invalidateFromParentBlockHash
|
|
1073
|
-
);
|
|
1074
|
-
if (invalidateFromParentIndex === undefined) {
|
|
1075
|
-
throw Error(
|
|
1076
|
-
`Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} invalidateFromParentBlockHash=${invalidateFromParentBlockHash} in forkChoice`
|
|
1077
|
-
);
|
|
1070
|
+
const {executionStatus} = this.nodes[latestValidHashIndex];
|
|
1071
|
+
if (executionStatus === ExecutionStatus.Valid || executionStatus === ExecutionStatus.PreMerge) {
|
|
1072
|
+
return false;
|
|
1073
|
+
}
|
|
1074
|
+
if (!strict) {
|
|
1075
|
+
// The walk up flips every Syncing ancestor, a lenient verdict must not contradict an invalidated one
|
|
1076
|
+
let nodeIndex: number | undefined = latestValidHashIndex;
|
|
1077
|
+
while (nodeIndex !== undefined) {
|
|
1078
|
+
const node: ProtoNode = this.nodes[nodeIndex];
|
|
1079
|
+
if (node.executionStatus === ExecutionStatus.Valid || node.executionStatus === ExecutionStatus.PreMerge) {
|
|
1080
|
+
break;
|
|
1081
|
+
}
|
|
1082
|
+
if (node.executionStatus === ExecutionStatus.Invalid) {
|
|
1083
|
+
return false;
|
|
1084
|
+
}
|
|
1085
|
+
nodeIndex = node.parent;
|
|
1086
|
+
}
|
|
1078
1087
|
}
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1088
|
+
this.propagateValidExecutionStatusByIndex(latestValidHashIndex);
|
|
1089
|
+
return true;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// In case of invalidation, ideally:
|
|
1093
|
+
// i) Find the invalid payload
|
|
1094
|
+
// ii) Obtain a chain [LVH.child, LVH.child.child, ....., invalid_payload]
|
|
1095
|
+
// iii) Obtain a chain [Last_known_valid_node, ...., LVH]
|
|
1096
|
+
//
|
|
1097
|
+
// Mark chain iii) as Valid if LVH is non null but right now LVH can be non null without
|
|
1098
|
+
// gurranteing chain iii) to be valid: for e.g. in following scenario LVH can be returned
|
|
1099
|
+
// as any of SYNCING: SYNCING, SYNCING, SYNCING, INVALID (due to simple check)/
|
|
1100
|
+
// So we currently ignore this chain and hope eventually it gets resolved
|
|
1101
|
+
//
|
|
1102
|
+
// Mark chain ii) as Invalid if LVH is found and non null, else only invalidate invalid_payload
|
|
1103
|
+
// if its in fcU.
|
|
1104
|
+
//
|
|
1105
|
+
const {invalidateFromParentBlockRoot, invalidateFromParentBlockHash, latestValidExecHash} = execResponse;
|
|
1106
|
+
const invalidateFromParentIndex = this.getNodeIndexByRootAndBlockHash(
|
|
1107
|
+
invalidateFromParentBlockRoot,
|
|
1108
|
+
invalidateFromParentBlockHash
|
|
1109
|
+
);
|
|
1110
|
+
if (invalidateFromParentIndex === undefined) {
|
|
1111
|
+
throw Error(
|
|
1112
|
+
`Unable to find invalidateFromParentBlockRoot=${invalidateFromParentBlockRoot} invalidateFromParentBlockHash=${invalidateFromParentBlockHash} in forkChoice`
|
|
1113
|
+
);
|
|
1114
|
+
}
|
|
1115
|
+
if (!strict && this.nodes[invalidateFromParentIndex].executionStatus !== ExecutionStatus.Syncing) {
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
let latestValidHashIndex =
|
|
1120
|
+
latestValidExecHash !== null ? this.getNodeIndexFromLVH(latestValidExecHash, invalidateFromParentIndex) : null;
|
|
1121
|
+
if (latestValidHashIndex === null) {
|
|
1122
|
+
if (strict) {
|
|
1123
|
+
// The strict from node is the parent of the rejected payload and may be valid, never guess here
|
|
1098
1124
|
throw Error(`Unable to find latestValidExecHash=${latestValidExecHash} in the forkchoice`);
|
|
1099
1125
|
}
|
|
1126
|
+
latestValidHashIndex = this.getUnknownLVHIndex(invalidateFromParentIndex);
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
if (!strict) {
|
|
1130
|
+
// Never invalidate the justified block, the EMPTY variant sharing its chain, or anything at or below it in
|
|
1131
|
+
// the tree. The justified block's own payload (FULL variant) is not part of the checkpoint
|
|
1132
|
+
const justifiedIndex = this.getDefaultNodeIndex(this.justifiedRoot) ?? -1;
|
|
1133
|
+
let nodeIndex: number | undefined = invalidateFromParentIndex;
|
|
1134
|
+
while (nodeIndex !== undefined && nodeIndex > latestValidHashIndex) {
|
|
1135
|
+
const node: ProtoNode = this.nodes[nodeIndex];
|
|
1136
|
+
if (
|
|
1137
|
+
node.executionStatus !== ExecutionStatus.Syncing ||
|
|
1138
|
+
nodeIndex <= justifiedIndex ||
|
|
1139
|
+
(node.blockRoot === this.justifiedRoot && node.payloadStatus === PayloadStatus.EMPTY)
|
|
1140
|
+
) {
|
|
1141
|
+
// Conflicting LVH: fall back to the unknown LVH bound, but never cross this node
|
|
1142
|
+
latestValidHashIndex = Math.max(nodeIndex, this.getUnknownLVHIndex(invalidateFromParentIndex));
|
|
1143
|
+
break;
|
|
1144
|
+
}
|
|
1145
|
+
nodeIndex = node.parent;
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1100
1148
|
|
|
1101
|
-
|
|
1149
|
+
if (invalidateFromParentIndex <= latestValidHashIndex) {
|
|
1150
|
+
return false;
|
|
1102
1151
|
}
|
|
1152
|
+
|
|
1153
|
+
this.propagateInValidExecutionStatusByIndex(invalidateFromParentIndex, latestValidHashIndex, currentSlot);
|
|
1154
|
+
return true;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
/**
|
|
1158
|
+
* Upper bound (exclusive) for invalidating from `fromIndex` when the engine gave no usable LVH: the parent of the
|
|
1159
|
+
* last ancestor that still inherits the queried payload. Pre-gloas this is the parent of `fromIndex`. For a gloas
|
|
1160
|
+
* EMPTY or PENDING node it reaches up to the FULL node that owns the payload. -1 when the chain reaches the root.
|
|
1161
|
+
*/
|
|
1162
|
+
private getUnknownLVHIndex(fromIndex: number): number {
|
|
1163
|
+
const payloadBlockHash = this.nodes[fromIndex].executionPayloadBlockHash;
|
|
1164
|
+
let nodeIndex = fromIndex;
|
|
1165
|
+
let parentIndex = this.nodes[nodeIndex].parent;
|
|
1166
|
+
while (parentIndex !== undefined && this.nodes[parentIndex].executionPayloadBlockHash === payloadBlockHash) {
|
|
1167
|
+
nodeIndex = parentIndex;
|
|
1168
|
+
parentIndex = this.nodes[nodeIndex].parent;
|
|
1169
|
+
}
|
|
1170
|
+
return parentIndex ?? -1;
|
|
1103
1171
|
}
|
|
1104
1172
|
|
|
1105
1173
|
private propagateValidExecutionStatusByIndex(validNodeIndex: number): void {
|
|
@@ -1295,6 +1363,15 @@ export class ProtoArray {
|
|
|
1295
1363
|
|
|
1296
1364
|
// For Gloas: check if from previous slot
|
|
1297
1365
|
if (node.slot + 1 !== currentSlot) {
|
|
1366
|
+
// During the block's own slot a payload known to not satisfy the inclusion list
|
|
1367
|
+
// constraints ranks below its EMPTY variant, as it will from the next slot on
|
|
1368
|
+
if (
|
|
1369
|
+
node.slot === currentSlot &&
|
|
1370
|
+
node.payloadStatus === PayloadStatus.FULL &&
|
|
1371
|
+
this.payloadInclusionListSatisfaction.get(node.blockRoot) === false
|
|
1372
|
+
) {
|
|
1373
|
+
return PayloadStatus.PENDING;
|
|
1374
|
+
}
|
|
1298
1375
|
return node.payloadStatus;
|
|
1299
1376
|
}
|
|
1300
1377
|
|