@lodestar/beacon-node 1.49.0 → 1.50.0-dev.01cc10ce70
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 +6 -1
- package/lib/api/impl/beacon/blocks/index.js.map +1 -1
- package/lib/api/impl/beacon/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/index.js +13 -0
- package/lib/api/impl/beacon/index.js.map +1 -1
- package/lib/api/impl/beacon/pool/index.js +4 -4
- package/lib/api/impl/beacon/pool/index.js.map +1 -1
- package/lib/api/impl/beacon/state/index.d.ts.map +1 -1
- package/lib/api/impl/beacon/state/index.js +28 -0
- package/lib/api/impl/beacon/state/index.js.map +1 -1
- package/lib/api/impl/validator/index.d.ts +1 -0
- package/lib/api/impl/validator/index.d.ts.map +1 -1
- package/lib/api/impl/validator/index.js +17 -1
- package/lib/api/impl/validator/index.js.map +1 -1
- package/lib/chain/archiveStore/archiveStore.d.ts +1 -0
- package/lib/chain/archiveStore/archiveStore.d.ts.map +1 -1
- package/lib/chain/archiveStore/archiveStore.js +27 -4
- package/lib/chain/archiveStore/archiveStore.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts +2 -2
- package/lib/chain/archiveStore/historicalState/getHistoricalState.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/getHistoricalState.js +5 -7
- package/lib/chain/archiveStore/historicalState/getHistoricalState.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js +1 -1
- package/lib/chain/archiveStore/historicalState/historicalStateRegen.js.map +1 -1
- package/lib/chain/archiveStore/historicalState/types.d.ts +2 -2
- package/lib/chain/archiveStore/historicalState/types.d.ts.map +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js +1 -1
- package/lib/chain/archiveStore/historicalState/worker.js.map +1 -1
- package/lib/chain/archiveStore/interface.d.ts +1 -1
- package/lib/chain/archiveStore/interface.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts +4 -1
- package/lib/chain/archiveStore/utils/pruneHistory.d.ts.map +1 -1
- package/lib/chain/archiveStore/utils/pruneHistory.js +4 -3
- package/lib/chain/archiveStore/utils/pruneHistory.js.map +1 -1
- package/lib/chain/blocks/importBlock.d.ts.map +1 -1
- package/lib/chain/blocks/importBlock.js +4 -0
- package/lib/chain/blocks/importBlock.js.map +1 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts +2 -1
- package/lib/chain/blocks/payloadEnvelopeInput/types.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlock.js +1 -1
- package/lib/chain/blocks/verifyBlock.js.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts +2 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.d.ts.map +1 -1
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js +5 -4
- package/lib/chain/blocks/verifyBlocksStateTransitionOnly.js.map +1 -1
- package/lib/chain/chain.d.ts +5 -4
- package/lib/chain/chain.d.ts.map +1 -1
- package/lib/chain/chain.js +32 -16
- package/lib/chain/chain.js.map +1 -1
- package/lib/chain/emitter.d.ts +7 -1
- 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/errors/envelopeReconstructionError.d.ts +5 -0
- package/lib/chain/errors/envelopeReconstructionError.d.ts.map +1 -1
- package/lib/chain/errors/envelopeReconstructionError.js +2 -0
- package/lib/chain/errors/envelopeReconstructionError.js.map +1 -1
- package/lib/chain/errors/gossipValidation.d.ts +3 -1
- package/lib/chain/errors/gossipValidation.d.ts.map +1 -1
- package/lib/chain/errors/gossipValidation.js +2 -0
- package/lib/chain/errors/gossipValidation.js.map +1 -1
- package/lib/chain/forkChoice/index.d.ts.map +1 -1
- package/lib/chain/forkChoice/index.js +2 -0
- package/lib/chain/forkChoice/index.js.map +1 -1
- package/lib/chain/initState.d.ts +2 -9
- package/lib/chain/initState.d.ts.map +1 -1
- package/lib/chain/initState.js +5 -38
- package/lib/chain/initState.js.map +1 -1
- package/lib/chain/interface.d.ts +2 -2
- package/lib/chain/interface.d.ts.map +1 -1
- package/lib/chain/opPools/opPool.d.ts +5 -4
- package/lib/chain/opPools/opPool.d.ts.map +1 -1
- package/lib/chain/opPools/opPool.js +62 -18
- package/lib/chain/opPools/opPool.js.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.d.ts +4 -4
- package/lib/chain/opPools/proposerPreferencesPool.d.ts.map +1 -1
- package/lib/chain/opPools/proposerPreferencesPool.js +14 -11
- package/lib/chain/opPools/proposerPreferencesPool.js.map +1 -1
- package/lib/chain/opPools/utils.d.ts +4 -1
- package/lib/chain/opPools/utils.d.ts.map +1 -1
- package/lib/chain/opPools/utils.js +11 -1
- package/lib/chain/opPools/utils.js.map +1 -1
- package/lib/chain/options.d.ts +1 -1
- package/lib/chain/options.d.ts.map +1 -1
- package/lib/chain/options.js +1 -1
- package/lib/chain/options.js.map +1 -1
- package/lib/chain/prepareNextSlot.d.ts.map +1 -1
- package/lib/chain/prepareNextSlot.js +5 -2
- package/lib/chain/prepareNextSlot.js.map +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js +1 -1
- package/lib/chain/produceBlock/computeNewStateRoot.js.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts +3 -1
- package/lib/chain/produceBlock/produceBlockBody.d.ts.map +1 -1
- package/lib/chain/produceBlock/produceBlockBody.js +6 -2
- package/lib/chain/produceBlock/produceBlockBody.js.map +1 -1
- package/lib/chain/regen/regen.d.ts +2 -0
- package/lib/chain/regen/regen.d.ts.map +1 -1
- package/lib/chain/regen/regen.js +10 -4
- package/lib/chain/regen/regen.js.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts +9 -2
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.d.ts.map +1 -1
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js +23 -13
- package/lib/chain/seenCache/seenPayloadEnvelopeInput.js.map +1 -1
- package/lib/chain/validation/attesterSlashing.d.ts +4 -4
- package/lib/chain/validation/attesterSlashing.d.ts.map +1 -1
- package/lib/chain/validation/attesterSlashing.js +3 -0
- package/lib/chain/validation/attesterSlashing.js.map +1 -1
- package/lib/chain/validation/proposerSlashing.d.ts +3 -3
- package/lib/chain/validation/proposerSlashing.d.ts.map +1 -1
- package/lib/chain/validation/proposerSlashing.js +3 -0
- package/lib/chain/validation/proposerSlashing.js.map +1 -1
- package/lib/chain/validatorMonitor.d.ts +5 -1
- package/lib/chain/validatorMonitor.d.ts.map +1 -1
- package/lib/chain/validatorMonitor.js +64 -54
- package/lib/chain/validatorMonitor.js.map +1 -1
- package/lib/db/beacon.d.ts.map +1 -1
- package/lib/db/beacon.js +2 -0
- package/lib/db/beacon.js.map +1 -1
- package/lib/db/repositories/proposerSlashing.d.ts +1 -0
- package/lib/db/repositories/proposerSlashing.d.ts.map +1 -1
- package/lib/db/repositories/proposerSlashing.js +5 -1
- package/lib/db/repositories/proposerSlashing.js.map +1 -1
- package/lib/execution/engine/disabled.d.ts +0 -2
- package/lib/execution/engine/disabled.d.ts.map +1 -1
- package/lib/execution/engine/disabled.js +0 -6
- package/lib/execution/engine/disabled.js.map +1 -1
- package/lib/execution/engine/http.d.ts +1 -3
- package/lib/execution/engine/http.d.ts.map +1 -1
- package/lib/execution/engine/http.js +7 -21
- package/lib/execution/engine/http.js.map +1 -1
- package/lib/execution/engine/interface.d.ts +2 -4
- package/lib/execution/engine/interface.d.ts.map +1 -1
- package/lib/execution/engine/jsonRpcHttpClient.d.ts +2 -1
- package/lib/execution/engine/jsonRpcHttpClient.d.ts.map +1 -1
- package/lib/execution/engine/jsonRpcHttpClient.js +6 -1
- package/lib/execution/engine/jsonRpcHttpClient.js.map +1 -1
- package/lib/execution/engine/mock.d.ts +0 -2
- package/lib/execution/engine/mock.d.ts.map +1 -1
- package/lib/execution/engine/mock.js +0 -8
- package/lib/execution/engine/mock.js.map +1 -1
- package/lib/execution/engine/transportMetrics.d.ts +13 -0
- package/lib/execution/engine/transportMetrics.d.ts.map +1 -0
- package/lib/execution/engine/transportMetrics.js +2 -0
- package/lib/execution/engine/transportMetrics.js.map +1 -0
- package/lib/execution/engine/types.d.ts +0 -8
- package/lib/execution/engine/types.d.ts.map +1 -1
- package/lib/execution/engine/types.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +1 -1
- package/lib/index.js.map +1 -1
- package/lib/metrics/metrics/beacon.d.ts +1 -1
- package/lib/metrics/metrics/beacon.d.ts.map +1 -1
- package/lib/metrics/metrics/beacon.js.map +1 -1
- package/lib/metrics/metrics/lodestar.d.ts +6 -0
- package/lib/metrics/metrics/lodestar.d.ts.map +1 -1
- package/lib/metrics/metrics/lodestar.js +10 -0
- package/lib/metrics/metrics/lodestar.js.map +1 -1
- package/lib/metrics/metrics.d.ts +7 -3
- package/lib/metrics/metrics.d.ts.map +1 -1
- package/lib/metrics/metrics.js +4 -4
- package/lib/metrics/metrics.js.map +1 -1
- package/lib/network/metadata.d.ts +1 -1
- package/lib/network/metadata.d.ts.map +1 -1
- package/lib/network/metadata.js +17 -17
- package/lib/network/metadata.js.map +1 -1
- package/lib/network/peers/datastore.d.ts +9 -9
- package/lib/network/peers/datastore.d.ts.map +1 -1
- package/lib/network/peers/datastore.js +36 -36
- package/lib/network/peers/datastore.js.map +1 -1
- package/lib/network/processor/gossipHandlers.d.ts.map +1 -1
- package/lib/network/processor/gossipHandlers.js +7 -4
- package/lib/network/processor/gossipHandlers.js.map +1 -1
- package/lib/network/processor/gossipValidatorFn.d.ts +7 -3
- package/lib/network/processor/gossipValidatorFn.d.ts.map +1 -1
- package/lib/network/processor/gossipValidatorFn.js +7 -3
- package/lib/network/processor/gossipValidatorFn.js.map +1 -1
- package/lib/network/processor/index.d.ts +9 -0
- package/lib/network/processor/index.d.ts.map +1 -1
- package/lib/network/processor/index.js +77 -10
- package/lib/network/processor/index.js.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.d.ts.map +1 -1
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js +41 -26
- package/lib/network/reqresp/handlers/executionPayloadEnvelopesByRoot.js.map +1 -1
- package/lib/node/nodejs.d.ts.map +1 -1
- package/lib/node/nodejs.js +25 -3
- package/lib/node/nodejs.js.map +1 -1
- package/lib/sync/range/batch.d.ts +6 -0
- package/lib/sync/range/batch.d.ts.map +1 -1
- package/lib/sync/range/batch.js +10 -0
- package/lib/sync/range/batch.js.map +1 -1
- package/lib/sync/range/chain.d.ts.map +1 -1
- package/lib/sync/range/chain.js +5 -1
- package/lib/sync/range/chain.js.map +1 -1
- package/lib/sync/range/range.d.ts.map +1 -1
- package/lib/sync/range/range.js +50 -18
- package/lib/sync/range/range.js.map +1 -1
- package/lib/sync/unknownBlock.d.ts +9 -5
- package/lib/sync/unknownBlock.d.ts.map +1 -1
- package/lib/sync/unknownBlock.js +88 -46
- package/lib/sync/unknownBlock.js.map +1 -1
- package/lib/util/execution.d.ts +11 -11
- package/lib/util/execution.d.ts.map +1 -1
- package/lib/util/execution.js +11 -7
- package/lib/util/execution.js.map +1 -1
- package/package.json +16 -16
- package/src/api/impl/beacon/blocks/index.ts +7 -4
- package/src/api/impl/beacon/index.ts +15 -0
- package/src/api/impl/beacon/pool/index.ts +4 -4
- package/src/api/impl/beacon/state/index.ts +36 -0
- package/src/api/impl/validator/index.ts +16 -2
- package/src/chain/archiveStore/archiveStore.ts +30 -5
- package/src/chain/archiveStore/historicalState/getHistoricalState.ts +5 -7
- package/src/chain/archiveStore/historicalState/historicalStateRegen.ts +1 -1
- package/src/chain/archiveStore/historicalState/types.ts +2 -2
- package/src/chain/archiveStore/historicalState/worker.ts +1 -2
- package/src/chain/archiveStore/interface.ts +1 -1
- package/src/chain/archiveStore/utils/pruneHistory.ts +6 -4
- package/src/chain/blocks/importBlock.ts +4 -0
- package/src/chain/blocks/payloadEnvelopeInput/types.ts +2 -1
- package/src/chain/blocks/verifyBlock.ts +1 -0
- package/src/chain/blocks/verifyBlocksStateTransitionOnly.ts +6 -3
- package/src/chain/chain.ts +38 -19
- package/src/chain/emitter.ts +7 -1
- package/src/chain/errors/envelopeReconstructionError.ts +3 -0
- package/src/chain/errors/gossipValidation.ts +2 -0
- package/src/chain/forkChoice/index.ts +2 -0
- package/src/chain/initState.ts +8 -60
- package/src/chain/interface.ts +2 -2
- package/src/chain/opPools/opPool.ts +91 -18
- package/src/chain/opPools/proposerPreferencesPool.ts +14 -10
- package/src/chain/opPools/utils.ts +31 -3
- package/src/chain/options.ts +2 -2
- package/src/chain/prepareNextSlot.ts +6 -3
- package/src/chain/produceBlock/computeNewStateRoot.ts +1 -1
- package/src/chain/produceBlock/produceBlockBody.ts +8 -1
- package/src/chain/regen/regen.ts +12 -4
- package/src/chain/seenCache/seenPayloadEnvelopeInput.ts +31 -13
- package/src/chain/validation/attesterSlashing.ts +7 -4
- package/src/chain/validation/proposerSlashing.ts +7 -4
- package/src/chain/validatorMonitor.ts +75 -57
- package/src/db/beacon.ts +2 -0
- package/src/db/repositories/proposerSlashing.ts +6 -1
- package/src/execution/engine/disabled.ts +0 -8
- package/src/execution/engine/http.ts +7 -34
- package/src/execution/engine/interface.ts +2 -6
- package/src/execution/engine/jsonRpcHttpClient.ts +9 -2
- package/src/execution/engine/mock.ts +1 -19
- package/src/execution/engine/transportMetrics.ts +9 -0
- package/src/execution/engine/types.ts +0 -10
- package/src/index.ts +1 -1
- package/src/metrics/metrics/beacon.ts +1 -1
- package/src/metrics/metrics/lodestar.ts +10 -0
- package/src/metrics/metrics.ts +21 -6
- package/src/network/metadata.ts +17 -17
- package/src/network/peers/datastore.ts +36 -36
- package/src/network/processor/gossipHandlers.ts +7 -4
- package/src/network/processor/gossipValidatorFn.ts +13 -3
- package/src/network/processor/index.ts +89 -10
- package/src/network/reqresp/handlers/executionPayloadEnvelopesByRoot.ts +42 -28
- package/src/node/nodejs.ts +31 -3
- package/src/sync/range/batch.ts +11 -0
- package/src/sync/range/chain.ts +6 -1
- package/src/sync/range/range.ts +50 -18
- package/src/sync/unknownBlock.ts +99 -50
- package/src/util/execution.ts +26 -14
package/src/chain/chain.ts
CHANGED
|
@@ -71,7 +71,7 @@ import {BufferPool} from "../util/bufferPool.js";
|
|
|
71
71
|
import {Clock, ClockEvent, IClock} from "../util/clock.js";
|
|
72
72
|
import {CustodyConfig, getValidatorsCustodyRequirement} from "../util/dataColumns.js";
|
|
73
73
|
import {callInNextEventLoop} from "../util/eventLoop.js";
|
|
74
|
-
import {
|
|
74
|
+
import {ReconstructMissPolicy, isRebuildMiss, reconstructExecutionPayloadEnvelopes} from "../util/execution.js";
|
|
75
75
|
import {ensureDir, writeIfNotExist} from "../util/file.js";
|
|
76
76
|
import {isOptimisticBlock} from "../util/forkChoice.js";
|
|
77
77
|
import {JobItemQueue} from "../util/queue/itemQueue.js";
|
|
@@ -432,6 +432,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
432
432
|
blockStateCache,
|
|
433
433
|
checkpointStateCache,
|
|
434
434
|
seenBlockInputCache: this.seenBlockInputCache,
|
|
435
|
+
serializedCache: this.serializedCache,
|
|
435
436
|
db,
|
|
436
437
|
metrics,
|
|
437
438
|
validatorMonitor,
|
|
@@ -611,7 +612,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
611
612
|
/** Populate in-memory caches with persisted data. Call at least once on startup */
|
|
612
613
|
async loadFromDisk(): Promise<void> {
|
|
613
614
|
await this.regen.init();
|
|
614
|
-
await this.opPool.fromPersisted(this.db);
|
|
615
|
+
await this.opPool.fromPersisted(this.db, this.getHeadState(), this.bls, this.clock.currentSlot);
|
|
615
616
|
}
|
|
616
617
|
|
|
617
618
|
/** Persist in-memory data to the DB. Call at least once before stopping the process */
|
|
@@ -702,6 +703,26 @@ export class BeaconChain implements IBeaconChain {
|
|
|
702
703
|
stateRoot: RootHex,
|
|
703
704
|
opts?: StateGetOpts
|
|
704
705
|
): Promise<{state: IBeaconStateView | Uint8Array; executionOptimistic: boolean; finalized: boolean} | null> {
|
|
706
|
+
const finalizedBlock = this.forkChoice.getFinalizedBlock();
|
|
707
|
+
const finalizedCheckpoint = this.forkChoice.getFinalizedCheckpoint();
|
|
708
|
+
// Checkpoint state only equals the block post-state if the block is at the epoch start slot
|
|
709
|
+
if (
|
|
710
|
+
finalizedBlock.stateRoot === stateRoot &&
|
|
711
|
+
finalizedBlock.slot === computeStartSlotAtEpoch(finalizedCheckpoint.epoch)
|
|
712
|
+
) {
|
|
713
|
+
const state = this.regen.getCheckpointStateSync({
|
|
714
|
+
epoch: finalizedCheckpoint.epoch,
|
|
715
|
+
rootHex: finalizedCheckpoint.rootHex,
|
|
716
|
+
});
|
|
717
|
+
if (state) {
|
|
718
|
+
return {
|
|
719
|
+
state,
|
|
720
|
+
executionOptimistic: isOptimisticBlock(finalizedBlock),
|
|
721
|
+
finalized: finalizedCheckpoint.epoch !== GENESIS_EPOCH,
|
|
722
|
+
};
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
705
726
|
if (opts?.allowRegen) {
|
|
706
727
|
const state = await this.regen.getState(stateRoot, RegenCaller.restApi);
|
|
707
728
|
const block = this.forkChoice.getBlockDefaultStatus(
|
|
@@ -934,12 +955,13 @@ export class BeaconChain implements IBeaconChain {
|
|
|
934
955
|
|
|
935
956
|
/**
|
|
936
957
|
* Batch variant: archived header envelopes are rebuilt 32 per EL round-trip. Aligned with `requests`.
|
|
937
|
-
*
|
|
938
|
-
* returns null for that entry, for
|
|
958
|
+
* An archived envelope that cannot be rebuilt (EL-unavailable body or body root mismatch) is not the
|
|
959
|
+
* same as an unknown one: `"throw"` surfaces it, `"omit"` returns null for that entry, for
|
|
960
|
+
* peer-facing paths where the spec allows omission.
|
|
939
961
|
*/
|
|
940
962
|
async getSerializedExecutionPayloadEnvelopes(
|
|
941
963
|
requests: {blockSlot: Slot; blockRootHex: RootHex}[],
|
|
942
|
-
|
|
964
|
+
onMiss: ReconstructMissPolicy = "throw"
|
|
943
965
|
): Promise<(Uint8Array | null)[]> {
|
|
944
966
|
const out: (Uint8Array | null)[] = new Array(requests.length).fill(null);
|
|
945
967
|
const headerEnvelopes: gloas.SignedExecutionPayloadHeaderEnvelope[] = [];
|
|
@@ -978,14 +1000,14 @@ export class BeaconChain implements IBeaconChain {
|
|
|
978
1000
|
for (let j = 0; j < rebuilt.length; j++) {
|
|
979
1001
|
const result = rebuilt[j];
|
|
980
1002
|
if (isRebuildMiss(result)) {
|
|
981
|
-
if (
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
"Archived envelope failed body root check against EL bodies"
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1003
|
+
if (onMiss === "throw") throw result.error;
|
|
1004
|
+
this.logger.debug(
|
|
1005
|
+
result.reason === "mismatch"
|
|
1006
|
+
? "Archived envelope failed body root check against EL bodies"
|
|
1007
|
+
: "EL cannot serve bodies for archived envelope, omitting",
|
|
1008
|
+
{slot: result.slot},
|
|
1009
|
+
result.error
|
|
1010
|
+
);
|
|
989
1011
|
continue;
|
|
990
1012
|
}
|
|
991
1013
|
out[headerEnvelopeIdxs[j]] = ssz.gloas.SignedExecutionPayloadEnvelope.serialize(result);
|
|
@@ -1016,10 +1038,7 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1016
1038
|
const [result] = await reconstructExecutionPayloadEnvelopes(this.executionEngine, this.metrics, [
|
|
1017
1039
|
archived.headerEnvelope,
|
|
1018
1040
|
]);
|
|
1019
|
-
if (isRebuildMiss(result))
|
|
1020
|
-
if (result.reason === "mismatch") throw result.error;
|
|
1021
|
-
return null;
|
|
1022
|
-
}
|
|
1041
|
+
if (isRebuildMiss(result)) throw result.error;
|
|
1023
1042
|
return result;
|
|
1024
1043
|
}
|
|
1025
1044
|
|
|
@@ -1329,13 +1348,13 @@ export class BeaconChain implements IBeaconChain {
|
|
|
1329
1348
|
};
|
|
1330
1349
|
|
|
1331
1350
|
try {
|
|
1332
|
-
await validateApiProposerSlashing(this, proposerSlashing);
|
|
1351
|
+
const verifiedDomain = await validateApiProposerSlashing(this, proposerSlashing);
|
|
1352
|
+
this.opPool.insertProposerSlashing(proposerSlashing, verifiedDomain);
|
|
1333
1353
|
} catch (e) {
|
|
1334
1354
|
this.logger.debug("Produced proposer slashing is not valid", {slot: blockSlot, proposerIndex}, e as Error);
|
|
1335
1355
|
return;
|
|
1336
1356
|
}
|
|
1337
1357
|
|
|
1338
|
-
this.opPool.insertProposerSlashing(proposerSlashing);
|
|
1339
1358
|
this.emitter.emit(routes.events.EventType.proposerSlashing, proposerSlashing);
|
|
1340
1359
|
this.emitter.emit(ChainEvent.publishProposerSlashing, proposerSlashing);
|
|
1341
1360
|
this.metrics?.opPool.proposerSlashingsProduced.inc();
|
package/src/chain/emitter.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {EventEmitter} from "node:events";
|
|
2
2
|
import {StrictEventEmitter} from "strict-event-emitter-types";
|
|
3
3
|
import {routes} from "@lodestar/api";
|
|
4
|
-
import {CheckpointWithHex} from "@lodestar/fork-choice";
|
|
4
|
+
import {CheckpointWithHex, PtcQuorumEvent} from "@lodestar/fork-choice";
|
|
5
5
|
import {IBeaconStateView} from "@lodestar/state-transition";
|
|
6
6
|
import {DataColumnSidecar, RootHex, Slot, deneb, phase0} from "@lodestar/types";
|
|
7
7
|
import {PeerIdStr} from "../util/peerId.js";
|
|
@@ -36,6 +36,11 @@ export enum ChainEvent {
|
|
|
36
36
|
* This event is guaranteed to be triggered whenever the fork choice finalized checkpoint is updated. This is either in response to a newly processed block or a new clock tick.
|
|
37
37
|
*/
|
|
38
38
|
forkChoiceFinalized = "forkChoice:finalized",
|
|
39
|
+
/**
|
|
40
|
+
* This event signals that the PTC verdict on a block was decided: a majority against payload presence or blob data
|
|
41
|
+
* availability, or a majority for both.
|
|
42
|
+
*/
|
|
43
|
+
forkChoicePtcQuorum = "forkChoice:ptcQuorum",
|
|
39
44
|
/**
|
|
40
45
|
* This event signals that dependent services (e.g. custody sampling) should update to account for the new target group count.
|
|
41
46
|
*/
|
|
@@ -112,6 +117,7 @@ export type IChainEvents = ApiEvents & {
|
|
|
112
117
|
|
|
113
118
|
[ChainEvent.forkChoiceJustified]: (checkpoint: CheckpointWithHex) => void;
|
|
114
119
|
[ChainEvent.forkChoiceFinalized]: (checkpoint: CheckpointWithHex) => void;
|
|
120
|
+
[ChainEvent.forkChoicePtcQuorum]: (data: PtcQuorumEvent) => void;
|
|
115
121
|
|
|
116
122
|
[ChainEvent.updateTargetCustodyGroupCount]: (targetGroupCount: number) => void;
|
|
117
123
|
|
|
@@ -4,6 +4,8 @@ import {LodestarError} from "@lodestar/utils";
|
|
|
4
4
|
export enum EnvelopeReconstructionErrorCode {
|
|
5
5
|
/** The EL failed to answer engine_getPayloadBodiesByHashV2 (transport / EL down). Transient. */
|
|
6
6
|
ENGINE_UNAVAILABLE = "ENVELOPE_RECONSTRUCTION_ERROR_ENGINE_UNAVAILABLE",
|
|
7
|
+
/** The EL does not have the block or its block access list (pruned, or not yet re-indexed after a restart). May recover. */
|
|
8
|
+
BODY_UNAVAILABLE = "ENVELOPE_RECONSTRUCTION_ERROR_BODY_UNAVAILABLE",
|
|
7
9
|
/** A body returned by the EL does not hash to the root stored in the header envelope. Local DB/EL inconsistency. */
|
|
8
10
|
BODY_ROOT_MISMATCH = "ENVELOPE_RECONSTRUCTION_ERROR_BODY_ROOT_MISMATCH",
|
|
9
11
|
/** A by-range stream stopped short at a slot that cannot be served; the response so far is still valid */
|
|
@@ -12,6 +14,7 @@ export enum EnvelopeReconstructionErrorCode {
|
|
|
12
14
|
|
|
13
15
|
export type EnvelopeReconstructionErrorType =
|
|
14
16
|
| {code: EnvelopeReconstructionErrorCode.ENGINE_UNAVAILABLE}
|
|
17
|
+
| {code: EnvelopeReconstructionErrorCode.BODY_UNAVAILABLE; slot: Slot}
|
|
15
18
|
| {
|
|
16
19
|
code: EnvelopeReconstructionErrorCode.BODY_ROOT_MISMATCH;
|
|
17
20
|
slot: Slot;
|
|
@@ -9,6 +9,8 @@ export enum GossipErrorCode {
|
|
|
9
9
|
INVALID_SERIALIZED_BYTES_ERROR_CODE = "GOSSIP_ERROR_INVALID_SERIALIZED_BYTES",
|
|
10
10
|
PAST_SLOT = "GOSSIP_ERROR_PAST_SLOT",
|
|
11
11
|
FUTURE_SLOT = "GOSSIP_ERROR_FUTURE_SLOT",
|
|
12
|
+
FATAL_PEER = "GOSSIP_ERROR_FATAL_PEER",
|
|
13
|
+
INVALID_SLOT = "GOSSIP_ERROR_INVALID_SLOT",
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
export class GossipActionError<T extends {code: string}> extends LodestarError<T> {
|
|
@@ -124,6 +124,7 @@ export function initializeForkChoiceFromFinalizedState(
|
|
|
124
124
|
onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
|
|
125
125
|
onFastConfirmation: ({block, slot, currentSlot}) =>
|
|
126
126
|
emitter.emit(routes.events.EventType.fastConfirmation, {block, slot, currentSlot}),
|
|
127
|
+
onPtcQuorum: (data) => emitter.emit(ChainEvent.forkChoicePtcQuorum, data),
|
|
127
128
|
}
|
|
128
129
|
),
|
|
129
130
|
|
|
@@ -222,6 +223,7 @@ export function initializeForkChoiceFromUnfinalizedState(
|
|
|
222
223
|
onFinalized: (cp) => emitter.emit(ChainEvent.forkChoiceFinalized, cp),
|
|
223
224
|
onFastConfirmation: ({block, slot, currentSlot}) =>
|
|
224
225
|
emitter.emit(routes.events.EventType.fastConfirmation, {block, slot, currentSlot}),
|
|
226
|
+
onPtcQuorum: (data) => emitter.emit(ChainEvent.forkChoicePtcQuorum, data),
|
|
225
227
|
}
|
|
226
228
|
);
|
|
227
229
|
|
package/src/chain/initState.ts
CHANGED
|
@@ -1,14 +1,8 @@
|
|
|
1
1
|
import {ChainForkConfig} from "@lodestar/config";
|
|
2
2
|
import {ForkPostGloas, ForkSeq, ZERO_HASH} from "@lodestar/params";
|
|
3
|
-
import {
|
|
4
|
-
BeaconStateAllForks,
|
|
5
|
-
BeaconStateGloas,
|
|
6
|
-
IBeaconStateView,
|
|
7
|
-
computeEpochAtSlot,
|
|
8
|
-
computeStartSlotAtEpoch,
|
|
9
|
-
} from "@lodestar/state-transition";
|
|
3
|
+
import {BeaconStateAllForks, IBeaconStateView, computeEpochAtSlot, isStatePostGloas} from "@lodestar/state-transition";
|
|
10
4
|
import {SignedBeaconBlock, ssz} from "@lodestar/types";
|
|
11
|
-
import {Logger, byteArrayEquals,
|
|
5
|
+
import {LodestarError, Logger, byteArrayEquals, toRootHex} from "@lodestar/utils";
|
|
12
6
|
import {GENESIS_SLOT} from "../constants/index.js";
|
|
13
7
|
import {IBeaconDb} from "../db/index.js";
|
|
14
8
|
import {Metrics} from "../metrics/index.js";
|
|
@@ -17,7 +11,7 @@ import {getStateTypeFromBytes} from "../util/multifork.js";
|
|
|
17
11
|
export async function persistAnchorState(
|
|
18
12
|
config: ChainForkConfig,
|
|
19
13
|
db: IBeaconDb,
|
|
20
|
-
anchorState:
|
|
14
|
+
anchorState: IBeaconStateView,
|
|
21
15
|
anchorStateBytes: Uint8Array
|
|
22
16
|
): Promise<void> {
|
|
23
17
|
if (anchorState.slot === GENESIS_SLOT) {
|
|
@@ -33,7 +27,8 @@ export async function persistAnchorState(
|
|
|
33
27
|
const latestBlockRoot = ssz.phase0.BeaconBlockHeader.hashTreeRoot(latestBlockHeader);
|
|
34
28
|
|
|
35
29
|
if (!byteArrayEquals(blockRoot, latestBlockRoot)) {
|
|
36
|
-
throw
|
|
30
|
+
throw new LodestarError(
|
|
31
|
+
{code: "GENESIS_BLOCK_ROOT_MISMATCH"},
|
|
37
32
|
`Genesis block root ${toRootHex(blockRoot)} does not match genesis state latest block root ${toRootHex(latestBlockRoot)}`
|
|
38
33
|
);
|
|
39
34
|
}
|
|
@@ -48,16 +43,15 @@ export async function persistAnchorState(
|
|
|
48
43
|
}
|
|
49
44
|
}
|
|
50
45
|
|
|
51
|
-
export function createGenesisBlock(config: ChainForkConfig, genesisState:
|
|
46
|
+
export function createGenesisBlock(config: ChainForkConfig, genesisState: IBeaconStateView): SignedBeaconBlock {
|
|
52
47
|
const types = config.getForkTypes(GENESIS_SLOT);
|
|
53
48
|
const genesisBlock = types.SignedBeaconBlock.defaultValue();
|
|
54
49
|
const stateRoot = genesisState.hashTreeRoot();
|
|
55
50
|
genesisBlock.message.stateRoot = stateRoot;
|
|
56
51
|
|
|
57
|
-
if (config.getForkSeq(GENESIS_SLOT) >= ForkSeq.gloas) {
|
|
52
|
+
if (config.getForkSeq(GENESIS_SLOT) >= ForkSeq.gloas && isStatePostGloas(genesisState)) {
|
|
58
53
|
const gloasBlock = genesisBlock as SignedBeaconBlock<ForkPostGloas>;
|
|
59
|
-
|
|
60
|
-
gloasBlock.message.body.signedExecutionPayloadBid.message = gloasState.latestExecutionPayloadBid.toValue();
|
|
54
|
+
gloasBlock.message.body.signedExecutionPayloadBid.message = genesisState.latestExecutionPayloadBid;
|
|
61
55
|
}
|
|
62
56
|
|
|
63
57
|
return genesisBlock;
|
|
@@ -86,52 +80,6 @@ export async function initStateFromDb(
|
|
|
86
80
|
return state;
|
|
87
81
|
}
|
|
88
82
|
|
|
89
|
-
/**
|
|
90
|
-
* Initialize and persist an anchor state (either weak subjectivity or genesis)
|
|
91
|
-
*/
|
|
92
|
-
export async function checkAndPersistAnchorState(
|
|
93
|
-
config: ChainForkConfig,
|
|
94
|
-
db: IBeaconDb,
|
|
95
|
-
logger: Logger,
|
|
96
|
-
anchorState: BeaconStateAllForks,
|
|
97
|
-
anchorStateBytes: Uint8Array,
|
|
98
|
-
{
|
|
99
|
-
isWithinWeakSubjectivityPeriod,
|
|
100
|
-
isCheckpointState,
|
|
101
|
-
}: {isWithinWeakSubjectivityPeriod: boolean; isCheckpointState: boolean}
|
|
102
|
-
): Promise<void> {
|
|
103
|
-
const expectedFork = config.getForkInfo(computeStartSlotAtEpoch(anchorState.fork.epoch));
|
|
104
|
-
const expectedForkVersion = toHex(expectedFork.version);
|
|
105
|
-
const stateFork = toHex(anchorState.fork.currentVersion);
|
|
106
|
-
if (stateFork !== expectedForkVersion) {
|
|
107
|
-
throw Error(
|
|
108
|
-
`State current fork version ${stateFork} not equal to current config ${expectedForkVersion}. Maybe caused by importing a state from a different network`
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const stateInfo = isCheckpointState ? "checkpoint" : "db";
|
|
113
|
-
if (isWithinWeakSubjectivityPeriod) {
|
|
114
|
-
logger.info(`Initializing beacon from a valid ${stateInfo} state`, {
|
|
115
|
-
slot: anchorState.slot,
|
|
116
|
-
epoch: computeEpochAtSlot(anchorState.slot),
|
|
117
|
-
stateRoot: toRootHex(anchorState.hashTreeRoot()),
|
|
118
|
-
isWithinWeakSubjectivityPeriod,
|
|
119
|
-
});
|
|
120
|
-
} else {
|
|
121
|
-
logger.warn(`Initializing from a stale ${stateInfo} state vulnerable to long range attacks`, {
|
|
122
|
-
slot: anchorState.slot,
|
|
123
|
-
epoch: computeEpochAtSlot(anchorState.slot),
|
|
124
|
-
stateRoot: toRootHex(anchorState.hashTreeRoot()),
|
|
125
|
-
isWithinWeakSubjectivityPeriod,
|
|
126
|
-
});
|
|
127
|
-
logger.warn("Checkpoint sync recommended, please use --help to see checkpoint sync options");
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
if (isCheckpointState || anchorState.slot === GENESIS_SLOT) {
|
|
131
|
-
await persistAnchorState(config, db, anchorState, anchorStateBytes);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
83
|
export function initBeaconMetrics(metrics: Metrics, state: IBeaconStateView): void {
|
|
136
84
|
metrics.headSlot.set(state.slot);
|
|
137
85
|
metrics.previousJustifiedEpoch.set(state.previousJustifiedCheckpoint.epoch);
|
package/src/chain/interface.ts
CHANGED
|
@@ -30,7 +30,7 @@ import {Metrics} from "../metrics/metrics.js";
|
|
|
30
30
|
import {BufferPool} from "../util/bufferPool.js";
|
|
31
31
|
import {IClock} from "../util/clock.js";
|
|
32
32
|
import {CustodyConfig} from "../util/dataColumns.js";
|
|
33
|
-
import {
|
|
33
|
+
import {ReconstructMissPolicy} from "../util/execution.js";
|
|
34
34
|
import {SerializedCache} from "../util/serializedCache.js";
|
|
35
35
|
import {IArchiveStore} from "./archiveStore/interface.js";
|
|
36
36
|
import {CheckpointBalancesCache} from "./balancesCache.js";
|
|
@@ -239,7 +239,7 @@ export interface IBeaconChain {
|
|
|
239
239
|
getSerializedExecutionPayloadEnvelope(blockSlot: Slot, blockRootHex: string): Promise<Uint8Array | null>;
|
|
240
240
|
getSerializedExecutionPayloadEnvelopes(
|
|
241
241
|
requests: {blockSlot: Slot; blockRootHex: RootHex}[],
|
|
242
|
-
|
|
242
|
+
onMiss?: ReconstructMissPolicy
|
|
243
243
|
): Promise<(Uint8Array | null)[]>;
|
|
244
244
|
getExecutionPayloadEnvelope(
|
|
245
245
|
blockSlot: Slot,
|
|
@@ -12,38 +12,54 @@ import {
|
|
|
12
12
|
} from "@lodestar/params";
|
|
13
13
|
import {
|
|
14
14
|
IBeaconStateView,
|
|
15
|
+
assertValidAttesterSlashing,
|
|
15
16
|
computeEpochAtSlot,
|
|
16
17
|
computeStartSlotAtEpoch,
|
|
17
18
|
getAttesterSlashableIndices,
|
|
19
|
+
getAttesterSlashingSignatureSets,
|
|
20
|
+
getProposerSlashingSignatureSets,
|
|
18
21
|
} from "@lodestar/state-transition";
|
|
19
22
|
import {
|
|
20
23
|
AttesterSlashing,
|
|
24
|
+
Domain,
|
|
21
25
|
Epoch,
|
|
22
26
|
SignedBeaconBlock,
|
|
27
|
+
Slot,
|
|
23
28
|
ValidatorIndex,
|
|
24
29
|
capella,
|
|
25
30
|
phase0,
|
|
31
|
+
ssz,
|
|
26
32
|
sszTypesFor,
|
|
27
33
|
} from "@lodestar/types";
|
|
28
|
-
import {fromHex, toHex, toRootHex} from "@lodestar/utils";
|
|
34
|
+
import {byteArrayEquals, fromHex, toHex, toRootHex} from "@lodestar/utils";
|
|
29
35
|
import {IBeaconDb} from "../../db/index.js";
|
|
30
36
|
import {Metrics} from "../../metrics/metrics.js";
|
|
31
37
|
import {SignedBLSToExecutionChangeVersioned} from "../../util/types.js";
|
|
38
|
+
import {IBlsVerifier} from "../bls/interface.js";
|
|
32
39
|
import {BlockType} from "../interface.js";
|
|
33
40
|
import {BlockProductionStep} from "../produceBlock/produceBlockBody.js";
|
|
34
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
getAttesterSlashingSignatureDomains,
|
|
43
|
+
getProposerSlashingSignatureDomain,
|
|
44
|
+
isValidBlsToExecutionChangeForBlockInclusion,
|
|
45
|
+
} from "./utils.js";
|
|
35
46
|
|
|
36
47
|
type HexRoot = string;
|
|
37
48
|
type AttesterSlashingCached = {
|
|
38
49
|
attesterSlashing: AttesterSlashing;
|
|
39
50
|
intersectingIndices: number[];
|
|
51
|
+
verifiedDomains: [Domain, Domain];
|
|
52
|
+
};
|
|
53
|
+
type ProposerSlashingCached = {
|
|
54
|
+
proposerSlashing: phase0.ProposerSlashing;
|
|
55
|
+
verifiedDomain: Domain;
|
|
40
56
|
};
|
|
41
57
|
|
|
42
58
|
export class OpPool {
|
|
43
59
|
/** Map of uniqueId(AttesterSlashing) -> AttesterSlashing */
|
|
44
60
|
private readonly attesterSlashings = new Map<HexRoot, AttesterSlashingCached>();
|
|
45
61
|
/** Map of to slash validator index -> ProposerSlashing */
|
|
46
|
-
private readonly proposerSlashings = new Map<ValidatorIndex,
|
|
62
|
+
private readonly proposerSlashings = new Map<ValidatorIndex, ProposerSlashingCached>();
|
|
47
63
|
/** Map of to exit validator index -> SignedVoluntaryExit */
|
|
48
64
|
private readonly voluntaryExits = new Map<ValidatorIndex, phase0.SignedVoluntaryExit>();
|
|
49
65
|
/** Set of seen attester slashing indexes. No need to prune */
|
|
@@ -68,7 +84,7 @@ export class OpPool {
|
|
|
68
84
|
return this.blsToExecutionChanges.size;
|
|
69
85
|
}
|
|
70
86
|
|
|
71
|
-
async fromPersisted(db: IBeaconDb): Promise<void> {
|
|
87
|
+
async fromPersisted(db: IBeaconDb, state: IBeaconStateView, bls: IBlsVerifier, currentSlot: Slot): Promise<void> {
|
|
72
88
|
const [attesterSlashings, proposerSlashings, voluntaryExits, blsToExecutionChanges] = await Promise.all([
|
|
73
89
|
db.attesterSlashing.entries(),
|
|
74
90
|
db.proposerSlashing.values(),
|
|
@@ -76,11 +92,42 @@ export class OpPool {
|
|
|
76
92
|
db.blsToExecutionChange.values(),
|
|
77
93
|
]);
|
|
78
94
|
|
|
79
|
-
|
|
80
|
-
|
|
95
|
+
// Persisted operations have no verification context. Rebuild it before they can enter the pool.
|
|
96
|
+
// The startup anchor may precede a fork whose slashings are already persisted.
|
|
97
|
+
for (const {key, value: attesterSlashing} of attesterSlashings) {
|
|
98
|
+
try {
|
|
99
|
+
assertValidAttesterSlashing(this.config, currentSlot, state.validatorCount, attesterSlashing, false);
|
|
100
|
+
} catch {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const verifiedDomains = getAttesterSlashingSignatureDomains(this.config, currentSlot, attesterSlashing);
|
|
104
|
+
if (
|
|
105
|
+
!(await bls.verifySignatureSets(getAttesterSlashingSignatureSets(this.config, currentSlot, attesterSlashing)))
|
|
106
|
+
) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
this.insertAttesterSlashing(ForkName.electra, attesterSlashing, verifiedDomains, key);
|
|
81
110
|
}
|
|
82
111
|
for (const proposerSlashing of proposerSlashings) {
|
|
83
|
-
|
|
112
|
+
const header1 = proposerSlashing.signedHeader1.message;
|
|
113
|
+
const header2 = proposerSlashing.signedHeader2.message;
|
|
114
|
+
// Startup may restore a state from before activation. Current slashability is checked
|
|
115
|
+
// when selecting operations, so it must not prevent restoring an otherwise valid slashing.
|
|
116
|
+
if (
|
|
117
|
+
header1.slot !== header2.slot ||
|
|
118
|
+
header1.proposerIndex !== header2.proposerIndex ||
|
|
119
|
+
header1.proposerIndex >= state.validatorCount ||
|
|
120
|
+
ssz.phase0.BeaconBlockHeaderBigint.equals(header1, header2)
|
|
121
|
+
) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const verifiedDomain = getProposerSlashingSignatureDomain(this.config, currentSlot, proposerSlashing);
|
|
125
|
+
if (
|
|
126
|
+
!(await bls.verifySignatureSets(getProposerSlashingSignatureSets(this.config, currentSlot, proposerSlashing)))
|
|
127
|
+
) {
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
this.insertProposerSlashing(proposerSlashing, verifiedDomain);
|
|
84
131
|
}
|
|
85
132
|
for (const voluntaryExit of voluntaryExits) {
|
|
86
133
|
this.insertVoluntaryExit(voluntaryExit);
|
|
@@ -102,8 +149,12 @@ export class OpPool {
|
|
|
102
149
|
),
|
|
103
150
|
persistDiff(
|
|
104
151
|
db.proposerSlashing,
|
|
105
|
-
Array.from(this.proposerSlashings.entries()).map(([key, value]) => ({key, value})),
|
|
106
|
-
(index) => index
|
|
152
|
+
Array.from(this.proposerSlashings.entries()).map(([key, value]) => ({key, value: value.proposerSlashing})),
|
|
153
|
+
(index) => index,
|
|
154
|
+
// A slashing invalidated by a fork may remain on disk after eviction from the pool.
|
|
155
|
+
// A new valid slashing for that validator has the same database key, so comparing
|
|
156
|
+
// keys alone would retain the invalid slashing and lose the replacement on restart.
|
|
157
|
+
{updateExisting: true}
|
|
107
158
|
),
|
|
108
159
|
persistDiff(
|
|
109
160
|
db.voluntaryExit,
|
|
@@ -143,7 +194,12 @@ export class OpPool {
|
|
|
143
194
|
}
|
|
144
195
|
|
|
145
196
|
/** Must be validated beforehand */
|
|
146
|
-
insertAttesterSlashing(
|
|
197
|
+
insertAttesterSlashing(
|
|
198
|
+
fork: ForkName,
|
|
199
|
+
attesterSlashing: AttesterSlashing,
|
|
200
|
+
verifiedDomains: [Domain, Domain],
|
|
201
|
+
rootHash?: Uint8Array
|
|
202
|
+
): void {
|
|
147
203
|
if (!rootHash) {
|
|
148
204
|
rootHash = sszTypesFor(fork).AttesterSlashing.hashTreeRoot(attesterSlashing);
|
|
149
205
|
}
|
|
@@ -153,6 +209,7 @@ export class OpPool {
|
|
|
153
209
|
this.attesterSlashings.set(toRootHex(rootHash), {
|
|
154
210
|
attesterSlashing,
|
|
155
211
|
intersectingIndices,
|
|
212
|
+
verifiedDomains,
|
|
156
213
|
});
|
|
157
214
|
for (const index of intersectingIndices) {
|
|
158
215
|
this.attesterSlashingIndexes.add(index);
|
|
@@ -160,8 +217,11 @@ export class OpPool {
|
|
|
160
217
|
}
|
|
161
218
|
|
|
162
219
|
/** Must be validated beforehand */
|
|
163
|
-
insertProposerSlashing(proposerSlashing: phase0.ProposerSlashing): void {
|
|
164
|
-
this.proposerSlashings.set(proposerSlashing.signedHeader1.message.proposerIndex,
|
|
220
|
+
insertProposerSlashing(proposerSlashing: phase0.ProposerSlashing, verifiedDomain: Domain): void {
|
|
221
|
+
this.proposerSlashings.set(proposerSlashing.signedHeader1.message.proposerIndex, {
|
|
222
|
+
proposerSlashing,
|
|
223
|
+
verifiedDomain,
|
|
224
|
+
});
|
|
165
225
|
}
|
|
166
226
|
|
|
167
227
|
/** Must be validated beforehand */
|
|
@@ -204,7 +264,14 @@ export class OpPool {
|
|
|
204
264
|
: metrics?.builderBlockProductionTimeSteps;
|
|
205
265
|
|
|
206
266
|
const endProposerSlashing = stepsMetrics?.startTimer();
|
|
207
|
-
for (const proposerSlashing of this.proposerSlashings
|
|
267
|
+
for (const [key, {proposerSlashing, verifiedDomain}] of this.proposerSlashings) {
|
|
268
|
+
// A fork can change the required domain even for verified slashings, including those with future-slot headers.
|
|
269
|
+
if (
|
|
270
|
+
!byteArrayEquals(verifiedDomain, getProposerSlashingSignatureDomain(this.config, state.slot, proposerSlashing))
|
|
271
|
+
) {
|
|
272
|
+
this.proposerSlashings.delete(key);
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
208
275
|
const index = proposerSlashing.signedHeader1.message.proposerIndex;
|
|
209
276
|
const validator = state.getValidator(index);
|
|
210
277
|
if (!validator.slashed && validator.activationEpoch <= stateEpoch && stateEpoch < validator.withdrawableEpoch) {
|
|
@@ -223,7 +290,12 @@ export class OpPool {
|
|
|
223
290
|
const endAttesterSlashings = stepsMetrics?.startTimer();
|
|
224
291
|
const attesterSlashings: AttesterSlashing[] = [];
|
|
225
292
|
const maxAttesterSlashings = stateFork >= ForkSeq.electra ? MAX_ATTESTER_SLASHINGS_ELECTRA : MAX_ATTESTER_SLASHINGS;
|
|
226
|
-
attesterSlashing: for (const attesterSlashing of this.attesterSlashings
|
|
293
|
+
attesterSlashing: for (const [key, attesterSlashing] of this.attesterSlashings) {
|
|
294
|
+
const domains = getAttesterSlashingSignatureDomains(this.config, state.slot, attesterSlashing.attesterSlashing);
|
|
295
|
+
if (!attesterSlashing.verifiedDomains.every((domain, i) => byteArrayEquals(domain, domains[i]))) {
|
|
296
|
+
this.attesterSlashings.delete(key);
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
227
299
|
/** Indices slashable in this attester slashing */
|
|
228
300
|
const slashableIndices = new Set<ValidatorIndex>();
|
|
229
301
|
for (let i = 0; i < attesterSlashing.intersectingIndices.length; i++) {
|
|
@@ -303,7 +375,7 @@ export class OpPool {
|
|
|
303
375
|
|
|
304
376
|
/** For beacon pool API */
|
|
305
377
|
getAllProposerSlashings(): phase0.ProposerSlashing[] {
|
|
306
|
-
return Array.from(this.proposerSlashings.values());
|
|
378
|
+
return Array.from(this.proposerSlashings.values()).map(({proposerSlashing}) => proposerSlashing);
|
|
307
379
|
}
|
|
308
380
|
|
|
309
381
|
/** For beacon pool API */
|
|
@@ -356,7 +428,7 @@ export class OpPool {
|
|
|
356
428
|
*/
|
|
357
429
|
private pruneProposerSlashings(headState: IBeaconStateView): void {
|
|
358
430
|
const finalizedEpoch = headState.finalizedCheckpoint.epoch;
|
|
359
|
-
for (const [key, proposerSlashing] of this.proposerSlashings.entries()) {
|
|
431
|
+
for (const [key, {proposerSlashing}] of this.proposerSlashings.entries()) {
|
|
360
432
|
const index = proposerSlashing.signedHeader1.message.proposerIndex;
|
|
361
433
|
if (headState.getValidator(index).exitEpoch <= finalizedEpoch) {
|
|
362
434
|
this.proposerSlashings.delete(key);
|
|
@@ -436,14 +508,15 @@ function isSlashableAtEpoch(validator: phase0.Validator, epoch: Epoch): boolean
|
|
|
436
508
|
async function persistDiff<K extends Id, V>(
|
|
437
509
|
dbRepo: Repository<K, V>,
|
|
438
510
|
items: {key: K; value: V}[],
|
|
439
|
-
serializeKey: (key: K) => number | string
|
|
511
|
+
serializeKey: (key: K) => number | string,
|
|
512
|
+
opts: {updateExisting?: boolean} = {}
|
|
440
513
|
): Promise<void> {
|
|
441
514
|
const persistedKeys = await dbRepo.keys();
|
|
442
515
|
const batch: DbBatch<K, V> = [];
|
|
443
516
|
|
|
444
517
|
const persistedKeysSerialized = new Set(persistedKeys.map(serializeKey));
|
|
445
518
|
for (const item of items) {
|
|
446
|
-
if (!persistedKeysSerialized.has(serializeKey(item.key))) {
|
|
519
|
+
if (opts.updateExisting || !persistedKeysSerialized.has(serializeKey(item.key))) {
|
|
447
520
|
batch.push({type: "put", key: item.key, value: item.value});
|
|
448
521
|
}
|
|
449
522
|
}
|
|
@@ -6,8 +6,8 @@ import {toRootHex} from "@lodestar/utils";
|
|
|
6
6
|
*
|
|
7
7
|
* The primary consumer is `validateExecutionPayloadBid`, which looks up the matching
|
|
8
8
|
* preferences via `get(bid.slot, dependent_root)` to enforce the IGNORE-existence and
|
|
9
|
-
* REJECT-equality rules from the gloas spec. The beacon API
|
|
10
|
-
*
|
|
9
|
+
* REJECT-equality rules from the gloas spec. The beacon API `getProposerPreferences`
|
|
10
|
+
* endpoint reads from the same pool via `getAll`.
|
|
11
11
|
*
|
|
12
12
|
* `validator_index` is intentionally not part of the key: gossip validation enforces
|
|
13
13
|
* `proposers[proposalSlot % SLOTS_PER_EPOCH] === validatorIndex` against the shuffling
|
|
@@ -37,15 +37,19 @@ export class ProposerPreferencesPool {
|
|
|
37
37
|
byRoot.set(rootHex, signed);
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
/** API read-out: flatten across branches, optionally filtered by slot. */
|
|
41
|
-
getAll(slot?: Slot): gloas.SignedProposerPreferences[] {
|
|
42
|
-
if (slot !== undefined) {
|
|
43
|
-
const byRoot = this.bySlot.get(slot);
|
|
44
|
-
return byRoot ? Array.from(byRoot.values()) : [];
|
|
45
|
-
}
|
|
40
|
+
/** API read-out: flatten across branches, optionally filtered by slot and dependent root. */
|
|
41
|
+
getAll(slot?: Slot, dependentRootHex?: RootHex): gloas.SignedProposerPreferences[] {
|
|
46
42
|
const out: gloas.SignedProposerPreferences[] = [];
|
|
47
|
-
for (const byRoot of this.bySlot
|
|
48
|
-
|
|
43
|
+
for (const [s, byRoot] of this.bySlot) {
|
|
44
|
+
if (slot !== undefined && s !== slot) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
for (const [root, signed] of byRoot) {
|
|
48
|
+
if (dependentRootHex !== undefined && root !== dependentRootHex) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
out.push(signed);
|
|
52
|
+
}
|
|
49
53
|
}
|
|
50
54
|
return out;
|
|
51
55
|
}
|
|
@@ -1,9 +1,37 @@
|
|
|
1
1
|
import {Signature} from "@chainsafe/lodestar-z/blst";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import {BeaconConfig} from "@lodestar/config";
|
|
3
|
+
import {BLS_WITHDRAWAL_PREFIX, DOMAIN_BEACON_ATTESTER, DOMAIN_BEACON_PROPOSER} from "@lodestar/params";
|
|
4
|
+
import {IBeaconStateView, computeStartSlotAtEpoch} from "@lodestar/state-transition";
|
|
5
|
+
import {AttesterSlashing, Domain, Slot, capella, phase0} from "@lodestar/types";
|
|
5
6
|
import {AggregateFast, AggregateFastElectra} from "./attestationPool.js";
|
|
6
7
|
|
|
8
|
+
export function getProposerSlashingSignatureDomain(
|
|
9
|
+
config: BeaconConfig,
|
|
10
|
+
stateSlot: Slot,
|
|
11
|
+
slashing: phase0.ProposerSlashing
|
|
12
|
+
): Domain {
|
|
13
|
+
return config.getDomain(stateSlot, DOMAIN_BEACON_PROPOSER, Number(slashing.signedHeader1.message.slot));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function getAttesterSlashingSignatureDomains(
|
|
17
|
+
config: BeaconConfig,
|
|
18
|
+
stateSlot: Slot,
|
|
19
|
+
slashing: AttesterSlashing
|
|
20
|
+
): [Domain, Domain] {
|
|
21
|
+
return [
|
|
22
|
+
config.getDomain(
|
|
23
|
+
stateSlot,
|
|
24
|
+
DOMAIN_BEACON_ATTESTER,
|
|
25
|
+
computeStartSlotAtEpoch(Number(slashing.attestation1.data.target.epoch))
|
|
26
|
+
),
|
|
27
|
+
config.getDomain(
|
|
28
|
+
stateSlot,
|
|
29
|
+
DOMAIN_BEACON_ATTESTER,
|
|
30
|
+
computeStartSlotAtEpoch(Number(slashing.attestation2.data.target.epoch))
|
|
31
|
+
),
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
|
|
7
35
|
/**
|
|
8
36
|
* Prune a Map indexed by slot to keep the most recent slots, up to `slotsRetained`
|
|
9
37
|
*/
|
package/src/chain/options.ts
CHANGED
|
@@ -52,7 +52,7 @@ export type IChainOptions = BlockProcessOpts &
|
|
|
52
52
|
minSameMessageSignatureSetsToBatch: number;
|
|
53
53
|
archiveDateEpochs?: number;
|
|
54
54
|
nHistoricalStatesFileDataStore?: boolean;
|
|
55
|
-
|
|
55
|
+
nativeStateTransition?: boolean;
|
|
56
56
|
/** Builder circuit breaker fault inspection window in slots */
|
|
57
57
|
faultInspectionWindow?: number;
|
|
58
58
|
/** Canonical EMPTY blocks allowed per `faultInspectionWindow` observed blocks */
|
|
@@ -131,7 +131,7 @@ export const defaultChainOptions: IChainOptions = {
|
|
|
131
131
|
// - users can prune the persisted checkpoint state files manually to save disc space
|
|
132
132
|
// - it helps debug easier when network is unfinalized
|
|
133
133
|
nHistoricalStatesFileDataStore: true,
|
|
134
|
-
|
|
134
|
+
nativeStateTransition: false,
|
|
135
135
|
maxBlockStates: DEFAULT_MAX_BLOCK_STATES,
|
|
136
136
|
maxCPStateEpochsInMemory: DEFAULT_MAX_CP_STATE_EPOCHS_IN_MEMORY,
|
|
137
137
|
maxCPStateEpochsOnDisk: DEFAULT_MAX_CP_STATE_ON_DISK,
|