@lodestar/beacon-node 1.46.0-dev.44e482c861 → 1.46.0-dev.47415586e0
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/api/impl/beacon/blocks/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/blocks/index.js +1 -1
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +3 -3
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.d.ts.map +1 -1
- package/lib/chain/blocks/importExecutionPayload.js +3 -4
- package/lib/chain/blocks/importExecutionPayload.js.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.d.ts.map +1 -1
- package/lib/chain/blocks/utils/chainSegment.js +31 -25
- package/lib/chain/blocks/utils/chainSegment.js.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +11 -3
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/chain.d.ts +5 -0
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +55 -0
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +6 -0
- package/lib/chain/emitter.d.ts.map +1 -1
- package/lib/chain/emitter.js +5 -0
- package/lib/chain/emitter.js.map +1 -1
- package/lib/chain/interface.d.ts +2 -0
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/options.d.ts +2 -0
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +1 -0
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +74 -6
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +6 -6
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/seenCache/seenBlockProposers.d.ts +8 -3
- package/lib/chain/seenCache/seenBlockProposers.d.ts.map +1 -1
- package/lib/chain/seenCache/seenBlockProposers.js +25 -11
- package/lib/chain/seenCache/seenBlockProposers.js.map +1 -1
- package/lib/chain/validation/block.d.ts.map +1 -1
- package/lib/chain/validation/block.js +8 -5
- package/lib/chain/validation/block.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +14 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +38 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/network/network.d.ts +1 -0
- package/lib/network/network.d.ts.map +1 -1
- package/lib/network/network.js +11 -1
- package/lib/network/network.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +8 -0
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/package.json +14 -14
- package/src/api/impl/beacon/blocks/index.ts +6 -1
- package/src/chain/blocks/importBlock.ts +3 -2
- package/src/chain/blocks/importExecutionPayload.ts +8 -4
- package/src/chain/blocks/utils/chainSegment.ts +35 -28
- package/src/chain/blocks/verifyBlock.ts +16 -6
- package/src/chain/chain.ts +60 -0
- package/src/chain/emitter.ts +7 -0
- package/src/chain/interface.ts +3 -0
- package/src/chain/options.ts +3 -0
- package/src/chain/prepareNextSlot.ts +82 -6
- package/src/chain/produceBlock/produceBlockBody.ts +11 -6
- package/src/chain/seenCache/seenBlockProposers.ts +40 -14
- package/src/chain/validation/block.ts +8 -4
- package/src/metrics/metrics/lodestar.ts +39 -0
- package/src/network/network.ts +16 -1
- package/src/network/processor/gossipHandlers.ts +7 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {routes} from "@lodestar/api";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
|
-
import {getSafeExecutionBlockHash} from "@lodestar/fork-choice";
|
|
3
|
+
import {getFinalizedExecutionBlockHash, getSafeExecutionBlockHash} from "@lodestar/fork-choice";
|
|
4
4
|
import {
|
|
5
5
|
ForkPostBellatrix,
|
|
6
6
|
ForkSeq,
|
|
@@ -10,17 +10,20 @@ import {
|
|
|
10
10
|
isForkPostGloas,
|
|
11
11
|
} from "@lodestar/params";
|
|
12
12
|
import {
|
|
13
|
+
GLOAS_PREVERIFY_WINDOW_EPOCHS,
|
|
13
14
|
IBeaconStateView,
|
|
14
15
|
IBeaconStateViewBellatrix,
|
|
16
|
+
MAX_BUILDER_DEPOSITS_PER_SLOT,
|
|
15
17
|
StateHashTreeRootSource,
|
|
16
18
|
computeEpochAtSlot,
|
|
17
19
|
computeTimeAtSlot,
|
|
18
20
|
isStatePostBellatrix,
|
|
21
|
+
isStatePostFulu,
|
|
19
22
|
isStatePostGloas,
|
|
20
23
|
} from "@lodestar/state-transition";
|
|
21
24
|
import {Bytes32, Slot, ValidatorIndex} from "@lodestar/types";
|
|
22
25
|
import {Logger, fromHex, isErrorAborted, sleep} from "@lodestar/utils";
|
|
23
|
-
import {GENESIS_SLOT
|
|
26
|
+
import {GENESIS_SLOT} from "../constants/constants.js";
|
|
24
27
|
import {BuilderStatus} from "../execution/builder/http.js";
|
|
25
28
|
import {Metrics} from "../metrics/index.js";
|
|
26
29
|
import {ClockEvent} from "../util/clock.js";
|
|
@@ -37,6 +40,11 @@ export const PREPARE_NEXT_SLOT_BPS = 6667;
|
|
|
37
40
|
/* We don't want to do more epoch transition than this */
|
|
38
41
|
const PREPARE_EPOCH_LIMIT = 1;
|
|
39
42
|
|
|
43
|
+
/* Fraction of a slot (out of 10_000) the pre-Gloas builder-deposit pre-verify may spend per tick.
|
|
44
|
+
* 2000 = 20% of slot (2.4s on mainnet), within the spare window left after PREPARE_NEXT_SLOT_BPS.
|
|
45
|
+
* Expressed in bps so it scales with slot duration (mainnet vs minimal/devnet). */
|
|
46
|
+
const BUILDER_PREVERIFY_LIMIT_BPS = 2000;
|
|
47
|
+
|
|
40
48
|
/**
|
|
41
49
|
* At Bellatrix, if we are responsible for proposing in next slot, we want to prepare payload
|
|
42
50
|
* 4s before the start of next slot at PREPARE_NEXT_SLOT_BPS of the current slot.
|
|
@@ -117,6 +125,7 @@ export class PrepareNextSlotScheduler {
|
|
|
117
125
|
? this.metrics?.precomputeNextEpochTransition.duration.startTimer()
|
|
118
126
|
: null;
|
|
119
127
|
const start = Date.now();
|
|
128
|
+
let feeRecipient: string | undefined;
|
|
120
129
|
// No need to wait for this or the clock drift
|
|
121
130
|
if (isForkPostBellatrix(fork)) {
|
|
122
131
|
let preparedState: IBeaconStateView | undefined;
|
|
@@ -140,7 +149,7 @@ export class PrepareNextSlotScheduler {
|
|
|
140
149
|
};
|
|
141
150
|
|
|
142
151
|
const proposerIndex = await getProposerIndex();
|
|
143
|
-
|
|
152
|
+
feeRecipient = this.chain.beaconProposerCache.get(proposerIndex);
|
|
144
153
|
|
|
145
154
|
if (feeRecipient) {
|
|
146
155
|
// If we are proposing next slot, we need to predict if we can proposer-boost-reorg or not
|
|
@@ -214,9 +223,8 @@ export class PrepareNextSlotScheduler {
|
|
|
214
223
|
computeTimeAtSlot(this.config, prepareSlot, this.chain.genesisTime) - Date.now() / 1000;
|
|
215
224
|
this.metrics?.blockPayload.payloadAdvancePrepTime.observe(preparationTime);
|
|
216
225
|
|
|
217
|
-
const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice);
|
|
218
|
-
const finalizedBlockHash =
|
|
219
|
-
this.chain.forkChoice.getFinalizedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX;
|
|
226
|
+
const safeBlockHash = getSafeExecutionBlockHash(this.chain.forkChoice, this.logger);
|
|
227
|
+
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.chain.forkChoice, this.logger);
|
|
220
228
|
|
|
221
229
|
// awaiting here instead of throwing an async call because there is no other task
|
|
222
230
|
// left for scheduler and this gives nice semantics to catch and log errors in the
|
|
@@ -287,6 +295,74 @@ export class PrepareNextSlotScheduler {
|
|
|
287
295
|
});
|
|
288
296
|
|
|
289
297
|
precomputeEpochTransitionTimer?.();
|
|
298
|
+
|
|
299
|
+
// Nothing more to do on an epoch-transition slot: that precompute already ran a full (>1s)
|
|
300
|
+
// epoch transition, so we don't pile the builder-deposit pre-verify (below) on top of it.
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Pre-verify builder-deposit signatures in the GLOAS_PREVERIFY_WINDOW_EPOCHS epochs leading
|
|
305
|
+
// up to the Gloas fork, off the block-import hot path, so onboardBuildersFromPendingDeposits
|
|
306
|
+
// at the transition is mostly cache hits. Reached only on non-epoch-transition slots (the
|
|
307
|
+
// epoch-transition branch returned above). Runs on the head state — its pendingDeposits are
|
|
308
|
+
// what the fork transition derives from, and its epochCtx holds the shared, app-wide
|
|
309
|
+
// builderDepositSignatureCache.
|
|
310
|
+
const headState = this.chain.getHeadState();
|
|
311
|
+
if (isStatePostFulu(headState)) {
|
|
312
|
+
const gloasEpoch = this.config.GLOAS_FORK_EPOCH;
|
|
313
|
+
const finalizedEpoch = this.chain.forkChoice.getFinalizedCheckpoint().epoch;
|
|
314
|
+
if (finalizedEpoch >= gloasEpoch) {
|
|
315
|
+
// Gloas is finalized — the pre-verify cache has served its purpose; release its memory.
|
|
316
|
+
headState.clearPreGloasBuilderDepositCache();
|
|
317
|
+
} else {
|
|
318
|
+
const clockEpoch = computeEpochAtSlot(clockSlot);
|
|
319
|
+
if (
|
|
320
|
+
ForkSeq[fork] < ForkSeq.gloas && // only before the fork
|
|
321
|
+
clockEpoch >= gloasEpoch - GLOAS_PREVERIFY_WINDOW_EPOCHS && // Infinity when Gloas unscheduled → skip
|
|
322
|
+
clockEpoch < gloasEpoch &&
|
|
323
|
+
// skip when we're the next-slot proposer — don't compete with block-production prep.
|
|
324
|
+
feeRecipient === undefined
|
|
325
|
+
) {
|
|
326
|
+
const maxDurationMs = this.config.getSlotComponentDurationMs(BUILDER_PREVERIFY_LIMIT_BPS);
|
|
327
|
+
const preVerifyTimer = this.metrics?.builderDepositPreVerify.duration.startTimer();
|
|
328
|
+
const result = headState.preVerifyBuilderDepositsPreGloas(MAX_BUILDER_DEPOSITS_PER_SLOT, maxDurationMs);
|
|
329
|
+
preVerifyTimer?.();
|
|
330
|
+
|
|
331
|
+
const preVerifyMetrics = this.metrics?.builderDepositPreVerify;
|
|
332
|
+
if (preVerifyMetrics) {
|
|
333
|
+
preVerifyMetrics.pendingDeposits.set(result.pendingDepositsCount);
|
|
334
|
+
preVerifyMetrics.cachedDeposits.set(result.totalCachedDeposits);
|
|
335
|
+
preVerifyMetrics.scannedDeposits.set(result.scannedPendingDeposits);
|
|
336
|
+
preVerifyMetrics.builderPubkeys.set(result.totalBuilderPubkeys);
|
|
337
|
+
if (result.validBuilderSignaturesCount > 0) {
|
|
338
|
+
preVerifyMetrics.validSignatures.inc({type: "builder"}, result.validBuilderSignaturesCount);
|
|
339
|
+
}
|
|
340
|
+
if (result.validValidatorSignaturesCount > 0) {
|
|
341
|
+
preVerifyMetrics.validSignatures.inc({type: "validator"}, result.validValidatorSignaturesCount);
|
|
342
|
+
}
|
|
343
|
+
if (result.invalidBuilderSignaturesCount > 0) {
|
|
344
|
+
preVerifyMetrics.invalidSignatures.inc({type: "builder"}, result.invalidBuilderSignaturesCount);
|
|
345
|
+
}
|
|
346
|
+
if (result.invalidValidatorSignaturesCount > 0) {
|
|
347
|
+
preVerifyMetrics.invalidSignatures.inc({type: "validator"}, result.invalidValidatorSignaturesCount);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Always log (once per slot, only within the pre-fork window) so devnets show the
|
|
352
|
+
// pre-verify draining even on all-cache-hit ticks.
|
|
353
|
+
this.logger.verbose("PrepareNextSlotScheduler pre-verified deposit signatures", {
|
|
354
|
+
clockSlot,
|
|
355
|
+
validBuilderSignaturesCount: result.validBuilderSignaturesCount,
|
|
356
|
+
invalidBuilderSignaturesCount: result.invalidBuilderSignaturesCount,
|
|
357
|
+
validValidatorSignaturesCount: result.validValidatorSignaturesCount,
|
|
358
|
+
invalidValidatorSignaturesCount: result.invalidValidatorSignaturesCount,
|
|
359
|
+
scannedPendingDeposits: result.scannedPendingDeposits,
|
|
360
|
+
totalCachedDeposits: result.totalCachedDeposits,
|
|
361
|
+
totalBuilderPubkeys: result.totalBuilderPubkeys,
|
|
362
|
+
pendingDepositsCount: result.pendingDepositsCount,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
290
366
|
}
|
|
291
367
|
} catch (e) {
|
|
292
368
|
if (!isErrorAborted(e) && !isQueueErrorAborted(e)) {
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import {BitArray} from "@chainsafe/ssz";
|
|
2
2
|
import {ChainForkConfig} from "@lodestar/config";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
IForkChoice,
|
|
5
|
+
ProtoBlock,
|
|
6
|
+
getFinalizedExecutionBlockHash,
|
|
7
|
+
getSafeExecutionBlockHash,
|
|
8
|
+
} from "@lodestar/fork-choice";
|
|
4
9
|
import {
|
|
5
10
|
BUILDER_INDEX_SELF_BUILD,
|
|
6
11
|
ForkName,
|
|
@@ -265,8 +270,8 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
265
270
|
// TODO GLOAS: support non self-building here, the block type differentiation between
|
|
266
271
|
// full and blinded no longer makes sense in gloas, it might be a good idea to move
|
|
267
272
|
// this into a completely separate function and have pre/post gloas more separated
|
|
268
|
-
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
|
|
269
|
-
const finalizedBlockHash = this.forkChoice.
|
|
273
|
+
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice, this.logger);
|
|
274
|
+
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice, this.logger);
|
|
270
275
|
// TODO GLOAS: post-Gloas, proposer feeRecipient is also carried (signed) in
|
|
271
276
|
// ProposerPreferencesPool. Consider using this unified cache instead
|
|
272
277
|
// see https://github.com/ChainSafe/lodestar/issues/9379
|
|
@@ -411,8 +416,8 @@ export async function produceBlockBody<T extends BlockType>(
|
|
|
411
416
|
throw new Error("Expected Bellatrix state for execution block production");
|
|
412
417
|
}
|
|
413
418
|
|
|
414
|
-
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice);
|
|
415
|
-
const finalizedBlockHash = this.forkChoice.
|
|
419
|
+
const safeBlockHash = getSafeExecutionBlockHash(this.forkChoice, this.logger);
|
|
420
|
+
const finalizedBlockHash = getFinalizedExecutionBlockHash(this.forkChoice, this.logger);
|
|
416
421
|
const feeRecipient = requestedFeeRecipient ?? this.beaconProposerCache.getOrDefault(proposerIndex);
|
|
417
422
|
const feeRecipientType = requestedFeeRecipient
|
|
418
423
|
? "requested"
|
|
@@ -1037,7 +1042,7 @@ export async function produceCommonBlockBody<T extends BlockType>(
|
|
|
1037
1042
|
graffiti,
|
|
1038
1043
|
// Eth1 data voting is no longer required since electra
|
|
1039
1044
|
eth1Data: currentState.eth1Data,
|
|
1040
|
-
proposerSlashings,
|
|
1045
|
+
proposerSlashings: this.opts.disableProposerSlashings === true ? [] : proposerSlashings,
|
|
1041
1046
|
attesterSlashings,
|
|
1042
1047
|
attestations,
|
|
1043
1048
|
// Since electra, deposits are processed by the execution layer,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
2
|
-
import {Epoch, RootHex, Slot, ValidatorIndex} from "@lodestar/types";
|
|
2
|
+
import {Epoch, RootHex, Slot, ValidatorIndex, phase0} from "@lodestar/types";
|
|
3
3
|
import {MapDef} from "@lodestar/utils";
|
|
4
4
|
|
|
5
5
|
/** Two distinct block roots signed by the same proposer for the same slot are sufficient to establish an equivocation */
|
|
@@ -13,13 +13,17 @@ const MAX_BLOCK_ROOTS_PER_PROPOSAL = 2;
|
|
|
13
13
|
* equivocation evidence, but the block may still fail other validation. Such a block must not mark the proposal as
|
|
14
14
|
* known, since that would cause a later valid block for the same slot and proposer to be ignored as a repeat proposal.
|
|
15
15
|
*
|
|
16
|
+
* The signed block header of each observed root is kept so a proposer slashing can be produced from the two
|
|
17
|
+
* conflicting headers once an equivocation is established.
|
|
18
|
+
*
|
|
16
19
|
* The cache is pruned on finalization and bounds the number of roots stored per proposer and slot
|
|
17
20
|
*/
|
|
18
21
|
export class SeenBlockProposers {
|
|
19
22
|
private readonly proposerIndexesBySlot = new MapDef<Slot, Set<ValidatorIndex>>(() => new Set<ValidatorIndex>());
|
|
20
|
-
private readonly
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
private readonly signedBlockHeadersBySlot = new MapDef<
|
|
24
|
+
Slot,
|
|
25
|
+
MapDef<ValidatorIndex, Map<RootHex, phase0.SignedBeaconBlockHeader>>
|
|
26
|
+
>(() => new MapDef<ValidatorIndex, Map<RootHex, phase0.SignedBeaconBlockHeader>>(() => new Map()));
|
|
23
27
|
private finalizedSlot: Slot = 0;
|
|
24
28
|
|
|
25
29
|
isKnown(blockSlot: Slot, proposerIndex: ValidatorIndex): boolean {
|
|
@@ -27,27 +31,49 @@ export class SeenBlockProposers {
|
|
|
27
31
|
}
|
|
28
32
|
|
|
29
33
|
hasBlockRoot(blockSlot: Slot, proposerIndex: ValidatorIndex, blockRoot: RootHex): boolean {
|
|
30
|
-
return this.
|
|
34
|
+
return this.signedBlockHeadersBySlot.get(blockSlot)?.get(proposerIndex)?.has(blockRoot) === true;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
isEquivocating(blockSlot: Slot, proposerIndex: ValidatorIndex): boolean {
|
|
34
|
-
return (
|
|
38
|
+
return (
|
|
39
|
+
(this.signedBlockHeadersBySlot.get(blockSlot)?.get(proposerIndex)?.size ?? 0) >= MAX_BLOCK_ROOTS_PER_PROPOSAL
|
|
40
|
+
);
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
getConflictingBlockRoots(blockSlot: Slot, proposerIndex: ValidatorIndex, blockRoot: RootHex): RootHex[] {
|
|
38
|
-
const
|
|
39
|
-
return
|
|
44
|
+
const signedBlockHeaderByRoot = this.signedBlockHeadersBySlot.get(blockSlot)?.get(proposerIndex);
|
|
45
|
+
return signedBlockHeaderByRoot === undefined
|
|
46
|
+
? []
|
|
47
|
+
: Array.from(signedBlockHeaderByRoot.keys()).filter((root) => root !== blockRoot);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Return the two signed block headers that establish an equivocation, or null if there is none */
|
|
51
|
+
getEquivocationHeaders(
|
|
52
|
+
blockSlot: Slot,
|
|
53
|
+
proposerIndex: ValidatorIndex
|
|
54
|
+
): [phase0.SignedBeaconBlockHeader, phase0.SignedBeaconBlockHeader] | null {
|
|
55
|
+
const signedBlockHeaderByRoot = this.signedBlockHeadersBySlot.get(blockSlot)?.get(proposerIndex);
|
|
56
|
+
if (signedBlockHeaderByRoot === undefined || signedBlockHeaderByRoot.size < MAX_BLOCK_ROOTS_PER_PROPOSAL) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const [signedHeader1, signedHeader2] = signedBlockHeaderByRoot.values();
|
|
60
|
+
return [signedHeader1, signedHeader2];
|
|
40
61
|
}
|
|
41
62
|
|
|
42
63
|
/** Record a block only after its proposer signature has been verified */
|
|
43
|
-
observeBlockRoot(
|
|
64
|
+
observeBlockRoot(
|
|
65
|
+
blockSlot: Slot,
|
|
66
|
+
proposerIndex: ValidatorIndex,
|
|
67
|
+
blockRoot: RootHex,
|
|
68
|
+
signedBlockHeader: phase0.SignedBeaconBlockHeader
|
|
69
|
+
): void {
|
|
44
70
|
if (blockSlot < this.finalizedSlot) {
|
|
45
71
|
throw Error(`blockSlot ${blockSlot} < finalizedSlot ${this.finalizedSlot}`);
|
|
46
72
|
}
|
|
47
73
|
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
|
|
74
|
+
const signedBlockHeaderByRoot = this.signedBlockHeadersBySlot.getOrDefault(blockSlot).getOrDefault(proposerIndex);
|
|
75
|
+
if (signedBlockHeaderByRoot.size < MAX_BLOCK_ROOTS_PER_PROPOSAL && !signedBlockHeaderByRoot.has(blockRoot)) {
|
|
76
|
+
signedBlockHeaderByRoot.set(blockRoot, signedBlockHeader);
|
|
51
77
|
}
|
|
52
78
|
}
|
|
53
79
|
|
|
@@ -67,9 +93,9 @@ export class SeenBlockProposers {
|
|
|
67
93
|
this.proposerIndexesBySlot.delete(slot);
|
|
68
94
|
}
|
|
69
95
|
}
|
|
70
|
-
for (const slot of this.
|
|
96
|
+
for (const slot of this.signedBlockHeadersBySlot.keys()) {
|
|
71
97
|
if (slot < finalizedSlot) {
|
|
72
|
-
this.
|
|
98
|
+
this.signedBlockHeadersBySlot.delete(slot);
|
|
73
99
|
}
|
|
74
100
|
}
|
|
75
101
|
}
|
|
@@ -23,8 +23,9 @@ import {
|
|
|
23
23
|
getBlockProposerSignatureSet,
|
|
24
24
|
isExecutionBlockBodyType,
|
|
25
25
|
isStatePostBellatrix,
|
|
26
|
+
signedBlockToSignedHeader,
|
|
26
27
|
} from "@lodestar/state-transition";
|
|
27
|
-
import {RootHex, SignedBeaconBlock, deneb, gloas, isGloasBeaconBlock} from "@lodestar/types";
|
|
28
|
+
import {RootHex, SignedBeaconBlock, deneb, gloas, isGloasBeaconBlock, ssz} from "@lodestar/types";
|
|
28
29
|
import {byteArrayEquals, sleep, toRootHex} from "@lodestar/utils";
|
|
29
30
|
import {BlockErrorCode, BlockGossipError, GossipAction} from "../errors/index.js";
|
|
30
31
|
import {IBeaconChain} from "../interface.js";
|
|
@@ -75,7 +76,10 @@ export async function validateGossipBlock(
|
|
|
75
76
|
// reboot if the `observed_block_producers` cache is empty. In that case, without this
|
|
76
77
|
// check, we will load the parent and state from disk only to find out later that we
|
|
77
78
|
// already know this block.
|
|
78
|
-
|
|
79
|
+
// A block's hash tree root is identical to its header's, so the root is derived from the header
|
|
80
|
+
// which is also used as potential equivocation evidence
|
|
81
|
+
const signedBlockHeader = signedBlockToSignedHeader(config, signedBlock);
|
|
82
|
+
const blockRoot = toRootHex(ssz.phase0.BeaconBlockHeader.hashTreeRoot(signedBlockHeader.message));
|
|
79
83
|
if (chain.forkChoice.getBlockHexDefaultStatus(blockRoot) !== null) {
|
|
80
84
|
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.ALREADY_KNOWN, root: blockRoot});
|
|
81
85
|
}
|
|
@@ -89,7 +93,7 @@ export async function validateGossipBlock(
|
|
|
89
93
|
if (chain.seenBlockProposers.isKnown(blockSlot, proposerIndex)) {
|
|
90
94
|
if (!hasBlockRoot && !chain.seenBlockProposers.isEquivocating(blockSlot, proposerIndex)) {
|
|
91
95
|
await verifyBlockProposerSignature(chain, signedBlock, blockRoot, {verifyOnMainThread: false});
|
|
92
|
-
chain.seenBlockProposers.observeBlockRoot(blockSlot, proposerIndex, blockRoot);
|
|
96
|
+
chain.seenBlockProposers.observeBlockRoot(blockSlot, proposerIndex, blockRoot, signedBlockHeader);
|
|
93
97
|
}
|
|
94
98
|
throw new BlockGossipError(GossipAction.IGNORE, {code: BlockErrorCode.REPEAT_PROPOSAL, proposerIndex});
|
|
95
99
|
}
|
|
@@ -276,7 +280,7 @@ export async function validateGossipBlock(
|
|
|
276
280
|
|
|
277
281
|
// [REJECT] The proposer signature, signed_beacon_block.signature, is valid with respect to the proposer_index pubkey.
|
|
278
282
|
await verifyBlockProposerSignature(chain, signedBlock, blockRoot);
|
|
279
|
-
chain.seenBlockProposers.observeBlockRoot(blockSlot, proposerIndex, blockRoot);
|
|
283
|
+
chain.seenBlockProposers.observeBlockRoot(blockSlot, proposerIndex, blockRoot, signedBlockHeader);
|
|
280
284
|
|
|
281
285
|
// [REJECT] The block is proposed by the expected proposer_index for the block's slot in the context of the current
|
|
282
286
|
// shuffling (defined by parent_root/slot). If the proposer_index cannot immediately be verified against the expected
|
|
@@ -1179,6 +1179,10 @@ export function createLodestarMetrics(
|
|
|
1179
1179
|
name: "lodestar_oppool_proposer_slashing_pool_size",
|
|
1180
1180
|
help: "Current size of the ProposerSlashingPool",
|
|
1181
1181
|
}),
|
|
1182
|
+
proposerSlashingsProduced: register.counter({
|
|
1183
|
+
name: "lodestar_oppool_proposer_slashings_produced_total",
|
|
1184
|
+
help: "Total number of proposer slashings produced from observed equivocations",
|
|
1185
|
+
}),
|
|
1182
1186
|
voluntaryExitPoolSize: register.gauge({
|
|
1183
1187
|
name: "lodestar_oppool_voluntary_exit_pool_size",
|
|
1184
1188
|
help: "Current size of the VoluntaryExitPool",
|
|
@@ -1708,6 +1712,41 @@ export function createLodestarMetrics(
|
|
|
1708
1712
|
}),
|
|
1709
1713
|
},
|
|
1710
1714
|
|
|
1715
|
+
// Pre-verify builder-deposit signatures in the epochs before the Gloas fork (prepareNextSlot).
|
|
1716
|
+
builderDepositPreVerify: {
|
|
1717
|
+
duration: register.histogram({
|
|
1718
|
+
name: "lodestar_builder_deposit_preverify_duration_seconds",
|
|
1719
|
+
help: "Duration of one preVerifyBuilderDepositsPreGloas call (BLS batch verify + scan)",
|
|
1720
|
+
buckets: [1, 2, 3, 4],
|
|
1721
|
+
}),
|
|
1722
|
+
pendingDeposits: register.gauge({
|
|
1723
|
+
name: "lodestar_builder_deposit_preverify_pending_deposits",
|
|
1724
|
+
help: "Current state.pendingDeposits length (backlog to drain before the fork)",
|
|
1725
|
+
}),
|
|
1726
|
+
cachedDeposits: register.gauge({
|
|
1727
|
+
name: "lodestar_builder_deposit_preverify_cached_deposits",
|
|
1728
|
+
help: "Deposit signatures verified & cached so far this pre-fork window (valid + invalid)",
|
|
1729
|
+
}),
|
|
1730
|
+
scannedDeposits: register.gauge({
|
|
1731
|
+
name: "lodestar_builder_deposit_preverify_scanned_deposits",
|
|
1732
|
+
help: "Pending deposits examined by the last pre-verify call (< pending_deposits ⇒ cap hit)",
|
|
1733
|
+
}),
|
|
1734
|
+
builderPubkeys: register.gauge({
|
|
1735
|
+
name: "lodestar_builder_deposit_preverify_builder_pubkeys",
|
|
1736
|
+
help: "Distinct builder pubkeys tracked this window (validator deposits for these are pre-verified)",
|
|
1737
|
+
}),
|
|
1738
|
+
validSignatures: register.counter<{type: "builder" | "validator"}>({
|
|
1739
|
+
name: "lodestar_builder_deposit_preverify_valid_signatures_total",
|
|
1740
|
+
help: "Cumulative deposit signatures whose BLS verification passed (VALID), by credential type",
|
|
1741
|
+
labelNames: ["type"],
|
|
1742
|
+
}),
|
|
1743
|
+
invalidSignatures: register.counter<{type: "builder" | "validator"}>({
|
|
1744
|
+
name: "lodestar_builder_deposit_preverify_invalid_signatures_total",
|
|
1745
|
+
help: "Cumulative deposit signatures whose BLS verification failed (INVALID), by credential type",
|
|
1746
|
+
labelNames: ["type"],
|
|
1747
|
+
}),
|
|
1748
|
+
},
|
|
1749
|
+
|
|
1711
1750
|
// reprocess attestations
|
|
1712
1751
|
reprocessApiAttestations: {
|
|
1713
1752
|
total: register.gauge({
|
package/src/network/network.ts
CHANGED
|
@@ -147,6 +147,7 @@ export class Network implements INetwork {
|
|
|
147
147
|
this.chain.emitter.on(ChainEvent.updateTargetCustodyGroupCount, this.onTargetGroupCountUpdated);
|
|
148
148
|
this.chain.emitter.on(ChainEvent.publishDataColumns, this.onPublishDataColumns);
|
|
149
149
|
this.chain.emitter.on(ChainEvent.publishBlobSidecars, this.onPublishBlobSidecars);
|
|
150
|
+
this.chain.emitter.on(ChainEvent.publishProposerSlashing, this.onPublishProposerSlashing);
|
|
150
151
|
this.chain.emitter.on(ChainEvent.updateStatus, this.onUpdateStatus);
|
|
151
152
|
}
|
|
152
153
|
|
|
@@ -244,6 +245,7 @@ export class Network implements INetwork {
|
|
|
244
245
|
this.chain.emitter.off(ChainEvent.updateTargetCustodyGroupCount, this.onTargetGroupCountUpdated);
|
|
245
246
|
this.chain.emitter.off(ChainEvent.publishDataColumns, this.onPublishDataColumns);
|
|
246
247
|
this.chain.emitter.off(ChainEvent.publishBlobSidecars, this.onPublishBlobSidecars);
|
|
248
|
+
this.chain.emitter.off(ChainEvent.publishProposerSlashing, this.onPublishProposerSlashing);
|
|
247
249
|
this.chain.emitter.off(ChainEvent.updateStatus, this.onUpdateStatus);
|
|
248
250
|
await this.core.close();
|
|
249
251
|
|
|
@@ -455,7 +457,8 @@ export class Network implements INetwork {
|
|
|
455
457
|
|
|
456
458
|
return this.publishGossip<GossipType.proposer_slashing>(
|
|
457
459
|
{type: GossipType.proposer_slashing, boundary},
|
|
458
|
-
proposerSlashing
|
|
460
|
+
proposerSlashing,
|
|
461
|
+
{ignoreDuplicatePublishError: true}
|
|
459
462
|
);
|
|
460
463
|
}
|
|
461
464
|
|
|
@@ -877,6 +880,18 @@ export class Network implements INetwork {
|
|
|
877
880
|
return promiseAllMaybeAsync(sidecars.map((sidecar) => () => this.publishBlobSidecar(sidecar)));
|
|
878
881
|
};
|
|
879
882
|
|
|
883
|
+
private onPublishProposerSlashing = async (proposerSlashing: phase0.ProposerSlashing): Promise<void> => {
|
|
884
|
+
try {
|
|
885
|
+
await this.publishProposerSlashing(proposerSlashing);
|
|
886
|
+
} catch (e) {
|
|
887
|
+
this.logger.debug(
|
|
888
|
+
"Error publishing proposer slashing",
|
|
889
|
+
{proposerIndex: proposerSlashing.signedHeader1.message.proposerIndex},
|
|
890
|
+
e as Error
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
};
|
|
894
|
+
|
|
880
895
|
private onUpdateStatus = async (): Promise<void> => {
|
|
881
896
|
await this.core.updateStatus(this.chain.getStatus());
|
|
882
897
|
};
|
|
@@ -257,6 +257,13 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand
|
|
|
257
257
|
chain.seenPayloadEnvelopeInputCache.prune(blockRootHex);
|
|
258
258
|
}
|
|
259
259
|
throw e;
|
|
260
|
+
} finally {
|
|
261
|
+
// The block received from the network may have established an equivocation, either by conflicting
|
|
262
|
+
// with a previously observed block root (REPEAT_PROPOSAL) or with a root observed during validation
|
|
263
|
+
const proposerIndex = signedBlock.message.proposerIndex;
|
|
264
|
+
if (chain.seenBlockProposers.isEquivocating(slot, proposerIndex)) {
|
|
265
|
+
chain.processProposerEquivocation(slot, proposerIndex);
|
|
266
|
+
}
|
|
260
267
|
}
|
|
261
268
|
}
|
|
262
269
|
|