@lodestar/fork-choice 1.36.0-dev.a2a22d66ef → 1.36.0-dev.a47974e6c6
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 +11 -9
- package/lib/forkChoice/forkChoice.d.ts.map +1 -1
- package/lib/forkChoice/forkChoice.js +54 -43
- package/lib/forkChoice/forkChoice.js.map +1 -1
- package/lib/forkChoice/interface.d.ts +2 -6
- package/lib/forkChoice/interface.d.ts.map +1 -1
- package/lib/forkChoice/interface.js.map +1 -1
- package/lib/metrics.d.ts +10 -0
- package/lib/metrics.d.ts.map +1 -1
- package/lib/metrics.js +35 -0
- package/lib/metrics.js.map +1 -1
- package/lib/protoArray/computeDeltas.d.ts +10 -2
- package/lib/protoArray/computeDeltas.d.ts.map +1 -1
- package/lib/protoArray/computeDeltas.js +51 -13
- package/lib/protoArray/computeDeltas.js.map +1 -1
- package/lib/protoArray/interface.d.ts +8 -7
- package/lib/protoArray/interface.d.ts.map +1 -1
- package/lib/protoArray/interface.js +6 -0
- package/lib/protoArray/interface.js.map +1 -1
- package/package.json +8 -8
- package/src/forkChoice/forkChoice.ts +73 -45
- package/src/forkChoice/interface.ts +2 -16
- package/src/metrics.ts +35 -0
- package/src/protoArray/computeDeltas.ts +72 -16
- package/src/protoArray/interface.ts +9 -8
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"computeDeltas.d.ts","sourceRoot":"","sources":["../../src/protoArray/computeDeltas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,0BAA0B,EAAC,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AAE/C,OAAO,
|
|
1
|
+
{"version":3,"file":"computeDeltas.d.ts","sourceRoot":"","sources":["../../src/protoArray/computeDeltas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,0BAA0B,EAAC,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAC,cAAc,EAAC,MAAM,iBAAiB,CAAC;AAE/C,OAAO,EAAkB,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAK1D,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,sBAAsB,EAAE,MAAM,CAAC;IAE/B,qBAAqB,EAAE,MAAM,CAAC;IAE9B,qBAAqB,EAAE,MAAM,CAAC;IAE9B,uBAAuB,EAAE,MAAM,CAAC;IAChC,iBAAiB,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,aAAa,EAAE,MAAM,EACrB,kBAAkB,EAAE,SAAS,EAAE,EAC/B,eAAe,EAAE,SAAS,EAAE,EAC5B,WAAW,EAAE,0BAA0B,EACvC,WAAW,EAAE,0BAA0B,EACvC,mBAAmB,EAAE,GAAG,CAAC,cAAc,CAAC,GACvC,YAAY,CAoHd"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ProtoArrayError, ProtoArrayErrorCode } from "./errors.js";
|
|
2
|
+
import { NULL_VOTE_INDEX } from "./interface.js";
|
|
2
3
|
// reuse arrays to avoid memory reallocation and gc
|
|
3
4
|
const deltas = new Array();
|
|
4
5
|
/**
|
|
@@ -10,21 +11,37 @@ const deltas = new Array();
|
|
|
10
11
|
*
|
|
11
12
|
* - If a value in `indices` is greater to or equal to `indices.length`.
|
|
12
13
|
*/
|
|
13
|
-
export function computeDeltas(numProtoNodes,
|
|
14
|
+
export function computeDeltas(numProtoNodes, voteCurrentIndices, voteNextIndices, oldBalances, newBalances, equivocatingIndices) {
|
|
15
|
+
if (voteCurrentIndices.length !== voteNextIndices.length) {
|
|
16
|
+
throw new Error(`voteCurrentIndices and voteNextIndices must have the same length: ${voteCurrentIndices.length} !== ${voteNextIndices.length}`);
|
|
17
|
+
}
|
|
18
|
+
if (numProtoNodes >= NULL_VOTE_INDEX) {
|
|
19
|
+
// this never happen in practice, but we check to be safe
|
|
20
|
+
throw new Error(`numProtoNodes must be less than NULL_VOTE_INDEX: ${numProtoNodes} >= ${NULL_VOTE_INDEX}`);
|
|
21
|
+
}
|
|
14
22
|
deltas.length = numProtoNodes;
|
|
15
23
|
deltas.fill(0);
|
|
16
24
|
// avoid creating new variables in the loop to potentially reduce GC pressure
|
|
17
25
|
let oldBalance, newBalance;
|
|
18
26
|
let currentIndex, nextIndex;
|
|
19
|
-
|
|
20
|
-
|
|
27
|
+
// sort equivocating indices to avoid Set.has() in the loop
|
|
28
|
+
const equivocatingArray = Array.from(equivocatingIndices).sort((a, b) => a - b);
|
|
29
|
+
let equivocatingIndex = 0;
|
|
30
|
+
let equivocatingValidatorIndex = equivocatingArray[equivocatingIndex];
|
|
31
|
+
const equivocatingValidators = equivocatingIndices.size;
|
|
32
|
+
let oldInactiveValidators = 0;
|
|
33
|
+
let newInactiveValidators = 0;
|
|
34
|
+
let unchangedVoteValidators = 0;
|
|
35
|
+
let newVoteValidators = 0;
|
|
36
|
+
for (let vIndex = 0; vIndex < voteNextIndices.length; vIndex++) {
|
|
37
|
+
currentIndex = voteCurrentIndices[vIndex];
|
|
38
|
+
nextIndex = voteNextIndices[vIndex];
|
|
21
39
|
// There is no need to create a score change if the validator has never voted or both of their
|
|
22
40
|
// votes are for the zero hash (genesis block)
|
|
23
|
-
if (
|
|
41
|
+
if (currentIndex === NULL_VOTE_INDEX && nextIndex === NULL_VOTE_INDEX) {
|
|
42
|
+
oldInactiveValidators++;
|
|
24
43
|
continue;
|
|
25
44
|
}
|
|
26
|
-
currentIndex = vote.currentIndex;
|
|
27
|
-
nextIndex = vote.nextIndex;
|
|
28
45
|
// IF the validator was not included in the _old_ balances (i.e. it did not exist yet)
|
|
29
46
|
// then say its balance was 0
|
|
30
47
|
oldBalance = oldBalances[vIndex] ?? 0;
|
|
@@ -34,9 +51,9 @@ export function computeDeltas(numProtoNodes, votes, oldBalances, newBalances, eq
|
|
|
34
51
|
// state to a new state with a higher epoch that is on a different fork because that fork may have
|
|
35
52
|
// on-boarded fewer validators than the prior fork.
|
|
36
53
|
newBalance = newBalances === oldBalances ? oldBalance : (newBalances[vIndex] ?? 0);
|
|
37
|
-
if (
|
|
54
|
+
if (vIndex === equivocatingValidatorIndex) {
|
|
38
55
|
// this function could be called multiple times but we only want to process slashing validator for 1 time
|
|
39
|
-
if (currentIndex !==
|
|
56
|
+
if (currentIndex !== NULL_VOTE_INDEX) {
|
|
40
57
|
if (currentIndex >= numProtoNodes) {
|
|
41
58
|
throw new ProtoArrayError({
|
|
42
59
|
code: ProtoArrayErrorCode.INVALID_NODE_DELTA,
|
|
@@ -45,13 +62,19 @@ export function computeDeltas(numProtoNodes, votes, oldBalances, newBalances, eq
|
|
|
45
62
|
}
|
|
46
63
|
deltas[currentIndex] -= oldBalance;
|
|
47
64
|
}
|
|
48
|
-
|
|
65
|
+
voteCurrentIndices[vIndex] = NULL_VOTE_INDEX;
|
|
66
|
+
equivocatingIndex++;
|
|
67
|
+
equivocatingValidatorIndex = equivocatingArray[equivocatingIndex];
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (oldBalance === 0 && newBalance === 0) {
|
|
71
|
+
newInactiveValidators++;
|
|
49
72
|
continue;
|
|
50
73
|
}
|
|
51
74
|
if (currentIndex !== nextIndex || oldBalance !== newBalance) {
|
|
52
75
|
// We ignore the vote if it is not known in `indices .
|
|
53
76
|
// We assume that it is outside of our tree (ie: pre-finalization) and therefore not interesting
|
|
54
|
-
if (currentIndex !==
|
|
77
|
+
if (currentIndex !== NULL_VOTE_INDEX) {
|
|
55
78
|
if (currentIndex >= numProtoNodes) {
|
|
56
79
|
throw new ProtoArrayError({
|
|
57
80
|
code: ProtoArrayErrorCode.INVALID_NODE_DELTA,
|
|
@@ -62,7 +85,7 @@ export function computeDeltas(numProtoNodes, votes, oldBalances, newBalances, eq
|
|
|
62
85
|
}
|
|
63
86
|
// We ignore the vote if it is not known in `indices .
|
|
64
87
|
// We assume that it is outside of our tree (ie: pre-finalization) and therefore not interesting
|
|
65
|
-
if (nextIndex !==
|
|
88
|
+
if (nextIndex !== NULL_VOTE_INDEX) {
|
|
66
89
|
if (nextIndex >= numProtoNodes) {
|
|
67
90
|
throw new ProtoArrayError({
|
|
68
91
|
code: ProtoArrayErrorCode.INVALID_NODE_DELTA,
|
|
@@ -71,9 +94,24 @@ export function computeDeltas(numProtoNodes, votes, oldBalances, newBalances, eq
|
|
|
71
94
|
}
|
|
72
95
|
deltas[nextIndex] += newBalance;
|
|
73
96
|
}
|
|
97
|
+
voteCurrentIndices[vIndex] = nextIndex;
|
|
98
|
+
newVoteValidators++;
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
unchangedVoteValidators++;
|
|
74
102
|
}
|
|
75
|
-
|
|
103
|
+
} // end validator loop
|
|
104
|
+
if (deltas.length !== numProtoNodes) {
|
|
105
|
+
// deltas array could be growed in the loop, especially if we mistakenly set the [NULL_VOTE_INDEX] to it , just to be safe
|
|
106
|
+
throw new Error(`deltas length mismatch: expected ${numProtoNodes}, got ${deltas.length}`);
|
|
76
107
|
}
|
|
77
|
-
return
|
|
108
|
+
return {
|
|
109
|
+
deltas,
|
|
110
|
+
equivocatingValidators,
|
|
111
|
+
oldInactiveValidators,
|
|
112
|
+
newInactiveValidators,
|
|
113
|
+
unchangedVoteValidators,
|
|
114
|
+
newVoteValidators,
|
|
115
|
+
};
|
|
78
116
|
}
|
|
79
117
|
//# sourceMappingURL=computeDeltas.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"computeDeltas.js","sourceRoot":"","sources":["../../src/protoArray/computeDeltas.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,eAAe,EAAE,mBAAmB,EAAC,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"computeDeltas.js","sourceRoot":"","sources":["../../src/protoArray/computeDeltas.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,14 +2,15 @@ import { DataAvailabilityStatus } from "@lodestar/state-transition";
|
|
|
2
2
|
import { Epoch, RootHex, Slot, UintNum64 } from "@lodestar/types";
|
|
3
3
|
export declare const HEX_ZERO_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* The null vote index indicates that a validator votes to a non-existent block. This usually happens when
|
|
6
|
+
* we prune the proto array and the validator's latest message is in the pruned part.
|
|
7
|
+
* The number of proto nodes will never exceed this value because it represents (0xffffffff / 365 / 24 / 60 / 5), ie > 1634 years of non-finalized network.
|
|
7
8
|
*/
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
export declare const NULL_VOTE_INDEX = 4294967295;
|
|
10
|
+
/**
|
|
11
|
+
* A vote index is a non-negative integer from 0 to NULL_VOTE_INDEX inclusive, and it will never be undefined.
|
|
12
|
+
*/
|
|
13
|
+
export type VoteIndex = number;
|
|
13
14
|
export declare enum ExecutionStatus {
|
|
14
15
|
Valid = "Valid",
|
|
15
16
|
Syncing = "Syncing",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/protoArray/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,sBAAsB,EAAC,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAIhE,eAAO,MAAM,aAAa,uEAAuE,CAAC;AAElG
|
|
1
|
+
{"version":3,"file":"interface.d.ts","sourceRoot":"","sources":["../../src/protoArray/interface.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,sBAAsB,EAAC,MAAM,4BAA4B,CAAC;AAClE,OAAO,EAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAIhE,eAAO,MAAM,aAAa,uEAAuE,CAAC;AAElG;;;;GAIG;AACH,eAAO,MAAM,eAAe,aAAa,CAAC;AAE1C;;GAEG;AACH,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAE/B,oBAAY,eAAe;IACzB,KAAK,UAAU;IACf,OAAO,YAAY;IACnB,QAAQ,aAAa;IACrB,OAAO,YAAY;CACpB;AAED,MAAM,MAAM,gBAAgB,GAAG;IAC7B,eAAe,EAAE,eAAe,CAAC,KAAK,CAAC;IACvC,mBAAmB,EAAE,OAAO,CAAC;CAC9B,CAAC;AACF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,eAAe,EAAE,eAAe,CAAC,OAAO,CAAC;IACzC,mBAAmB,EAAE,OAAO,GAAG,IAAI,CAAC;IACpC,6BAA6B,EAAE,OAAO,CAAC;CACxC,CAAC;AACF,MAAM,MAAM,eAAe,GAAG,gBAAgB,GAAG,kBAAkB,CAAC;AAEpE,MAAM,MAAM,yBAAyB,GAAG,OAAO,CAAC,eAAe,EAAE,eAAe,CAAC,OAAO,CAAC,CAAC;AAE1F,MAAM,MAAM,cAAc,GACtB;IACE,yBAAyB,EAAE,OAAO,CAAC;IACnC,sBAAsB,EAAE,SAAS,CAAC;IAClC,eAAe,EAAE,OAAO,CAAC,eAAe,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IACpE,sBAAsB,EAAE,sBAAsB,CAAC;CAChD,GACD;IACE,yBAAyB,EAAE,IAAI,CAAC;IAChC,eAAe,EAAE,eAAe,CAAC,QAAQ,CAAC;IAC1C,sBAAsB,EAAE,sBAAsB,CAAC,OAAO,CAAC;CACxD,CAAC;AAEN;;;;GAIG;AAEH,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG;IACxC;;;;OAIG;IACH,IAAI,EAAE,IAAI,CAAC;IACX,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,UAAU,EAAE,OAAO,CAAC;IAEpB,cAAc,EAAE,KAAK,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,cAAc,EAAE,KAAK,CAAC;IACtB,aAAa,EAAE,OAAO,CAAC;IACvB,wBAAwB,EAAE,KAAK,CAAC;IAChC,uBAAuB,EAAE,OAAO,CAAC;IACjC,wBAAwB,EAAE,KAAK,CAAC;IAChC,uBAAuB,EAAE,OAAO,CAAC;IAGjC,UAAU,EAAE,OAAO,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,SAAS,GAAG,UAAU,GAAG;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB,CAAC"}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// RootHex is a root as a hex string
|
|
2
2
|
// Used for lightweight and easy comparison
|
|
3
3
|
export const HEX_ZERO_HASH = "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
4
|
+
/**
|
|
5
|
+
* The null vote index indicates that a validator votes to a non-existent block. This usually happens when
|
|
6
|
+
* we prune the proto array and the validator's latest message is in the pruned part.
|
|
7
|
+
* The number of proto nodes will never exceed this value because it represents (0xffffffff / 365 / 24 / 60 / 5), ie > 1634 years of non-finalized network.
|
|
8
|
+
*/
|
|
9
|
+
export const NULL_VOTE_INDEX = 0xffffffff;
|
|
4
10
|
export var ExecutionStatus;
|
|
5
11
|
(function (ExecutionStatus) {
|
|
6
12
|
ExecutionStatus["Valid"] = "Valid";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/protoArray/interface.ts"],"names":[],"mappings":"AAGA,oCAAoC;AACpC,2CAA2C;AAC3C,MAAM,CAAC,MAAM,aAAa,GAAG,oEAAoE,CAAC;
|
|
1
|
+
{"version":3,"file":"interface.js","sourceRoot":"","sources":["../../src/protoArray/interface.ts"],"names":[],"mappings":"AAGA,oCAAoC;AACpC,2CAA2C;AAC3C,MAAM,CAAC,MAAM,aAAa,GAAG,oEAAoE,CAAC;AAElG;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;AAO1C,MAAM,CAAN,IAAY,eAKX;AALD,WAAY,eAAe;IACzB,kCAAe,CAAA;IACf,sCAAmB,CAAA;IACnB,wCAAqB,CAAA;IACrB,sCAAmB,CAAA;AACrB,CAAC,EALW,eAAe,KAAf,eAAe,QAK1B"}
|
package/package.json
CHANGED
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
"bugs": {
|
|
12
12
|
"url": "https://github.com/ChainSafe/lodestar/issues"
|
|
13
13
|
},
|
|
14
|
-
"version": "1.36.0-dev.
|
|
14
|
+
"version": "1.36.0-dev.a47974e6c6",
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
17
17
|
".": {
|
|
18
18
|
"bun": "./src/index.ts",
|
|
19
|
+
"types": "./lib/index.d.ts",
|
|
19
20
|
"import": "./lib/index.js"
|
|
20
21
|
}
|
|
21
22
|
},
|
|
22
|
-
"types": "./lib/index.d.ts",
|
|
23
23
|
"files": [
|
|
24
24
|
"src",
|
|
25
25
|
"lib",
|
|
@@ -40,11 +40,11 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@chainsafe/ssz": "^1.2.2",
|
|
43
|
-
"@lodestar/config": "1.36.0-dev.
|
|
44
|
-
"@lodestar/params": "1.36.0-dev.
|
|
45
|
-
"@lodestar/state-transition": "1.36.0-dev.
|
|
46
|
-
"@lodestar/types": "1.36.0-dev.
|
|
47
|
-
"@lodestar/utils": "1.36.0-dev.
|
|
43
|
+
"@lodestar/config": "1.36.0-dev.a47974e6c6",
|
|
44
|
+
"@lodestar/params": "1.36.0-dev.a47974e6c6",
|
|
45
|
+
"@lodestar/state-transition": "1.36.0-dev.a47974e6c6",
|
|
46
|
+
"@lodestar/types": "1.36.0-dev.a47974e6c6",
|
|
47
|
+
"@lodestar/utils": "1.36.0-dev.a47974e6c6"
|
|
48
48
|
},
|
|
49
49
|
"keywords": [
|
|
50
50
|
"ethereum",
|
|
@@ -52,5 +52,5 @@
|
|
|
52
52
|
"beacon",
|
|
53
53
|
"blockchain"
|
|
54
54
|
],
|
|
55
|
-
"gitHead": "
|
|
55
|
+
"gitHead": "6b3851e30f785d87ec8223fbabee5ff6e843309e"
|
|
56
56
|
}
|
|
@@ -36,9 +36,10 @@ import {
|
|
|
36
36
|
HEX_ZERO_HASH,
|
|
37
37
|
LVHExecResponse,
|
|
38
38
|
MaybeValidExecutionStatus,
|
|
39
|
+
NULL_VOTE_INDEX,
|
|
39
40
|
ProtoBlock,
|
|
40
41
|
ProtoNode,
|
|
41
|
-
|
|
42
|
+
VoteIndex,
|
|
42
43
|
} from "../protoArray/interface.js";
|
|
43
44
|
import {ProtoArray} from "../protoArray/protoArray.js";
|
|
44
45
|
import {ForkChoiceError, ForkChoiceErrorCode, InvalidAttestationCode, InvalidBlockCode} from "./errors.js";
|
|
@@ -47,7 +48,6 @@ import {
|
|
|
47
48
|
AncestorStatus,
|
|
48
49
|
EpochDifference,
|
|
49
50
|
IForkChoice,
|
|
50
|
-
LatestMessage,
|
|
51
51
|
NotReorgedReason,
|
|
52
52
|
PowBlockHex,
|
|
53
53
|
ShouldOverrideForkChoiceUpdateResult,
|
|
@@ -71,6 +71,9 @@ export type UpdateAndGetHeadOpt =
|
|
|
71
71
|
| {mode: UpdateHeadOpt.GetProposerHead; secFromSlot: number; slot: Slot}
|
|
72
72
|
| {mode: UpdateHeadOpt.GetPredictedProposerHead; secFromSlot: number; slot: Slot};
|
|
73
73
|
|
|
74
|
+
// the initial vote epoch for all validators
|
|
75
|
+
const INIT_VOTE_EPOCH: Epoch = 0;
|
|
76
|
+
|
|
74
77
|
/**
|
|
75
78
|
* Provides an implementation of "Ethereum Consensus -- Beacon Chain Fork Choice":
|
|
76
79
|
*
|
|
@@ -91,11 +94,12 @@ export type UpdateAndGetHeadOpt =
|
|
|
91
94
|
export class ForkChoice implements IForkChoice {
|
|
92
95
|
irrecoverableError?: Error;
|
|
93
96
|
/**
|
|
94
|
-
* Votes currently tracked in the protoArray
|
|
95
|
-
*
|
|
96
|
-
* Each vote contains the latest message and previous message
|
|
97
|
+
* Votes currently tracked in the protoArray. Instead of tracking a VoteTracker of currentIndex, nextIndex and epoch,
|
|
98
|
+
* we decompose the struct and track them in 3 separate arrays for performance reason.
|
|
97
99
|
*/
|
|
98
|
-
private readonly
|
|
100
|
+
private readonly voteCurrentIndices: VoteIndex[];
|
|
101
|
+
private readonly voteNextIndices: VoteIndex[];
|
|
102
|
+
private readonly voteNextEpochs: Epoch[];
|
|
99
103
|
|
|
100
104
|
/**
|
|
101
105
|
* Attestations that arrived at the current slot and must be queued for later processing.
|
|
@@ -138,15 +142,22 @@ export class ForkChoice implements IForkChoice {
|
|
|
138
142
|
private readonly fcStore: IForkChoiceStore,
|
|
139
143
|
/** The underlying representation of the block DAG. */
|
|
140
144
|
private readonly protoArray: ProtoArray,
|
|
145
|
+
validatorCount: number,
|
|
141
146
|
readonly metrics: ForkChoiceMetrics | null,
|
|
142
147
|
private readonly opts?: ForkChoiceOpts,
|
|
143
148
|
private readonly logger?: Logger
|
|
144
149
|
) {
|
|
150
|
+
// initialize votes, they will grow in addLatestMessage() function below
|
|
151
|
+
this.voteCurrentIndices = new Array(validatorCount).fill(NULL_VOTE_INDEX);
|
|
152
|
+
this.voteNextIndices = new Array(validatorCount).fill(NULL_VOTE_INDEX);
|
|
153
|
+
// when compute deltas, we ignore epoch if voteNextIndex is NULL_VOTE_INDEX anyway
|
|
154
|
+
this.voteNextEpochs = new Array(validatorCount).fill(INIT_VOTE_EPOCH);
|
|
155
|
+
|
|
145
156
|
this.head = this.updateHead();
|
|
146
157
|
this.balances = this.fcStore.justified.balances;
|
|
147
158
|
|
|
148
159
|
metrics?.forkChoice.votes.addCollect(() => {
|
|
149
|
-
metrics.forkChoice.votes.set(this.
|
|
160
|
+
metrics.forkChoice.votes.set(this.voteNextEpochs.length);
|
|
150
161
|
metrics.forkChoice.queuedAttestations.set(this.queuedAttestationsPreviousSlot);
|
|
151
162
|
metrics.forkChoice.validatedAttestationDatas.set(this.validatedAttestationDatas.size);
|
|
152
163
|
metrics.forkChoice.balancesLength.set(this.balances.length);
|
|
@@ -443,13 +454,34 @@ export class ForkChoice implements IForkChoice {
|
|
|
443
454
|
// Check if scores need to be calculated/updated
|
|
444
455
|
const oldBalances = this.balances;
|
|
445
456
|
const newBalances = this.fcStore.justified.balances;
|
|
446
|
-
const
|
|
457
|
+
const computeDeltasMetrics = this.metrics?.forkChoice.computeDeltas;
|
|
458
|
+
|
|
459
|
+
const timer = computeDeltasMetrics?.duration.startTimer();
|
|
460
|
+
const {
|
|
461
|
+
deltas,
|
|
462
|
+
equivocatingValidators,
|
|
463
|
+
oldInactiveValidators,
|
|
464
|
+
newInactiveValidators,
|
|
465
|
+
unchangedVoteValidators,
|
|
466
|
+
newVoteValidators,
|
|
467
|
+
} = computeDeltas(
|
|
447
468
|
this.protoArray.nodes.length,
|
|
448
|
-
this.
|
|
469
|
+
this.voteCurrentIndices,
|
|
470
|
+
this.voteNextIndices,
|
|
449
471
|
oldBalances,
|
|
450
472
|
newBalances,
|
|
451
473
|
this.fcStore.equivocatingIndices
|
|
452
474
|
);
|
|
475
|
+
timer?.();
|
|
476
|
+
|
|
477
|
+
computeDeltasMetrics?.deltasCount.set(deltas.length);
|
|
478
|
+
computeDeltasMetrics?.zeroDeltasCount.set(deltas.filter((d) => d === 0).length);
|
|
479
|
+
computeDeltasMetrics?.equivocatingValidators.set(equivocatingValidators);
|
|
480
|
+
computeDeltasMetrics?.oldInactiveValidators.set(oldInactiveValidators);
|
|
481
|
+
computeDeltasMetrics?.newInactiveValidators.set(newInactiveValidators);
|
|
482
|
+
computeDeltasMetrics?.unchangedVoteValidators.set(unchangedVoteValidators);
|
|
483
|
+
computeDeltasMetrics?.newVoteValidators.set(newVoteValidators);
|
|
484
|
+
|
|
453
485
|
this.balances = newBalances;
|
|
454
486
|
/**
|
|
455
487
|
* The structure in line with deltas to propagate boost up the branch
|
|
@@ -819,17 +851,6 @@ export class ForkChoice implements IForkChoice {
|
|
|
819
851
|
}
|
|
820
852
|
}
|
|
821
853
|
|
|
822
|
-
getLatestMessage(validatorIndex: ValidatorIndex): LatestMessage | undefined {
|
|
823
|
-
const vote = this.votes[validatorIndex];
|
|
824
|
-
if (vote === undefined) {
|
|
825
|
-
return undefined;
|
|
826
|
-
}
|
|
827
|
-
return {
|
|
828
|
-
epoch: vote.nextEpoch,
|
|
829
|
-
root: vote.nextIndex === null ? HEX_ZERO_HASH : this.protoArray.nodes[vote.nextIndex].blockRoot,
|
|
830
|
-
};
|
|
831
|
-
}
|
|
832
|
-
|
|
833
854
|
/**
|
|
834
855
|
* Call `onTick` for all slots between `fcStore.getCurrentSlot()` and the provided `currentSlot`.
|
|
835
856
|
* This should only be called once per slot because:
|
|
@@ -931,6 +952,11 @@ export class ForkChoice implements IForkChoice {
|
|
|
931
952
|
return block;
|
|
932
953
|
}
|
|
933
954
|
|
|
955
|
+
getFinalizedCheckpointSlot(): Slot {
|
|
956
|
+
const finalizedEpoch = this.fcStore.finalizedCheckpoint.epoch;
|
|
957
|
+
return computeStartSlotAtEpoch(finalizedEpoch);
|
|
958
|
+
}
|
|
959
|
+
|
|
934
960
|
/**
|
|
935
961
|
* Returns true if the `descendantRoot` has an ancestor with `ancestorRoot`.
|
|
936
962
|
*
|
|
@@ -947,28 +973,26 @@ export class ForkChoice implements IForkChoice {
|
|
|
947
973
|
prune(finalizedRoot: RootHex): ProtoBlock[] {
|
|
948
974
|
const prunedNodes = this.protoArray.maybePrune(finalizedRoot);
|
|
949
975
|
const prunedCount = prunedNodes.length;
|
|
950
|
-
for (let i = 0; i < this.
|
|
951
|
-
const
|
|
952
|
-
// validator has never voted
|
|
953
|
-
if (vote === undefined) {
|
|
954
|
-
continue;
|
|
955
|
-
}
|
|
976
|
+
for (let i = 0; i < this.voteNextEpochs.length; i++) {
|
|
977
|
+
const currentIndex = this.voteCurrentIndices[i];
|
|
956
978
|
|
|
957
|
-
if (
|
|
958
|
-
if (
|
|
959
|
-
|
|
979
|
+
if (currentIndex !== NULL_VOTE_INDEX) {
|
|
980
|
+
if (currentIndex >= prunedCount) {
|
|
981
|
+
this.voteCurrentIndices[i] = currentIndex - prunedCount;
|
|
960
982
|
} else {
|
|
961
983
|
// the vote was for a pruned proto node
|
|
962
|
-
|
|
984
|
+
this.voteCurrentIndices[i] = NULL_VOTE_INDEX;
|
|
963
985
|
}
|
|
964
986
|
}
|
|
965
987
|
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
988
|
+
const nextIndex = this.voteNextIndices[i];
|
|
989
|
+
|
|
990
|
+
if (nextIndex !== NULL_VOTE_INDEX) {
|
|
991
|
+
if (nextIndex >= prunedCount) {
|
|
992
|
+
this.voteNextIndices[i] = nextIndex - prunedCount;
|
|
969
993
|
} else {
|
|
970
994
|
// the vote was for a pruned proto node
|
|
971
|
-
|
|
995
|
+
this.voteNextIndices[i] = NULL_VOTE_INDEX;
|
|
972
996
|
}
|
|
973
997
|
}
|
|
974
998
|
}
|
|
@@ -1435,25 +1459,29 @@ export class ForkChoice implements IForkChoice {
|
|
|
1435
1459
|
}
|
|
1436
1460
|
|
|
1437
1461
|
/**
|
|
1438
|
-
* Add a validator's latest message to the tracked votes
|
|
1462
|
+
* Add a validator's latest message to the tracked votes.
|
|
1463
|
+
* Always sync voteCurrentIndices and voteNextIndices so that it'll not throw in computeDeltas()
|
|
1439
1464
|
*/
|
|
1440
1465
|
private addLatestMessage(validatorIndex: ValidatorIndex, nextEpoch: Epoch, nextRoot: RootHex): void {
|
|
1441
|
-
const vote = this.votes[validatorIndex];
|
|
1442
1466
|
// should not happen, attestation is validated before this step
|
|
1443
1467
|
const nextIndex = this.protoArray.indices.get(nextRoot);
|
|
1444
1468
|
if (nextIndex === undefined) {
|
|
1445
1469
|
throw new Error(`Could not find proto index for nextRoot ${nextRoot}`);
|
|
1446
1470
|
}
|
|
1447
1471
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1472
|
+
// ensure there is no undefined entries in Votes arrays
|
|
1473
|
+
if (this.voteNextEpochs.length < validatorIndex + 1) {
|
|
1474
|
+
for (let i = this.voteNextEpochs.length; i < validatorIndex + 1; i++) {
|
|
1475
|
+
this.voteNextEpochs[i] = INIT_VOTE_EPOCH;
|
|
1476
|
+
this.voteCurrentIndices[i] = this.voteNextIndices[i] = NULL_VOTE_INDEX;
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
const existingNextEpoch = this.voteNextEpochs[validatorIndex];
|
|
1481
|
+
if (existingNextEpoch === INIT_VOTE_EPOCH || nextEpoch > existingNextEpoch) {
|
|
1482
|
+
// nextIndex is transfered to currentIndex in computeDeltas()
|
|
1483
|
+
this.voteNextIndices[validatorIndex] = nextIndex;
|
|
1484
|
+
this.voteNextEpochs[validatorIndex] = nextEpoch;
|
|
1457
1485
|
}
|
|
1458
1486
|
// else its an old vote, don't count it
|
|
1459
1487
|
}
|
|
@@ -3,16 +3,7 @@ import {
|
|
|
3
3
|
DataAvailabilityStatus,
|
|
4
4
|
EffectiveBalanceIncrements,
|
|
5
5
|
} from "@lodestar/state-transition";
|
|
6
|
-
import {
|
|
7
|
-
AttesterSlashing,
|
|
8
|
-
BeaconBlock,
|
|
9
|
-
Epoch,
|
|
10
|
-
IndexedAttestation,
|
|
11
|
-
Root,
|
|
12
|
-
RootHex,
|
|
13
|
-
Slot,
|
|
14
|
-
ValidatorIndex,
|
|
15
|
-
} from "@lodestar/types";
|
|
6
|
+
import {AttesterSlashing, BeaconBlock, Epoch, IndexedAttestation, Root, RootHex, Slot} from "@lodestar/types";
|
|
16
7
|
import {LVHExecResponse, MaybeValidExecutionStatus, ProtoBlock, ProtoNode} from "../protoArray/interface.js";
|
|
17
8
|
import {UpdateAndGetHeadOpt} from "./forkChoice.js";
|
|
18
9
|
import {CheckpointWithHex} from "./store.js";
|
|
@@ -178,7 +169,6 @@ export interface IForkChoice {
|
|
|
178
169
|
* https://github.com/ethereum/consensus-specs/blob/v1.2.0-rc.3/specs/phase0/fork-choice.md#on_attester_slashing
|
|
179
170
|
*/
|
|
180
171
|
onAttesterSlashing(slashing: AttesterSlashing): void;
|
|
181
|
-
getLatestMessage(validatorIndex: ValidatorIndex): LatestMessage | undefined;
|
|
182
172
|
/**
|
|
183
173
|
* Call `onTick` for all slots between `fcStore.getCurrentSlot()` and the provided `currentSlot`.
|
|
184
174
|
*/
|
|
@@ -206,6 +196,7 @@ export interface IForkChoice {
|
|
|
206
196
|
getBlockHex(blockRoot: RootHex): ProtoBlock | null;
|
|
207
197
|
getFinalizedBlock(): ProtoBlock;
|
|
208
198
|
getJustifiedBlock(): ProtoBlock;
|
|
199
|
+
getFinalizedCheckpointSlot(): Slot;
|
|
209
200
|
/**
|
|
210
201
|
* Returns true if the `descendantRoot` has an ancestor with `ancestorRoot`.
|
|
211
202
|
*
|
|
@@ -262,8 +253,3 @@ export type PowBlockHex = {
|
|
|
262
253
|
parentHash: RootHex;
|
|
263
254
|
totalDifficulty: bigint;
|
|
264
255
|
};
|
|
265
|
-
|
|
266
|
-
export type LatestMessage = {
|
|
267
|
-
epoch: Epoch;
|
|
268
|
-
root: RootHex;
|
|
269
|
-
};
|
package/src/metrics.ts
CHANGED
|
@@ -66,6 +66,41 @@ export function getForkChoiceMetrics(register: MetricsRegisterExtra) {
|
|
|
66
66
|
help: "Reason why the current head is not re-orged out",
|
|
67
67
|
labelNames: ["reason"],
|
|
68
68
|
}),
|
|
69
|
+
computeDeltas: {
|
|
70
|
+
duration: register.histogram({
|
|
71
|
+
name: "beacon_fork_choice_compute_deltas_seconds",
|
|
72
|
+
help: "Time taken to compute deltas in seconds",
|
|
73
|
+
buckets: [0.01, 0.05, 0.1, 0.2],
|
|
74
|
+
}),
|
|
75
|
+
deltasCount: register.gauge({
|
|
76
|
+
name: "beacon_fork_choice_compute_deltas_deltas_count",
|
|
77
|
+
help: "Count of deltas computed",
|
|
78
|
+
}),
|
|
79
|
+
zeroDeltasCount: register.gauge({
|
|
80
|
+
name: "beacon_fork_choice_compute_deltas_zero_deltas_count",
|
|
81
|
+
help: "Count of zero deltas processed",
|
|
82
|
+
}),
|
|
83
|
+
equivocatingValidators: register.gauge({
|
|
84
|
+
name: "beacon_fork_choice_compute_deltas_equivocating_validators_count",
|
|
85
|
+
help: "Count of equivocating validators processed",
|
|
86
|
+
}),
|
|
87
|
+
oldInactiveValidators: register.gauge({
|
|
88
|
+
name: "beacon_fork_choice_compute_deltas_old_inactive_validators_count",
|
|
89
|
+
help: "Count of old inactive validators processed",
|
|
90
|
+
}),
|
|
91
|
+
newInactiveValidators: register.gauge({
|
|
92
|
+
name: "beacon_fork_choice_compute_deltas_new_inactive_validators_count",
|
|
93
|
+
help: "Count of new inactive validators processed",
|
|
94
|
+
}),
|
|
95
|
+
unchangedVoteValidators: register.gauge({
|
|
96
|
+
name: "beacon_fork_choice_compute_deltas_unchanged_vote_validators_count",
|
|
97
|
+
help: "Count of unchanged vote validators processed",
|
|
98
|
+
}),
|
|
99
|
+
newVoteValidators: register.gauge({
|
|
100
|
+
name: "beacon_fork_choice_compute_deltas_new_vote_validators_count",
|
|
101
|
+
help: "Count of new vote validators processed",
|
|
102
|
+
}),
|
|
103
|
+
},
|
|
69
104
|
},
|
|
70
105
|
};
|
|
71
106
|
}
|