From b78cb92802cfc12c3a75f33a053055e3db5a7c45 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:20:52 +0700 Subject: [PATCH 01/43] feat: remove unfinalized pubkey cache (#7230) * Remove unfinalized pubkey cache * lint * Fix unit test --- packages/beacon-node/src/chain/chain.ts | 22 --- .../beacon-node/src/chain/regen/queued.ts | 50 +----- .../chain/stateCache/blockStateCacheImpl.ts | 2 +- .../beacon-node/src/metrics/metrics/beacon.ts | 7 - .../src/metrics/metrics/lodestar.ts | 11 -- .../test/memory/unfinalizedPubkey2Index.ts | 54 ------- .../updateUnfinalizedPubkeys.test.ts | 114 -------------- .../test/sim/electra-interop.test.ts | 31 +--- packages/state-transition/package.json | 3 +- .../state-transition/src/cache/epochCache.ts | 146 +----------------- .../state-transition/src/cache/pubkeyCache.ts | 36 ----- packages/state-transition/src/index.ts | 7 +- packages/state-transition/src/metrics.ts | 5 - .../test/unit/cachedBeaconState.test.ts | 36 +---- yarn.lock | 5 - 15 files changed, 15 insertions(+), 514 deletions(-) delete mode 100644 packages/beacon-node/test/memory/unfinalizedPubkey2Index.ts delete mode 100644 packages/beacon-node/test/perf/chain/stateCache/updateUnfinalizedPubkeys.test.ts diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 8b9fa0336283..4751770b3bfc 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -1047,9 +1047,6 @@ export class BeaconChain implements IBeaconChain { metrics.forkChoice.balancesLength.set(forkChoiceMetrics.balancesLength); metrics.forkChoice.nodes.set(forkChoiceMetrics.nodes); metrics.forkChoice.indices.set(forkChoiceMetrics.indices); - - const headState = this.getHeadState(); - metrics.headState.unfinalizedPubkeyCacheSize.set(headState.epochCtx.unfinalizedPubkey2index.size); } private onClockSlot(slot: Slot): void { @@ -1139,27 +1136,8 @@ export class BeaconChain implements IBeaconChain { this.opPool.pruneAll(headBlock, headState); } - const cpEpoch = cp.epoch; - if (headState === null) { this.logger.verbose("Head state is null"); - } else if (cpEpoch >= this.config.ELECTRA_FORK_EPOCH) { - // Get the validator.length from the state at cpEpoch - // We are confident the last element in the list is from headEpoch - // Thus we query from the end of the list. (cpEpoch - headEpoch - 1) is negative number - const pivotValidatorIndex = headState.epochCtx.getValidatorCountAtEpoch(cpEpoch); - - if (pivotValidatorIndex !== undefined) { - // Note EIP-6914 will break this logic - const newFinalizedValidators = headState.epochCtx.unfinalizedPubkey2index.filter( - (index, _pubkey) => index < pivotValidatorIndex - ); - - // Populate finalized pubkey cache and remove unfinalized pubkey cache - if (!newFinalizedValidators.isEmpty()) { - this.regen.updateUnfinalizedPubkeys(newFinalizedValidators); - } - } } // TODO-Electra: Deprecating eth1Data poll requires a check on a finalized checkpoint state. diff --git a/packages/beacon-node/src/chain/regen/queued.ts b/packages/beacon-node/src/chain/regen/queued.ts index 7b812c04dc8d..b5084d593356 100644 --- a/packages/beacon-node/src/chain/regen/queued.ts +++ b/packages/beacon-node/src/chain/regen/queued.ts @@ -1,6 +1,6 @@ import {routes} from "@lodestar/api"; import {IForkChoice, ProtoBlock} from "@lodestar/fork-choice"; -import {CachedBeaconStateAllForks, UnfinalizedPubkeyIndexMap, computeEpochAtSlot} from "@lodestar/state-transition"; +import {CachedBeaconStateAllForks, computeEpochAtSlot} from "@lodestar/state-transition"; import {BeaconBlock, Epoch, RootHex, Slot, phase0} from "@lodestar/types"; import {Logger, toRootHex} from "@lodestar/utils"; import {Metrics} from "../../metrics/index.js"; @@ -206,54 +206,6 @@ export class QueuedStateRegenerator implements IStateRegenerator { return this.checkpointStateCache.updatePreComputedCheckpoint(rootHex, epoch); } - /** - * Remove `validators` from all unfinalized cache's epochCtx.UnfinalizedPubkey2Index, - * and add them to epochCtx.pubkey2index and epochCtx.index2pubkey - */ - updateUnfinalizedPubkeys(validators: UnfinalizedPubkeyIndexMap): void { - let numStatesUpdated = 0; - const states = this.blockStateCache.getStates(); - const cpStates = this.checkpointStateCache.getStates(); - - // Add finalized pubkeys to all states. - const addTimer = this.metrics?.regenFnAddPubkeyTime.startTimer(); - - // We only need to add pubkeys to any one of the states since the finalized caches is shared globally across all states - const firstState = (states.next().value ?? cpStates.next().value) as CachedBeaconStateAllForks | undefined; - - if (firstState !== undefined) { - firstState.epochCtx.addFinalizedPubkeys(validators, this.metrics?.epochCache ?? undefined); - } else { - this.logger.warn("Attempt to delete finalized pubkey from unfinalized pubkey cache. But no state is available"); - } - - addTimer?.(); - - // Delete finalized pubkeys from unfinalized pubkey cache for all states - const deleteTimer = this.metrics?.regenFnDeletePubkeyTime.startTimer(); - const pubkeysToDelete = Array.from(validators.keys()); - - for (const s of states) { - s.epochCtx.deleteUnfinalizedPubkeys(pubkeysToDelete); - numStatesUpdated++; - } - - for (const s of cpStates) { - s.epochCtx.deleteUnfinalizedPubkeys(pubkeysToDelete); - numStatesUpdated++; - } - - // Since first state is consumed from the iterator. Will need to perform delete explicitly - if (firstState !== undefined) { - firstState?.epochCtx.deleteUnfinalizedPubkeys(pubkeysToDelete); - numStatesUpdated++; - } - - deleteTimer?.(); - - this.metrics?.regenFnNumStatesUpdated.observe(numStatesUpdated); - } - /** * Get the state to run with `block`. * - State after `block.parentRoot` dialed forward to block.slot diff --git a/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts b/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts index 886f6e386309..f57c9a411923 100644 --- a/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts +++ b/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts @@ -34,7 +34,7 @@ export class BlockStateCacheImpl implements BlockStateCache { this.maxStates = maxStates; this.cache = new MapTracker(metrics?.stateCache); if (metrics) { - this.metrics = {...metrics.stateCache, ...metrics.epochCache}; + this.metrics = metrics.stateCache; metrics.stateCache.size.addCollect(() => metrics.stateCache.size.set(this.cache.size)); } } diff --git a/packages/beacon-node/src/metrics/metrics/beacon.ts b/packages/beacon-node/src/metrics/metrics/beacon.ts index f572ec0d3c1f..60a49b0b673d 100644 --- a/packages/beacon-node/src/metrics/metrics/beacon.ts +++ b/packages/beacon-node/src/metrics/metrics/beacon.ts @@ -124,13 +124,6 @@ export function createBeaconMetrics(register: RegistryMetricCreator) { }), }, - headState: { - unfinalizedPubkeyCacheSize: register.gauge({ - name: "beacon_head_state_unfinalized_pubkey_cache_size", - help: "Current size of the unfinalizedPubkey2Index cache in the head state", - }), - }, - parentBlockDistance: register.histogram({ name: "beacon_imported_block_parent_distance", help: "Histogram of distance to parent block of valid imported blocks", diff --git a/packages/beacon-node/src/metrics/metrics/lodestar.ts b/packages/beacon-node/src/metrics/metrics/lodestar.ts index af8d87daa246..461f9c9e935b 100644 --- a/packages/beacon-node/src/metrics/metrics/lodestar.ts +++ b/packages/beacon-node/src/metrics/metrics/lodestar.ts @@ -378,17 +378,6 @@ export function createLodestarMetrics( help: "Total count state.validators nodesPopulated is false on stfn for post state", }), - epochCache: { - finalizedPubkeyDuplicateInsert: register.gauge({ - name: "lodestar_epoch_cache_finalized_pubkey_duplicate_insert_total", - help: "Total count of duplicate insert of finalized pubkeys", - }), - newUnFinalizedPubkey: register.gauge({ - name: "lodestar_epoch_cache_new_unfinalized_pubkey_total", - help: "Total count of unfinalized pubkeys added", - }), - }, - // BLS verifier thread pool and queue bls: { diff --git a/packages/beacon-node/test/memory/unfinalizedPubkey2Index.ts b/packages/beacon-node/test/memory/unfinalizedPubkey2Index.ts deleted file mode 100644 index 294dde750865..000000000000 --- a/packages/beacon-node/test/memory/unfinalizedPubkey2Index.ts +++ /dev/null @@ -1,54 +0,0 @@ -import crypto from "node:crypto"; -import {toMemoryEfficientHexStr} from "@lodestar/state-transition/src/cache/pubkeyCache.js"; -import {ValidatorIndex} from "@lodestar/types"; -// biome-ignore lint/suspicious/noShadowRestrictedNames: We explicitly want `Map` name to be imported -import {Map} from "immutable"; -import {testRunnerMemory} from "./testRunnerMemory.js"; - -// Results in MacOS Nov 2023 -// -// UnfinalizedPubkey2Index 1000 keys - 274956.5 bytes / instance -// UnfinalizedPubkey2Index 10000 keys - 2591129.3 bytes / instance -// UnfinalizedPubkey2Index 100000 keys - 27261443.4 bytes / instance - -testRunnerMemoryBpi([ - { - id: "UnfinalizedPubkey2Index 1000 keys", - getInstance: () => getRandomMap(1000, () => toMemoryEfficientHexStr(crypto.randomBytes(48))), - }, - { - id: "UnfinalizedPubkey2Index 10000 keys", - getInstance: () => getRandomMap(10000, () => toMemoryEfficientHexStr(crypto.randomBytes(48))), - }, - { - id: "UnfinalizedPubkey2Index 100000 keys", - getInstance: () => getRandomMap(100000, () => toMemoryEfficientHexStr(crypto.randomBytes(48))), - }, -]); - -function getRandomMap(n: number, getKey: (i: number) => string): Map { - const map = Map(); - - return map.withMutations((m) => { - for (let i = 0; i < n; i++) { - m.set(getKey(i), i); - } - }); -} - -/** - * Test bytes per instance in different representations of raw binary data - */ -function testRunnerMemoryBpi(testCases: {getInstance: (bytes: number) => unknown; id: string}[]): void { - const longestId = Math.max(...testCases.map(({id}) => id.length)); - - for (const {id, getInstance} of testCases) { - const bpi = testRunnerMemory({ - getInstance, - convergeFactor: 1 / 100, - sampleEvery: 5, - }); - - console.log(`${id.padEnd(longestId)} - ${bpi.toFixed(1)} bytes / instance`); - } -} diff --git a/packages/beacon-node/test/perf/chain/stateCache/updateUnfinalizedPubkeys.test.ts b/packages/beacon-node/test/perf/chain/stateCache/updateUnfinalizedPubkeys.test.ts deleted file mode 100644 index 8009f36c6301..000000000000 --- a/packages/beacon-node/test/perf/chain/stateCache/updateUnfinalizedPubkeys.test.ts +++ /dev/null @@ -1,114 +0,0 @@ -import {digest} from "@chainsafe/as-sha256"; -import {SecretKey} from "@chainsafe/blst"; -import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; -import {itBench, setBenchOpts} from "@dapplion/benchmark"; -import {type CachedBeaconStateAllForks, toMemoryEfficientHexStr} from "@lodestar/state-transition"; -import {ValidatorIndex, ssz} from "@lodestar/types"; -import {bytesToBigInt, intToBytes} from "@lodestar/utils"; -import {toBufferBE} from "bigint-buffer"; -import {Map as ImmutableMap} from "immutable"; -import {BlockStateCacheImpl, InMemoryCheckpointStateCache} from "../../../../src/chain/stateCache/index.js"; -import {BlockStateCache} from "../../../../src/chain/stateCache/types.js"; -import {generateCachedElectraState} from "../../../utils/state.js"; - -// Benchmark date from Mon Nov 21 2023 - Intel Core i7-9750H @ 2.60Ghz -// ✔ updateUnfinalizedPubkeys - updating 10 pubkeys 1444.173 ops/s 692.4380 us/op - 1057 runs 6.03 s -// ✔ updateUnfinalizedPubkeys - updating 100 pubkeys 189.5965 ops/s 5.274358 ms/op - 57 runs 1.15 s -// ✔ updateUnfinalizedPubkeys - updating 1000 pubkeys 12.90495 ops/s 77.48967 ms/op - 13 runs 1.62 s -describe("updateUnfinalizedPubkeys perf tests", () => { - setBenchOpts({noThreshold: true}); - - const numPubkeysToBeFinalizedCases = [10, 100, 1000]; - const numCheckpointStateCache = 8; - const numStateCache = 3 * 32; - - let checkpointStateCache: InMemoryCheckpointStateCache; - let stateCache: BlockStateCache; - - const unfinalizedPubkey2Index = generatePubkey2Index(0, Math.max.apply(null, numPubkeysToBeFinalizedCases)); - const baseState = generateCachedElectraState(); - - for (const numPubkeysToBeFinalized of numPubkeysToBeFinalizedCases) { - itBench({ - id: `updateUnfinalizedPubkeys - updating ${numPubkeysToBeFinalized} pubkeys`, - beforeEach: async () => { - baseState.epochCtx.unfinalizedPubkey2index = ImmutableMap(unfinalizedPubkey2Index); - baseState.epochCtx.pubkey2index = new PubkeyIndexMap(); - baseState.epochCtx.index2pubkey = []; - - checkpointStateCache = new InMemoryCheckpointStateCache({}); - stateCache = new BlockStateCacheImpl({}); - - for (let i = 0; i < numCheckpointStateCache; i++) { - const clonedState = baseState.clone(); - const checkpoint = ssz.phase0.Checkpoint.defaultValue(); - - clonedState.slot = i; - checkpoint.epoch = i; // Assigning arbitrary non-duplicate values to ensure checkpointStateCache correctly saves all the states - - checkpointStateCache.add(checkpoint, clonedState); - } - - for (let i = 0; i < numStateCache; i++) { - const clonedState = baseState.clone(); - clonedState.slot = i; - stateCache.add(clonedState); - } - }, - fn: async () => { - const newFinalizedValidators = baseState.epochCtx.unfinalizedPubkey2index.filter( - (index, _pubkey) => index < numPubkeysToBeFinalized - ); - - const states = stateCache.getStates(); - const cpStates = checkpointStateCache.getStates(); - - const firstState = states.next().value as CachedBeaconStateAllForks; - firstState.epochCtx.addFinalizedPubkeys(newFinalizedValidators); - - const pubkeysToDelete = Array.from(newFinalizedValidators.keys()); - - firstState.epochCtx.deleteUnfinalizedPubkeys(pubkeysToDelete); - - for (const s of states) { - s.epochCtx.deleteUnfinalizedPubkeys(pubkeysToDelete); - } - - for (const s of cpStates) { - s.epochCtx.deleteUnfinalizedPubkeys(pubkeysToDelete); - } - }, - }); - } - - type PubkeyHex = string; - - function generatePubkey2Index(startIndex: number, endIndex: number): Map { - const pubkey2Index = new Map(); - const pubkeys = generatePubkeys(endIndex - startIndex); - - for (let i = startIndex; i < endIndex; i++) { - pubkey2Index.set(toMemoryEfficientHexStr(pubkeys[i]), i); - } - - return pubkey2Index; - } - - function generatePubkeys(validatorCount: number): Uint8Array[] { - const keys = []; - - for (let i = 0; i < validatorCount; i++) { - const sk = generatePrivateKey(i); - const pk = sk.toPublicKey().toBytes(); - keys.push(pk); - } - - return keys; - } - - function generatePrivateKey(index: number): SecretKey { - const secretKeyBytes = toBufferBE(bytesToBigInt(digest(intToBytes(index, 32))) % BigInt("38581184513"), 32); - const secret: SecretKey = SecretKey.fromBytes(secretKeyBytes); - return secret; - } -}); diff --git a/packages/beacon-node/test/sim/electra-interop.test.ts b/packages/beacon-node/test/sim/electra-interop.test.ts index 47b7d127fb42..148f210f7f65 100644 --- a/packages/beacon-node/test/sim/electra-interop.test.ts +++ b/packages/beacon-node/test/sim/electra-interop.test.ts @@ -375,17 +375,8 @@ describe("executionEngine / ExecutionEngineHttp", () => { if (headState.validators.length !== 33 || headState.balances.length !== 33) { throw Error("New validator is not reflected in the beacon state at slot 5"); } - if (epochCtx.index2pubkey.length !== 32 || epochCtx.pubkey2index.size !== 32) { - throw Error("Finalized cache is modified."); - } - if (epochCtx.unfinalizedPubkey2index.size !== 1) { - throw Error( - `Unfinalized cache is missing the expected validator. Size: ${epochCtx.unfinalizedPubkey2index.size}` - ); - } - // validator count at epoch 1 should be empty at this point since no epoch transition has happened. - if (epochCtx.getValidatorCountAtEpoch(1) !== undefined) { - throw Error("Historical validator lengths is modified"); + if (epochCtx.index2pubkey.length !== 33 || epochCtx.pubkey2index.size !== 33) { + throw Error("Pubkey cache is not updated"); } await new Promise((resolve, _reject) => { @@ -412,23 +403,7 @@ describe("executionEngine / ExecutionEngineHttp", () => { throw Error("New validator is not reflected in the beacon state."); } if (epochCtx.index2pubkey.length !== 33 || epochCtx.pubkey2index.size !== 33) { - throw Error("New validator is not in finalized cache"); - } - if (!epochCtx.unfinalizedPubkey2index.isEmpty()) { - throw Error("Unfinalized cache still contains new validator"); - } - // After 4 epochs, headState's finalized cp epoch should be 2 - // epochCtx should only have validator count for epoch 3 and 4. - if (epochCtx.getValidatorCountAtEpoch(4) === undefined || epochCtx.getValidatorCountAtEpoch(3) === undefined) { - throw Error("Missing historical validator length for epoch 3 or 4"); - } - - if (epochCtx.getValidatorCountAtEpoch(4) !== 33 || epochCtx.getValidatorCountAtEpoch(3) !== 33) { - throw Error("Incorrect historical validator length for epoch 3 or 4"); - } - - if (epochCtx.getValidatorCountAtEpoch(2) !== undefined || epochCtx.getValidatorCountAtEpoch(1) !== undefined) { - throw Error("Historical validator length for epoch 1 or 2 is not dropped properly"); + throw Error("New validator is not in pubkey cache"); } if (headState.depositRequestsStartIndex === UNSET_DEPOSIT_REQUESTS_START_INDEX) { diff --git a/packages/state-transition/package.json b/packages/state-transition/package.json index 5dfecfa9bbf6..ac297b240756 100644 --- a/packages/state-transition/package.json +++ b/packages/state-transition/package.json @@ -69,8 +69,7 @@ "@lodestar/params": "^1.23.0", "@lodestar/types": "^1.23.0", "@lodestar/utils": "^1.23.0", - "bigint-buffer": "^1.1.5", - "immutable": "^4.3.2" + "bigint-buffer": "^1.1.5" }, "keywords": [ "ethereum", diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index 207267dff4f4..86e63c672024 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -25,10 +25,8 @@ import { electra, phase0, } from "@lodestar/types"; -import {LodestarError, fromHex} from "@lodestar/utils"; -import * as immutable from "immutable"; +import {LodestarError} from "@lodestar/utils"; import {getTotalSlashingsByIncrement} from "../epoch/processSlashings.js"; -import {EpochCacheMetrics} from "../metrics.js"; import {AttesterDuty, calculateCommitteeAssignments} from "../util/calculateCommitteeAssignments.js"; import { EpochShuffling, @@ -51,14 +49,7 @@ import { import {computeBaseRewardPerIncrement, computeSyncParticipantReward} from "../util/syncCommittee.js"; import {sumTargetUnslashedBalanceIncrements} from "../util/targetUnslashedBalance.js"; import {EffectiveBalanceIncrements, getEffectiveBalanceIncrementsWithLen} from "./effectiveBalanceIncrements.js"; -import { - Index2PubkeyCache, - PubkeyHex, - UnfinalizedPubkeyIndexMap, - newUnfinalizedPubkeyIndexMap, - syncPubkeys, - toMemoryEfficientHexStr, -} from "./pubkeyCache.js"; +import {Index2PubkeyCache, syncPubkeys} from "./pubkeyCache.js"; import {CachedBeaconStateAllForks} from "./stateCache.js"; import { SyncCommitteeCache, @@ -111,30 +102,20 @@ type ProposersDeferred = {computed: false; seed: Uint8Array} | {computed: true; export class EpochCache { config: BeaconConfig; /** - * Unique globally shared finalized pubkey registry. There should only exist one for the entire application. + * Unique globally shared pubkey registry. There should only exist one for the entire application. * * TODO: this is a hack, we need a safety mechanism in case a bad eth1 majority vote is in, * or handle non finalized data differently, or use an immutable.js structure for cheap copies * - * New: This would include only validators whose activation_eligibility_epoch != FAR_FUTURE_EPOCH and hence it is - * insert only. Validators could be 1) Active 2) In the activation queue 3) Initialized but pending queued - * * $VALIDATOR_COUNT x 192 char String -> Number Map */ pubkey2index: PubkeyIndexMap; /** - * Unique globally shared finalized pubkey registry. There should only exist one for the entire application. - * - * New: This would include only validators whose activation_eligibility_epoch != FAR_FUTURE_EPOCH and hence it is - * insert only. Validators could be 1) Active 2) In the activation queue 3) Initialized but pending queued + * Unique globally shared pubkey registry. There should only exist one for the entire application. * * $VALIDATOR_COUNT x BLST deserialized pubkey (Jacobian coordinates) */ index2pubkey: Index2PubkeyCache; - /** - * Unique pubkey registry shared in the same fork. There should only exist one for the fork. - */ - unfinalizedPubkey2index: UnfinalizedPubkeyIndexMap; /** * ShufflingCache is passed in from `beacon-node` so should be available at runtime but may not be * present during testing. @@ -252,15 +233,6 @@ export class EpochCache { // TODO: Helper stats syncPeriod: SyncPeriod; - /** - * state.validators.length of every state at epoch boundary - * They are saved in increasing order of epoch. - * The first validator length in the list corresponds to the state AFTER the latest finalized checkpoint state. ie. state.finalizedCheckpoint.epoch - 1 - * The last validator length corresponds to the latest epoch state ie. this.epoch - * eg. latest epoch = 105, latest finalized cp state epoch = 102 - * then the list will be (in terms of epoch) [103, 104, 105] - */ - historicalValidatorLengths: immutable.List; epoch: Epoch; @@ -272,7 +244,6 @@ export class EpochCache { config: BeaconConfig; pubkey2index: PubkeyIndexMap; index2pubkey: Index2PubkeyCache; - unfinalizedPubkey2index: UnfinalizedPubkeyIndexMap; shufflingCache?: IShufflingCache; proposers: number[]; proposersPrevEpoch: number[] | null; @@ -300,12 +271,10 @@ export class EpochCache { nextSyncCommitteeIndexed: SyncCommitteeCache; epoch: Epoch; syncPeriod: SyncPeriod; - historialValidatorLengths: immutable.List; }) { this.config = data.config; this.pubkey2index = data.pubkey2index; this.index2pubkey = data.index2pubkey; - this.unfinalizedPubkey2index = data.unfinalizedPubkey2index; this.shufflingCache = data.shufflingCache; this.proposers = data.proposers; this.proposersPrevEpoch = data.proposersPrevEpoch; @@ -333,12 +302,11 @@ export class EpochCache { this.nextSyncCommitteeIndexed = data.nextSyncCommitteeIndexed; this.epoch = data.epoch; this.syncPeriod = data.syncPeriod; - this.historicalValidatorLengths = data.historialValidatorLengths; } /** * Create an epoch cache - * @param state a finalized beacon state. Passing in unfinalized state may cause unexpected behaviour eg. empty unfinalized cache + * @param state a finalized beacon state. Passing in unfinalized state may cause unexpected behaviour * * SLOW CODE - 🐢 */ @@ -551,8 +519,6 @@ export class EpochCache { config, pubkey2index, index2pubkey, - // `createFromFinalizedState()` creates cache with empty unfinalizedPubkey2index. Be cautious to only pass in finalized state - unfinalizedPubkey2index: newUnfinalizedPubkeyIndexMap(), shufflingCache, proposers, // On first epoch, set to null to prevent unnecessary work since this is only used for metrics @@ -581,7 +547,6 @@ export class EpochCache { nextSyncCommitteeIndexed, epoch: currentEpoch, syncPeriod: computeSyncPeriodAtEpoch(currentEpoch), - historialValidatorLengths: immutable.List(), }); } @@ -597,8 +562,6 @@ export class EpochCache { // Common append-only structures shared with all states, no need to clone pubkey2index: this.pubkey2index, index2pubkey: this.index2pubkey, - // No need to clone this reference. On each mutation the `unfinalizedPubkey2index` reference is replaced, @see `addPubkey` - unfinalizedPubkey2index: this.unfinalizedPubkey2index, shufflingCache: this.shufflingCache, // Immutable data proposers: this.proposers, @@ -630,7 +593,6 @@ export class EpochCache { nextSyncCommitteeIndexed: this.nextSyncCommitteeIndexed, epoch: this.epoch, syncPeriod: this.syncPeriod, - historialValidatorLengths: this.historicalValidatorLengths, }); } @@ -773,25 +735,6 @@ export class EpochCache { // ``` this.epoch = computeEpochAtSlot(state.slot); this.syncPeriod = computeSyncPeriodAtEpoch(this.epoch); - // ELECTRA Only: Add current cpState.validators.length - // Only keep validatorLength for epochs after finalized cpState.epoch - // eg. [100(epoch 1), 102(epoch 2)].push(104(epoch 3)), this.epoch = 3, finalized cp epoch = 1 - // We keep the last (3 - 1) items = [102, 104] - if (upcomingEpoch >= this.config.ELECTRA_FORK_EPOCH) { - this.historicalValidatorLengths = this.historicalValidatorLengths.push(state.validators.length); - - // If number of validatorLengths we want to keep exceeds the current list size, it implies - // finalized checkpoint hasn't advanced, and no need to slice - const hasFinalizedCpAdvanced = - this.epoch - state.finalizedCheckpoint.epoch < this.historicalValidatorLengths.size; - - if (hasFinalizedCpAdvanced) { - // We use finalized cp epoch - this.epoch which is a negative number to keep the last n entries and discard the rest - this.historicalValidatorLengths = this.historicalValidatorLengths.slice( - state.finalizedCheckpoint.epoch - this.epoch - ); - } - } } beforeEpochTransition(): void { @@ -1018,75 +961,19 @@ export class EpochCache { } /** - * Return finalized pubkey given the validator index. - * Only finalized pubkey as we do not store unfinalized pubkey because no where in the spec has a - * need to make such enquiry + * Return pubkey given the validator index. */ getPubkey(index: ValidatorIndex): PublicKey | undefined { return this.index2pubkey[index]; } getValidatorIndex(pubkey: Uint8Array): ValidatorIndex | null { - if (this.isPostElectra()) { - return this.pubkey2index.get(pubkey) ?? this.unfinalizedPubkey2index.get(toMemoryEfficientHexStr(pubkey)) ?? null; - } return this.pubkey2index.get(pubkey); } - /** - * - * Add unfinalized pubkeys - * - */ addPubkey(index: ValidatorIndex, pubkey: Uint8Array): void { - if (this.isPostElectra()) { - this.addUnFinalizedPubkey(index, pubkey); - } else { - // deposit mechanism pre ELECTRA follows a safe distance with assumption - // that they are already canonical - this.addFinalizedPubkey(index, pubkey); - } - } - - addUnFinalizedPubkey(index: ValidatorIndex, pubkey: PubkeyHex | Uint8Array, metrics?: EpochCacheMetrics): void { - this.unfinalizedPubkey2index = this.unfinalizedPubkey2index.set(toMemoryEfficientHexStr(pubkey), index); - metrics?.newUnFinalizedPubkey.inc(); - } - - addFinalizedPubkeys(pubkeyMap: UnfinalizedPubkeyIndexMap, metrics?: EpochCacheMetrics): void { - pubkeyMap.forEach((index, pubkey) => this.addFinalizedPubkey(index, pubkey, metrics)); - } - - /** - * Add finalized validator index and pubkey into finalized cache. - * Since addFinalizedPubkey() primarily takes pubkeys from unfinalized cache, it can take pubkey hex string directly - */ - addFinalizedPubkey(index: ValidatorIndex, pubkeyOrHex: PubkeyHex | Uint8Array, metrics?: EpochCacheMetrics): void { - const pubkey = typeof pubkeyOrHex === "string" ? fromHex(pubkeyOrHex) : pubkeyOrHex; - const existingIndex = this.pubkey2index.get(pubkey); - - if (existingIndex !== null) { - if (existingIndex === index) { - // Repeated insert. - metrics?.finalizedPubkeyDuplicateInsert.inc(); - return; - } - // attempt to insert the same pubkey with different index, should never happen. - throw Error( - `inserted existing pubkey into finalizedPubkey2index cache with a different index, index=${index} priorIndex=${existingIndex}` - ); - } - this.pubkey2index.set(pubkey, index); - const pubkeyBytes = pubkey instanceof Uint8Array ? pubkey : fromHex(pubkey); - this.index2pubkey[index] = PublicKey.fromBytes(pubkeyBytes); // Optimize for aggregation - } - - /** - * Delete pubkeys from unfinalized cache - */ - deleteUnfinalizedPubkeys(pubkeys: Iterable): void { - this.unfinalizedPubkey2index = this.unfinalizedPubkey2index.deleteAll(pubkeys); + this.index2pubkey[index] = PublicKey.fromBytes(pubkey); // Optimize for aggregation } getShufflingAtSlot(slot: Slot): EpochShuffling { @@ -1220,25 +1107,6 @@ export class EpochCache { isPostElectra(): boolean { return this.epoch >= this.config.ELECTRA_FORK_EPOCH; } - - getValidatorCountAtEpoch(targetEpoch: Epoch): number | undefined { - const currentEpoch = this.epoch; - - if (targetEpoch === currentEpoch) { - return this.historicalValidatorLengths.get(-1); - } - - // Attempt to get validator count from future epoch - if (targetEpoch > currentEpoch) { - return undefined; - } - - // targetEpoch is so far back that historicalValidatorLengths doesnt contain such info - if (targetEpoch < currentEpoch - this.historicalValidatorLengths.size + 1) { - return undefined; - } - return this.historicalValidatorLengths.get(targetEpoch - currentEpoch - 1); - } } function getEffectiveBalanceIncrementsByteLen(validatorCount: number): number { diff --git a/packages/state-transition/src/cache/pubkeyCache.ts b/packages/state-transition/src/cache/pubkeyCache.ts index 16c8f3de6787..75281e52e060 100644 --- a/packages/state-transition/src/cache/pubkeyCache.ts +++ b/packages/state-transition/src/cache/pubkeyCache.ts @@ -1,44 +1,8 @@ import {PublicKey} from "@chainsafe/blst"; import {PubkeyIndexMap} from "@chainsafe/pubkey-index-map"; import {ValidatorIndex, phase0} from "@lodestar/types"; -import * as immutable from "immutable"; export type Index2PubkeyCache = PublicKey[]; -/** - * OrderedMap preserves the order of entries in which they are `set()`. - * We assume `values()` yields validator indices in strictly increasing order - * as new validator indices are assigned in increasing order. - * EIP-6914 will break this assumption. - */ -export type UnfinalizedPubkeyIndexMap = immutable.Map; - -export type PubkeyHex = string; - -/** - * toHexString() creates hex strings via string concatenation, which are very memory inefficient. - * Memory benchmarks show that Buffer.toString("hex") produces strings with 10x less memory. - * - * Does not prefix to save memory, thus the prefix is removed from an already string representation. - * - * See https://github.com/ChainSafe/lodestar/issues/3446 - */ -export function toMemoryEfficientHexStr(hex: Uint8Array | string): string { - if (typeof hex === "string") { - if (hex.startsWith("0x")) { - hex = hex.slice(2); - } - return hex; - } - - return Buffer.from(hex.buffer, hex.byteOffset, hex.byteLength).toString("hex"); -} - -/** - * A wrapper for calling immutable.js. To abstract the initialization of UnfinalizedPubkeyIndexMap - */ -export function newUnfinalizedPubkeyIndexMap(): UnfinalizedPubkeyIndexMap { - return immutable.Map(); -} /** * Checks the pubkey indices against a state and adds missing pubkeys diff --git a/packages/state-transition/src/index.ts b/packages/state-transition/src/index.ts index 600bbf173462..0e76c5248a97 100644 --- a/packages/state-transition/src/index.ts +++ b/packages/state-transition/src/index.ts @@ -41,15 +41,10 @@ export { EpochCacheError, EpochCacheErrorCode, } from "./cache/epochCache.js"; -export {toMemoryEfficientHexStr} from "./cache/pubkeyCache.js"; export {type EpochTransitionCache, beforeProcessEpoch} from "./cache/epochTransitionCache.js"; // Aux data-structures -export { - type Index2PubkeyCache, - type UnfinalizedPubkeyIndexMap, - newUnfinalizedPubkeyIndexMap, -} from "./cache/pubkeyCache.js"; +export {type Index2PubkeyCache} from "./cache/pubkeyCache.js"; export { type EffectiveBalanceIncrements, diff --git a/packages/state-transition/src/metrics.ts b/packages/state-transition/src/metrics.ts index fee20de1d565..d975833ba13c 100644 --- a/packages/state-transition/src/metrics.ts +++ b/packages/state-transition/src/metrics.ts @@ -31,11 +31,6 @@ export type BeaconStateTransitionMetrics = { ) => void; }; -export type EpochCacheMetrics = { - finalizedPubkeyDuplicateInsert: Gauge; - newUnFinalizedPubkey: Gauge; -}; - export function onStateCloneMetrics( state: CachedBeaconStateAllForks, metrics: BeaconStateTransitionMetrics, diff --git a/packages/state-transition/test/unit/cachedBeaconState.test.ts b/packages/state-transition/test/unit/cachedBeaconState.test.ts index de2ba5893b02..9466dfd83ab7 100644 --- a/packages/state-transition/test/unit/cachedBeaconState.test.ts +++ b/packages/state-transition/test/unit/cachedBeaconState.test.ts @@ -28,7 +28,7 @@ describe("CachedBeaconState", () => { expect(state2.epochCtx.epoch).toBe(0); }); - it("Clone and mutate cache pre-Electra", () => { + it("Clone and mutate cache", () => { const stateView = ssz.altair.BeaconState.defaultViewDU(); const state1 = createCachedBeaconStateTest(stateView); @@ -52,40 +52,6 @@ describe("CachedBeaconState", () => { expect(state2.epochCtx.getValidatorIndex(pubkey2)).toBe(index2); }); - it("Clone and mutate cache post-Electra", () => { - const stateView = ssz.electra.BeaconState.defaultViewDU(); - const state1 = createCachedBeaconStateTest( - stateView, - createChainForkConfig({ - ALTAIR_FORK_EPOCH: 0, - BELLATRIX_FORK_EPOCH: 0, - CAPELLA_FORK_EPOCH: 0, - DENEB_FORK_EPOCH: 0, - ELECTRA_FORK_EPOCH: 0, - }), - {skipSyncCommitteeCache: true, skipSyncPubkeys: true} - ); - - const pubkey1 = fromHexString( - "0x84105a985058fc8740a48bf1ede9d223ef09e8c6b1735ba0a55cf4a9ff2ff92376b778798365e488dab07a652eb04576" - ); - const index1 = 123; - const pubkey2 = fromHexString( - "0xa41726266b1d83ef609d759ba7796d54cfe549154e01e4730a3378309bc81a7638140d7e184b33593c072595f23f032d" - ); - const index2 = 456; - - state1.epochCtx.addPubkey(index1, pubkey1); - - const state2 = state1.clone(); - state2.epochCtx.addPubkey(index2, pubkey2); - - expect(state1.epochCtx.getValidatorIndex(pubkey1)).toBe(index1); - expect(state2.epochCtx.getValidatorIndex(pubkey1)).toBe(index1); - expect(state1.epochCtx.getValidatorIndex(pubkey2)).toBe(null); - expect(state2.epochCtx.getValidatorIndex(pubkey2)).toBe(index2); - }); - it("Auto-commit on hashTreeRoot", () => { // Use Checkpoint instead of BeaconState to speed up the test const cp1 = ssz.phase0.Checkpoint.defaultViewDU(); diff --git a/yarn.lock b/yarn.lock index a9cb9ebe4fdc..f59b3d082456 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7267,11 +7267,6 @@ ignore@^5.0.4, ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.0.tgz#67418ae40d34d6999c95ff56016759c718c82f78" integrity sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg== -immutable@^4.3.2: - version "4.3.5" - resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.5.tgz#f8b436e66d59f99760dc577f5c99a4fd2a5cc5a0" - integrity sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw== - import-fresh@^3.3.0: version "3.3.0" resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz" From 18f421859c1cf569437e8bb727b10067f24169b6 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 27 Nov 2024 07:46:14 +0100 Subject: [PATCH 02/43] chore: skip web3_provider unit tests (#7252) --- packages/prover/test/unit/web3_provider.node.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/prover/test/unit/web3_provider.node.test.ts b/packages/prover/test/unit/web3_provider.node.test.ts index 10f0fee3387d..b370c871d0e6 100644 --- a/packages/prover/test/unit/web3_provider.node.test.ts +++ b/packages/prover/test/unit/web3_provider.node.test.ts @@ -7,7 +7,8 @@ import {JsonRpcRequest, JsonRpcRequestOrBatch, JsonRpcResponse} from "../../src/ import {ELRpcProvider} from "../../src/utils/rpc_provider.js"; import {createVerifiedExecutionProvider} from "../../src/web3_provider.js"; -describe("web3_provider", () => { +// https://github.com/ChainSafe/lodestar/issues/7250 +describe.skip("web3_provider", () => { afterEach(() => { vi.clearAllMocks(); }); From e86e816b1d8418063cd64e238f0d88ca6dd0f5ea Mon Sep 17 00:00:00 2001 From: twoeths Date: Wed, 27 Nov 2024 15:22:24 +0700 Subject: [PATCH 03/43] fix: prune checkpoint states at syncing time (#7241) * fix: prune checkpoint states at syncing time * fix: lint * fix: check-types in test --- packages/beacon-node/src/chain/chain.ts | 1 - .../stateCache/persistentCheckpointsCache.ts | 27 +++---------------- .../persistentCheckpointsCache.test.ts | 18 +++++-------- 3 files changed, 9 insertions(+), 37 deletions(-) diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 4751770b3bfc..415158abb2d5 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -293,7 +293,6 @@ export class BeaconChain implements IBeaconChain { metrics, logger, clock, - shufflingCache: this.shufflingCache, blockStateCache, bufferPool: this.bufferPool, datastore: fileDataStore diff --git a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts index 4c9a8b0f1265..7fb67a758673 100644 --- a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts @@ -9,7 +9,6 @@ import {AllocSource, BufferPool, BufferWithKey} from "../../util/bufferPool.js"; import {IClock} from "../../util/clock.js"; import {StateCloneOpts} from "../regen/interface.js"; import {serializeState} from "../serializeState.js"; -import {ShufflingCache} from "../shufflingCache.js"; import {CPStateDatastore, DatastoreKey, datastoreKeyToCheckpoint} from "./datastore/index.js"; import {MapTracker} from "./mapMetrics.js"; import {BlockStateCache, CacheItemType, CheckpointHex, CheckpointStateCache} from "./types.js"; @@ -17,8 +16,6 @@ import {BlockStateCache, CacheItemType, CheckpointHex, CheckpointStateCache} fro export type PersistentCheckpointStateCacheOpts = { /** Keep max n states in memory, persist the rest to disk */ maxCPStateEpochsInMemory?: number; - /** for testing only */ - processLateBlock?: boolean; }; type PersistentCheckpointStateCacheModules = { @@ -26,7 +23,6 @@ type PersistentCheckpointStateCacheModules = { logger: Logger; clock?: IClock | null; signal?: AbortSignal; - shufflingCache: ShufflingCache; datastore: CPStateDatastore; blockStateCache: BlockStateCache; bufferPool?: BufferPool | null; @@ -102,24 +98,12 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { private preComputedCheckpoint: string | null = null; private preComputedCheckpointHits: number | null = null; private readonly maxEpochsInMemory: number; - // only for testing, default false for production - private readonly processLateBlock: boolean; private readonly datastore: CPStateDatastore; - private readonly shufflingCache: ShufflingCache; private readonly blockStateCache: BlockStateCache; private readonly bufferPool?: BufferPool | null; constructor( - { - metrics, - logger, - clock, - signal, - shufflingCache, - datastore, - blockStateCache, - bufferPool, - }: PersistentCheckpointStateCacheModules, + {metrics, logger, clock, signal, datastore, blockStateCache, bufferPool}: PersistentCheckpointStateCacheModules, opts: PersistentCheckpointStateCacheOpts ) { this.cache = new MapTracker(metrics?.cpStateCache); @@ -153,10 +137,8 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { throw new Error("maxEpochsInMemory must be >= 0"); } this.maxEpochsInMemory = opts.maxCPStateEpochsInMemory ?? DEFAULT_MAX_CP_STATE_EPOCHS_IN_MEMORY; - this.processLateBlock = opts.processLateBlock ?? false; // Specify different datastore for testing this.datastore = datastore; - this.shufflingCache = shufflingCache; this.blockStateCache = blockStateCache; this.bufferPool = bufferPool; } @@ -487,12 +469,9 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { // 2/3 of slot is the most free time of every slot, take that chance to persist checkpoint states // normally it should only persist checkpoint states at 2/3 of slot 0 of epoch await sleep(secToTwoThirdsSlot * 1000, this.signal); - } else if (!this.processLateBlock) { - // normally the block persist happens at 2/3 of slot 0 of epoch, if it's already late then just skip to allow other tasks to run - // there are plenty of chances in the same epoch to persist checkpoint states, also if block is late it could be reorged - this.logger.verbose("Skip persist checkpoint states", {blockSlot, root: blockRootHex}); - return 0; } + // at syncing time, it's critical to persist checkpoint states as soon as possible to avoid OOM during unfinality time + // if node is synced this is not a hot time because block comes late, we'll likely miss attestation already, or the block is orphaned const persistEpochs = sortedEpochs.slice(0, sortedEpochs.length - this.maxEpochsInMemory); for (const lowestEpoch of persistEpochs) { diff --git a/packages/beacon-node/test/unit/chain/stateCache/persistentCheckpointsCache.test.ts b/packages/beacon-node/test/unit/chain/stateCache/persistentCheckpointsCache.test.ts index 5ab3da532948..4e8b62013331 100644 --- a/packages/beacon-node/test/unit/chain/stateCache/persistentCheckpointsCache.test.ts +++ b/packages/beacon-node/test/unit/chain/stateCache/persistentCheckpointsCache.test.ts @@ -90,10 +90,9 @@ describe("PersistentCheckpointStateCache", () => { { datastore, logger: testLogger(), - shufflingCache: new ShufflingCache(), blockStateCache: new FIFOBlockStateCache({}, {}), }, - {maxCPStateEpochsInMemory: 2, processLateBlock: true} + {maxCPStateEpochsInMemory: 2} ); cache.add(cp0a, states["cp0a"]); cache.add(cp0b, states["cp0b"]); @@ -165,10 +164,9 @@ describe("PersistentCheckpointStateCache", () => { { datastore, logger: testLogger(), - shufflingCache: new ShufflingCache(), blockStateCache: new FIFOBlockStateCache({}, {}), }, - {maxCPStateEpochsInMemory: 2, processLateBlock: true} + {maxCPStateEpochsInMemory: 2} ); cache.add(cp0a, states["cp0a"]); cache.add(cp0b, states["cp0b"]); @@ -242,10 +240,9 @@ describe("PersistentCheckpointStateCache", () => { { datastore, logger: testLogger(), - shufflingCache: new ShufflingCache(), blockStateCache: new FIFOBlockStateCache({}, {}), }, - {maxCPStateEpochsInMemory: 2, processLateBlock: true} + {maxCPStateEpochsInMemory: 2} ); cache.add(cp0a, states["cp0a"]); cache.add(cp0b, states["cp0b"]); @@ -548,10 +545,9 @@ describe("PersistentCheckpointStateCache", () => { { datastore, logger: testLogger(), - shufflingCache: new ShufflingCache(), blockStateCache: new FIFOBlockStateCache({}, {}), }, - {maxCPStateEpochsInMemory: 1, processLateBlock: true} + {maxCPStateEpochsInMemory: 1} ); cache.add(cp0a, states["cp0a"]); cache.add(cp0b, states["cp0b"]); @@ -820,10 +816,9 @@ describe("PersistentCheckpointStateCache", () => { { datastore, logger: testLogger(), - shufflingCache: new ShufflingCache(), blockStateCache: new FIFOBlockStateCache({}, {}), }, - {maxCPStateEpochsInMemory: 0, processLateBlock: true} + {maxCPStateEpochsInMemory: 0} ); cache.add(cp0a, states["cp0a"]); cache.add(cp0b, states["cp0b"]); @@ -911,10 +906,9 @@ describe("PersistentCheckpointStateCache", () => { { datastore, logger: testLogger(), - shufflingCache: new ShufflingCache(), blockStateCache: new FIFOBlockStateCache({}, {}), }, - {maxCPStateEpochsInMemory: 0, processLateBlock: true} + {maxCPStateEpochsInMemory: 0} ); const root1a = Buffer.alloc(32, 100); From 443e3a8ab3dd1c49706f3e9b9b784aebd627f318 Mon Sep 17 00:00:00 2001 From: twoeths Date: Wed, 27 Nov 2024 15:52:43 +0700 Subject: [PATCH 04/43] fix: sync cached isCompoundingValidatorArr at epoch transition (#7247) --- .../src/cache/epochTransitionCache.ts | 25 +++++++++++++------ .../epoch/processEffectiveBalanceUpdates.ts | 7 +++--- .../src/epoch/processPendingDeposits.ts | 3 +++ 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index d86431c72eee..16279e851188 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -9,7 +9,12 @@ import {Epoch, RootHex, ValidatorIndex, phase0} from "@lodestar/types"; import {intDiv, toRootHex} from "@lodestar/utils"; import {processPendingAttestations} from "../epoch/processPendingAttestations.js"; -import {CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStatePhase0} from "../index.js"; +import { + CachedBeaconStateAllForks, + CachedBeaconStateAltair, + CachedBeaconStatePhase0, + hasCompoundingWithdrawalCredential, +} from "../index.js"; import {computeBaseRewardPerIncrement} from "../util/altair.js"; import { FLAG_CURR_HEAD_ATTESTER, @@ -133,11 +138,7 @@ export interface EpochTransitionCache { flags: number[]; - /** - * Validators in the current epoch, should use it for read-only value instead of accessing state.validators directly. - * Note that during epoch processing, validators could be updated so need to use it with care. - */ - validators: phase0.Validator[]; + isCompoundingValidatorArr: boolean[]; /** * balances array will be populated by processRewardsAndPenalties() and consumed by processEffectiveBalanceUpdates(). @@ -209,6 +210,8 @@ const inclusionDelays = new Array(); /** WARNING: reused, never gc'd */ const flags = new Array(); /** WARNING: reused, never gc'd */ +const isCompoundingValidatorArr = new Array(); +/** WARNING: reused, never gc'd */ const nextEpochShufflingActiveValidatorIndices = new Array(); export function beforeProcessEpoch( @@ -262,6 +265,10 @@ export function beforeProcessEpoch( // TODO: optimize by combining the two loops // likely will require splitting into phase0 and post-phase0 versions + if (forkSeq >= ForkSeq.electra) { + isCompoundingValidatorArr.length = validatorCount; + } + // Clone before being mutated in processEffectiveBalanceUpdates epochCtx.beforeEpochTransition(); @@ -298,6 +305,10 @@ export function beforeProcessEpoch( flags[i] = flag; + if (forkSeq >= ForkSeq.electra) { + isCompoundingValidatorArr[i] = hasCompoundingWithdrawalCredential(validator.withdrawalCredentials); + } + if (isActiveCurr) { totalActiveStakeByIncrement += effectiveBalancesByIncrements[i]; } else { @@ -511,7 +522,7 @@ export function beforeProcessEpoch( proposerIndices, inclusionDelays, flags, - validators, + isCompoundingValidatorArr, // Will be assigned in processRewardsAndPenalties() balances: undefined, }; diff --git a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts index d55f37aba178..b98e5cce9f35 100644 --- a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts +++ b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts @@ -5,10 +5,11 @@ import { HYSTERESIS_QUOTIENT, HYSTERESIS_UPWARD_MULTIPLIER, MAX_EFFECTIVE_BALANCE, + MAX_EFFECTIVE_BALANCE_ELECTRA, + MIN_ACTIVATION_BALANCE, TIMELY_TARGET_FLAG_INDEX, } from "@lodestar/params"; import {BeaconStateAltair, CachedBeaconStateAllForks, EpochTransitionCache} from "../types.js"; -import {getMaxEffectiveBalance} from "../util/validator.js"; /** Same to https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#has_flag */ const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX; @@ -43,7 +44,7 @@ export function processEffectiveBalanceUpdates( // and updated in processPendingDeposits() and processPendingConsolidations() // so it's recycled here for performance. const balances = cache.balances ?? state.balances.getAll(); - const currentEpochValidators = cache.validators; + const isCompoundingValidatorArr = cache.isCompoundingValidatorArr; let numUpdate = 0; for (let i = 0, len = balances.length; i < len; i++) { @@ -58,7 +59,7 @@ export function processEffectiveBalanceUpdates( effectiveBalanceLimit = MAX_EFFECTIVE_BALANCE; } else { // from electra, effectiveBalanceLimit is per validator - effectiveBalanceLimit = getMaxEffectiveBalance(currentEpochValidators[i].withdrawalCredentials); + effectiveBalanceLimit = isCompoundingValidatorArr[i] ? MAX_EFFECTIVE_BALANCE_ELECTRA : MIN_ACTIVATION_BALANCE; } if ( diff --git a/packages/state-transition/src/epoch/processPendingDeposits.ts b/packages/state-transition/src/epoch/processPendingDeposits.ts index 866dcc2510ad..e00e1ef93c8c 100644 --- a/packages/state-transition/src/epoch/processPendingDeposits.ts +++ b/packages/state-transition/src/epoch/processPendingDeposits.ts @@ -3,6 +3,7 @@ import {PendingDeposit} from "@lodestar/types/lib/electra/types.js"; import {addValidatorToRegistry, isValidDepositSignature} from "../block/processDeposit.js"; import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js"; import {increaseBalance} from "../util/balance.js"; +import {hasCompoundingWithdrawalCredential} from "../util/electra.js"; import {computeStartSlotAtEpoch} from "../util/epoch.js"; import {getActivationExitChurnLimit} from "../util/validator.js"; @@ -106,6 +107,8 @@ function applyPendingDeposit( // Verify the deposit signature (proof of possession) which is not checked by the deposit contract if (isValidDepositSignature(state.config, pubkey, withdrawalCredentials, amount, signature)) { addValidatorToRegistry(ForkSeq.electra, state, pubkey, withdrawalCredentials, amount); + const newValidatorIndex = state.validators.length - 1; + cache.isCompoundingValidatorArr[newValidatorIndex] = hasCompoundingWithdrawalCredential(withdrawalCredentials); } } else { // Increase balance From 0c1ad93f8919c66abf0053ec3f9c2519623ec1b6 Mon Sep 17 00:00:00 2001 From: twoeths Date: Wed, 27 Nov 2024 17:01:15 +0700 Subject: [PATCH 05/43] fix: handle outOfRangeData when range sync Deneb (#7249) * fix: handle outOfRangeData for beaconBlocksMaybeBlobsByRange() * fix: lint * fix: archiveBlocks - handle deneb outOfRangeData block --- .../src/chain/archiver/archiveBlocks.ts | 44 ++++++++++++++----- .../reqresp/beaconBlocksMaybeBlobsByRange.ts | 8 ++-- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/packages/beacon-node/src/chain/archiver/archiveBlocks.ts b/packages/beacon-node/src/chain/archiver/archiveBlocks.ts index 69c7dedfc560..ad0ac0ff2c6b 100644 --- a/packages/beacon-node/src/chain/archiver/archiveBlocks.ts +++ b/packages/beacon-node/src/chain/archiver/archiveBlocks.ts @@ -59,8 +59,8 @@ export async function archiveBlocks( }); if (finalizedPostDeneb) { - await migrateBlobSidecarsFromHotToColdDb(config, db, finalizedCanonicalBlockRoots); - logger.verbose("Migrated blobSidecars from hot DB to cold DB"); + const migrate = await migrateBlobSidecarsFromHotToColdDb(config, db, finalizedCanonicalBlockRoots, currentEpoch); + logger.verbose(migrate ? "Migrated blobSidecars from hot DB to cold DB" : "Skip blobSidecars migration"); } } @@ -157,22 +157,36 @@ async function migrateBlocksFromHotToColdDb(db: IBeaconDb, blocks: BlockRootSlot } } +/** + * Migrate blobSidecars from hot db to cold db. + * @returns true if we do that, false if block is out of range data. + */ async function migrateBlobSidecarsFromHotToColdDb( config: ChainForkConfig, db: IBeaconDb, - blocks: BlockRootSlot[] -): Promise { + blocks: BlockRootSlot[], + currentEpoch: Epoch +): Promise { + let result = false; + for (let i = 0; i < blocks.length; i += BLOB_SIDECAR_BATCH_SIZE) { const toIdx = Math.min(i + BLOB_SIDECAR_BATCH_SIZE, blocks.length); const canonicalBlocks = blocks.slice(i, toIdx); // processCanonicalBlocks - if (canonicalBlocks.length === 0) return; + if (canonicalBlocks.length === 0) return false; // load Buffer instead of ssz deserialized to improve performance const canonicalBlobSidecarsEntries: KeyValue[] = await Promise.all( canonicalBlocks - .filter((block) => config.getForkSeq(block.slot) >= ForkSeq.deneb) + .filter((block) => { + const blockSlot = block.slot; + const blockEpoch = computeEpochAtSlot(blockSlot); + return ( + config.getForkSeq(blockSlot) >= ForkSeq.deneb && + blockEpoch >= currentEpoch - config.MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS + ); + }) .map(async (block) => { const bytes = await db.blobSidecars.getBinary(block.root); if (!bytes) { @@ -182,12 +196,20 @@ async function migrateBlobSidecarsFromHotToColdDb( }) ); - // put to blockArchive db and delete block db - await Promise.all([ - db.blobSidecarsArchive.batchPutBinary(canonicalBlobSidecarsEntries), - db.blobSidecars.batchDelete(canonicalBlocks.map((block) => block.root)), - ]); + const migrate = canonicalBlobSidecarsEntries.length > 0; + + if (migrate) { + // put to blockArchive db and delete block db + await Promise.all([ + db.blobSidecarsArchive.batchPutBinary(canonicalBlobSidecarsEntries), + db.blobSidecars.batchDelete(canonicalBlocks.map((block) => block.root)), + ]); + } + + result = result || migrate; } + + return result; } /** diff --git a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts index 21d781f11437..d19a7867d873 100644 --- a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts +++ b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts @@ -36,8 +36,9 @@ export async function beaconBlocksMaybeBlobsByRange( return blocks.map((block) => getBlockInput.preData(config, block.data, BlockSource.byRange, block.bytes)); } + // From Deneb // Only request blobs if they are recent enough - if (computeEpochAtSlot(startSlot) >= currentEpoch - config.MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS) { + if (startEpoch >= currentEpoch - config.MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS) { const [allBlocks, allBlobSidecars] = await Promise.all([ network.sendBeaconBlocksByRange(peerId, request), network.sendBlobSidecarsByRange(peerId, request), @@ -46,8 +47,9 @@ export async function beaconBlocksMaybeBlobsByRange( return matchBlockWithBlobs(config, allBlocks, allBlobSidecars, endSlot, BlockSource.byRange, BlobsSource.byRange); } - // Post Deneb but old blobs - throw Error("Cannot sync blobs outside of blobs prune window"); + // Data is out of range, only request blocks + const blocks = await network.sendBeaconBlocksByRange(peerId, request); + return blocks.map((block) => getBlockInput.outOfRangeData(config, block.data, BlockSource.byRange, block.bytes)); } // Assumes that the blobs are in the same sequence as blocks, doesn't require block to be sorted From 48dea55f542b6356d301663ec7c36e5ab9412c1b Mon Sep 17 00:00:00 2001 From: twoeths Date: Thu, 28 Nov 2024 14:20:20 +0700 Subject: [PATCH 06/43] fix: sync cached balance when adding new validator to registry (#7255) * fix: sync cached balance when adding new validator to registry * chore: add more comments * fix: remove persisted checkpoint states from the previous run at startup --- .../chain/stateCache/persistentCheckpointsCache.ts | 11 +++++------ .../src/epoch/processPendingDeposits.ts | 6 ++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts index 7fb67a758673..d0540e507df3 100644 --- a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts @@ -151,12 +151,11 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { await this.datastore.init(); } const persistedKeys = await this.datastore.readKeys(); - for (const persistedKey of persistedKeys) { - const cp = datastoreKeyToCheckpoint(persistedKey); - this.cache.set(toCacheKey(cp), {type: CacheItemType.persisted, value: persistedKey}); - this.epochIndex.getOrDefault(cp.epoch).add(toRootHex(cp.root)); - } - this.logger.info("Loaded persisted checkpoint states from the last run", { + // all checkpoint states from the last run are not trusted, remove them + // otherwise if we have a bad checkpoint state from the last run, the node get stucked + // this was found during mekong devnet, see https://github.com/ChainSafe/lodestar/pull/7255 + await Promise.all(persistedKeys.map((key) => this.datastore.remove(key))); + this.logger.info("Removed persisted checkpoint states from the last run", { count: persistedKeys.length, maxEpochsInMemory: this.maxEpochsInMemory, }); diff --git a/packages/state-transition/src/epoch/processPendingDeposits.ts b/packages/state-transition/src/epoch/processPendingDeposits.ts index e00e1ef93c8c..d925aa1cc741 100644 --- a/packages/state-transition/src/epoch/processPendingDeposits.ts +++ b/packages/state-transition/src/epoch/processPendingDeposits.ts @@ -109,6 +109,12 @@ function applyPendingDeposit( addValidatorToRegistry(ForkSeq.electra, state, pubkey, withdrawalCredentials, amount); const newValidatorIndex = state.validators.length - 1; cache.isCompoundingValidatorArr[newValidatorIndex] = hasCompoundingWithdrawalCredential(withdrawalCredentials); + // set balance, so that the next deposit of same pubkey will increase the balance correctly + // this is to fix the double deposit issue found in mekong + // see https://github.com/ChainSafe/lodestar/pull/7255 + if (cachedBalances) { + cachedBalances[newValidatorIndex] = amount; + } } } else { // Increase balance From 36be6b39d1c290dd2884887e37313a11792de19b Mon Sep 17 00:00:00 2001 From: Phil Ngo Date: Thu, 28 Nov 2024 12:54:20 -0500 Subject: [PATCH 07/43] chore: bump package versions to 1.23.1 --- lerna.json | 2 +- packages/api/package.json | 10 +++++----- packages/beacon-node/package.json | 26 +++++++++++++------------- packages/cli/package.json | 26 +++++++++++++------------- packages/config/package.json | 8 ++++---- packages/db/package.json | 8 ++++---- packages/flare/package.json | 14 +++++++------- packages/fork-choice/package.json | 12 ++++++------ packages/light-client/package.json | 12 ++++++------ packages/logger/package.json | 6 +++--- packages/params/package.json | 2 +- packages/prover/package.json | 18 +++++++++--------- packages/reqresp/package.json | 12 ++++++------ packages/spec-test-util/package.json | 4 ++-- packages/state-transition/package.json | 10 +++++----- packages/test-utils/package.json | 6 +++--- packages/types/package.json | 4 ++-- packages/utils/package.json | 2 +- packages/validator/package.json | 18 +++++++++--------- 19 files changed, 100 insertions(+), 100 deletions(-) diff --git a/lerna.json b/lerna.json index 6d76d7f4a488..cdd74f75b2cf 100644 --- a/lerna.json +++ b/lerna.json @@ -4,7 +4,7 @@ ], "npmClient": "yarn", "useNx": true, - "version": "1.23.0", + "version": "1.23.1", "stream": true, "command": { "version": { diff --git a/packages/api/package.json b/packages/api/package.json index a787147f6f07..1f4f3e7c3fdf 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.23.0", + "version": "1.23.1", "type": "module", "exports": { ".": { @@ -72,10 +72,10 @@ "dependencies": { "@chainsafe/persistent-merkle-tree": "^0.8.0", "@chainsafe/ssz": "^0.18.0", - "@lodestar/config": "^1.23.0", - "@lodestar/params": "^1.23.0", - "@lodestar/types": "^1.23.0", - "@lodestar/utils": "^1.23.0", + "@lodestar/config": "^1.23.1", + "@lodestar/params": "^1.23.1", + "@lodestar/types": "^1.23.1", + "@lodestar/utils": "^1.23.1", "eventsource": "^2.0.2", "qs": "^6.11.1" }, diff --git a/packages/beacon-node/package.json b/packages/beacon-node/package.json index 87d80d27039c..ffce2f3eacfe 100644 --- a/packages/beacon-node/package.json +++ b/packages/beacon-node/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.23.0", + "version": "1.23.1", "type": "module", "exports": { ".": { @@ -120,18 +120,18 @@ "@libp2p/peer-id-factory": "^4.1.0", "@libp2p/prometheus-metrics": "^3.0.21", "@libp2p/tcp": "9.0.23", - "@lodestar/api": "^1.23.0", - "@lodestar/config": "^1.23.0", - "@lodestar/db": "^1.23.0", - "@lodestar/fork-choice": "^1.23.0", - "@lodestar/light-client": "^1.23.0", - "@lodestar/logger": "^1.23.0", - "@lodestar/params": "^1.23.0", - "@lodestar/reqresp": "^1.23.0", - "@lodestar/state-transition": "^1.23.0", - "@lodestar/types": "^1.23.0", - "@lodestar/utils": "^1.23.0", - "@lodestar/validator": "^1.23.0", + "@lodestar/api": "^1.23.1", + "@lodestar/config": "^1.23.1", + "@lodestar/db": "^1.23.1", + "@lodestar/fork-choice": "^1.23.1", + "@lodestar/light-client": "^1.23.1", + "@lodestar/logger": "^1.23.1", + "@lodestar/params": "^1.23.1", + "@lodestar/reqresp": "^1.23.1", + "@lodestar/state-transition": "^1.23.1", + "@lodestar/types": "^1.23.1", + "@lodestar/utils": "^1.23.1", + "@lodestar/validator": "^1.23.1", "@multiformats/multiaddr": "^12.1.3", "c-kzg": "^2.1.2", "datastore-core": "^9.1.1", diff --git a/packages/cli/package.json b/packages/cli/package.json index 287300183a7f..e1b2134f5f82 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@chainsafe/lodestar", - "version": "1.23.0", + "version": "1.23.1", "description": "Command line interface for lodestar", "author": "ChainSafe Systems", "license": "LGPL-3.0", @@ -62,17 +62,17 @@ "@libp2p/crypto": "^4.1.0", "@libp2p/peer-id": "^4.1.0", "@libp2p/peer-id-factory": "^4.1.0", - "@lodestar/api": "^1.23.0", - "@lodestar/beacon-node": "^1.23.0", - "@lodestar/config": "^1.23.0", - "@lodestar/db": "^1.23.0", - "@lodestar/light-client": "^1.23.0", - "@lodestar/logger": "^1.23.0", - "@lodestar/params": "^1.23.0", - "@lodestar/state-transition": "^1.23.0", - "@lodestar/types": "^1.23.0", - "@lodestar/utils": "^1.23.0", - "@lodestar/validator": "^1.23.0", + "@lodestar/api": "^1.23.1", + "@lodestar/beacon-node": "^1.23.1", + "@lodestar/config": "^1.23.1", + "@lodestar/db": "^1.23.1", + "@lodestar/light-client": "^1.23.1", + "@lodestar/logger": "^1.23.1", + "@lodestar/params": "^1.23.1", + "@lodestar/state-transition": "^1.23.1", + "@lodestar/types": "^1.23.1", + "@lodestar/utils": "^1.23.1", + "@lodestar/validator": "^1.23.1", "@multiformats/multiaddr": "^12.1.3", "deepmerge": "^4.3.1", "ethers": "^6.7.0", @@ -88,7 +88,7 @@ "yargs": "^17.7.1" }, "devDependencies": { - "@lodestar/test-utils": "^1.23.0", + "@lodestar/test-utils": "^1.23.1", "@types/debug": "^4.1.7", "@types/got": "^9.6.12", "@types/inquirer": "^9.0.3", diff --git a/packages/config/package.json b/packages/config/package.json index aadbf9e004b2..5a4edcf7879c 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/config", - "version": "1.23.0", + "version": "1.23.1", "description": "Chain configuration required for lodestar", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -65,8 +65,8 @@ ], "dependencies": { "@chainsafe/ssz": "^0.18.0", - "@lodestar/params": "^1.23.0", - "@lodestar/types": "^1.23.0", - "@lodestar/utils": "^1.23.0" + "@lodestar/params": "^1.23.1", + "@lodestar/types": "^1.23.1", + "@lodestar/utils": "^1.23.1" } } diff --git a/packages/db/package.json b/packages/db/package.json index 370caaa5f8c9..a332d316185b 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/db", - "version": "1.23.0", + "version": "1.23.1", "description": "DB modules of Lodestar", "author": "ChainSafe Systems", "homepage": "https://github.com/ChainSafe/lodestar#readme", @@ -36,12 +36,12 @@ }, "dependencies": { "@chainsafe/ssz": "^0.18.0", - "@lodestar/config": "^1.23.0", - "@lodestar/utils": "^1.23.0", + "@lodestar/config": "^1.23.1", + "@lodestar/utils": "^1.23.1", "classic-level": "^1.4.1", "it-all": "^3.0.4" }, "devDependencies": { - "@lodestar/logger": "^1.23.0" + "@lodestar/logger": "^1.23.1" } } diff --git a/packages/flare/package.json b/packages/flare/package.json index 1ec88f28ac4d..c3d262173321 100644 --- a/packages/flare/package.json +++ b/packages/flare/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/flare", - "version": "1.23.0", + "version": "1.23.1", "description": "Beacon chain debugging tool", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -60,12 +60,12 @@ "dependencies": { "@chainsafe/bls-keygen": "^0.4.0", "@chainsafe/blst": "^2.1.0", - "@lodestar/api": "^1.23.0", - "@lodestar/config": "^1.23.0", - "@lodestar/params": "^1.23.0", - "@lodestar/state-transition": "^1.23.0", - "@lodestar/types": "^1.23.0", - "@lodestar/utils": "^1.23.0", + "@lodestar/api": "^1.23.1", + "@lodestar/config": "^1.23.1", + "@lodestar/params": "^1.23.1", + "@lodestar/state-transition": "^1.23.1", + "@lodestar/types": "^1.23.1", + "@lodestar/utils": "^1.23.1", "source-map-support": "^0.5.21", "yargs": "^17.7.1" }, diff --git a/packages/fork-choice/package.json b/packages/fork-choice/package.json index 1ee9d043c925..cc86cd75b45f 100644 --- a/packages/fork-choice/package.json +++ b/packages/fork-choice/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.23.0", + "version": "1.23.1", "type": "module", "exports": "./lib/index.js", "types": "./lib/index.d.ts", @@ -37,11 +37,11 @@ }, "dependencies": { "@chainsafe/ssz": "^0.18.0", - "@lodestar/config": "^1.23.0", - "@lodestar/params": "^1.23.0", - "@lodestar/state-transition": "^1.23.0", - "@lodestar/types": "^1.23.0", - "@lodestar/utils": "^1.23.0" + "@lodestar/config": "^1.23.1", + "@lodestar/params": "^1.23.1", + "@lodestar/state-transition": "^1.23.1", + "@lodestar/types": "^1.23.1", + "@lodestar/utils": "^1.23.1" }, "keywords": [ "ethereum", diff --git a/packages/light-client/package.json b/packages/light-client/package.json index c942ff07ed44..f972250937ae 100644 --- a/packages/light-client/package.json +++ b/packages/light-client/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.23.0", + "version": "1.23.1", "type": "module", "exports": { ".": { @@ -77,11 +77,11 @@ "@chainsafe/blst": "^0.2.0", "@chainsafe/persistent-merkle-tree": "^0.8.0", "@chainsafe/ssz": "^0.18.0", - "@lodestar/api": "^1.23.0", - "@lodestar/config": "^1.23.0", - "@lodestar/params": "^1.23.0", - "@lodestar/types": "^1.23.0", - "@lodestar/utils": "^1.23.0", + "@lodestar/api": "^1.23.1", + "@lodestar/config": "^1.23.1", + "@lodestar/params": "^1.23.1", + "@lodestar/types": "^1.23.1", + "@lodestar/utils": "^1.23.1", "mitt": "^3.0.0" }, "devDependencies": { diff --git a/packages/logger/package.json b/packages/logger/package.json index b9e9beef39be..3e8d2095866b 100644 --- a/packages/logger/package.json +++ b/packages/logger/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.23.0", + "version": "1.23.1", "type": "module", "exports": { ".": { @@ -66,14 +66,14 @@ }, "types": "lib/index.d.ts", "dependencies": { - "@lodestar/utils": "^1.23.0", + "@lodestar/utils": "^1.23.1", "winston": "^3.8.2", "winston-daily-rotate-file": "^4.7.1", "winston-transport": "^4.5.0" }, "devDependencies": { "@chainsafe/threads": "^1.11.1", - "@lodestar/test-utils": "^1.23.0", + "@lodestar/test-utils": "^1.23.1", "@types/triple-beam": "^1.3.2", "triple-beam": "^1.3.0" }, diff --git a/packages/params/package.json b/packages/params/package.json index ce78e826ef18..203dd660571e 100644 --- a/packages/params/package.json +++ b/packages/params/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/params", - "version": "1.23.0", + "version": "1.23.1", "description": "Chain parameters required for lodestar", "author": "ChainSafe Systems", "license": "Apache-2.0", diff --git a/packages/prover/package.json b/packages/prover/package.json index eee42f4226c2..cbaa7dabdbf3 100644 --- a/packages/prover/package.json +++ b/packages/prover/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.23.0", + "version": "1.23.1", "type": "module", "exports": { ".": { @@ -69,13 +69,13 @@ "@ethereumjs/tx": "^4.1.2", "@ethereumjs/util": "^8.0.6", "@ethereumjs/vm": "^6.4.2", - "@lodestar/api": "^1.23.0", - "@lodestar/config": "^1.23.0", - "@lodestar/light-client": "^1.23.0", - "@lodestar/logger": "^1.23.0", - "@lodestar/params": "^1.23.0", - "@lodestar/types": "^1.23.0", - "@lodestar/utils": "^1.23.0", + "@lodestar/api": "^1.23.1", + "@lodestar/config": "^1.23.1", + "@lodestar/light-client": "^1.23.1", + "@lodestar/logger": "^1.23.1", + "@lodestar/params": "^1.23.1", + "@lodestar/types": "^1.23.1", + "@lodestar/utils": "^1.23.1", "ethereum-cryptography": "^2.0.0", "find-up": "^6.3.0", "http-proxy": "^1.18.1", @@ -84,7 +84,7 @@ "yargs": "^17.7.1" }, "devDependencies": { - "@lodestar/test-utils": "^1.23.0", + "@lodestar/test-utils": "^1.23.1", "@types/http-proxy": "^1.17.10", "@types/yargs": "^17.0.24", "axios": "^1.3.4", diff --git a/packages/reqresp/package.json b/packages/reqresp/package.json index 981016a363df..c2280ee8fc37 100644 --- a/packages/reqresp/package.json +++ b/packages/reqresp/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.23.0", + "version": "1.23.1", "type": "module", "exports": { ".": { @@ -54,9 +54,9 @@ "dependencies": { "@chainsafe/fast-crc32c": "^4.1.1", "@libp2p/interface": "^1.3.0", - "@lodestar/config": "^1.23.0", - "@lodestar/params": "^1.23.0", - "@lodestar/utils": "^1.23.0", + "@lodestar/config": "^1.23.1", + "@lodestar/params": "^1.23.1", + "@lodestar/utils": "^1.23.1", "it-all": "^3.0.4", "it-pipe": "^3.0.1", "snappy": "^7.2.2", @@ -65,8 +65,8 @@ "uint8arraylist": "^2.4.7" }, "devDependencies": { - "@lodestar/logger": "^1.23.0", - "@lodestar/types": "^1.23.0", + "@lodestar/logger": "^1.23.1", + "@lodestar/types": "^1.23.1", "libp2p": "1.4.3" }, "peerDependencies": { diff --git a/packages/spec-test-util/package.json b/packages/spec-test-util/package.json index 6f58e55d2201..c35814d8b57f 100644 --- a/packages/spec-test-util/package.json +++ b/packages/spec-test-util/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/spec-test-util", - "version": "1.23.0", + "version": "1.23.1", "description": "Spec test suite generator from yaml test files", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -62,7 +62,7 @@ "blockchain" ], "dependencies": { - "@lodestar/utils": "^1.23.0", + "@lodestar/utils": "^1.23.1", "axios": "^1.3.4", "rimraf": "^4.4.1", "snappyjs": "^0.7.0", diff --git a/packages/state-transition/package.json b/packages/state-transition/package.json index 5dfecfa9bbf6..31d638bb0b7e 100644 --- a/packages/state-transition/package.json +++ b/packages/state-transition/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.23.0", + "version": "1.23.1", "type": "module", "exports": { ".": { @@ -65,10 +65,10 @@ "@chainsafe/pubkey-index-map": "2.0.0", "@chainsafe/ssz": "^0.18.0", "@chainsafe/swap-or-not-shuffle": "^0.0.2", - "@lodestar/config": "^1.23.0", - "@lodestar/params": "^1.23.0", - "@lodestar/types": "^1.23.0", - "@lodestar/utils": "^1.23.0", + "@lodestar/config": "^1.23.1", + "@lodestar/params": "^1.23.1", + "@lodestar/types": "^1.23.1", + "@lodestar/utils": "^1.23.1", "bigint-buffer": "^1.1.5", "immutable": "^4.3.2" }, diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index c8fc5cd41c55..ff65198c3b35 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@lodestar/test-utils", "private": true, - "version": "1.23.0", + "version": "1.23.1", "description": "Test utilities reused across other packages", "author": "ChainSafe Systems", "license": "Apache-2.0", @@ -59,8 +59,8 @@ "dependencies": { "@chainsafe/bls-keystore": "^3.1.0", "@chainsafe/blst": "^2.1.0", - "@lodestar/params": "^1.23.0", - "@lodestar/utils": "^1.23.0", + "@lodestar/params": "^1.23.1", + "@lodestar/utils": "^1.23.1", "axios": "^1.3.4", "testcontainers": "^10.2.1", "tmp": "^0.2.1", diff --git a/packages/types/package.json b/packages/types/package.json index 9201ef2f4e88..fbaf060afbca 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.23.0", + "version": "1.23.1", "type": "module", "exports": { ".": { @@ -74,7 +74,7 @@ "types": "lib/index.d.ts", "dependencies": { "@chainsafe/ssz": "^0.18.0", - "@lodestar/params": "^1.23.0", + "@lodestar/params": "^1.23.1", "ethereum-cryptography": "^2.0.0" }, "keywords": [ diff --git a/packages/utils/package.json b/packages/utils/package.json index 9b6f5e797e68..babd09066e8c 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -11,7 +11,7 @@ "bugs": { "url": "https://github.com/ChainSafe/lodestar/issues" }, - "version": "1.23.0", + "version": "1.23.1", "type": "module", "exports": "./lib/index.js", "files": [ diff --git a/packages/validator/package.json b/packages/validator/package.json index 1566c08668b9..823d2dd86907 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -1,6 +1,6 @@ { "name": "@lodestar/validator", - "version": "1.23.0", + "version": "1.23.1", "description": "A Typescript implementation of the validator client", "author": "ChainSafe Systems", "license": "LGPL-3.0", @@ -47,17 +47,17 @@ "dependencies": { "@chainsafe/blst": "^2.1.0", "@chainsafe/ssz": "^0.18.0", - "@lodestar/api": "^1.23.0", - "@lodestar/config": "^1.23.0", - "@lodestar/db": "^1.23.0", - "@lodestar/params": "^1.23.0", - "@lodestar/state-transition": "^1.23.0", - "@lodestar/types": "^1.23.0", - "@lodestar/utils": "^1.23.0", + "@lodestar/api": "^1.23.1", + "@lodestar/config": "^1.23.1", + "@lodestar/db": "^1.23.1", + "@lodestar/params": "^1.23.1", + "@lodestar/state-transition": "^1.23.1", + "@lodestar/types": "^1.23.1", + "@lodestar/utils": "^1.23.1", "strict-event-emitter-types": "^2.0.0" }, "devDependencies": { - "@lodestar/test-utils": "^1.23.0", + "@lodestar/test-utils": "^1.23.1", "bigint-buffer": "^1.1.5", "rimraf": "^4.4.1" } From ffa4bb8cc1f34ac82ef0aa1248a359cdff47c7b8 Mon Sep 17 00:00:00 2001 From: twoeths Date: Wed, 27 Nov 2024 15:22:24 +0700 Subject: [PATCH 08/43] fix: prune checkpoint states at syncing time (#7241) * fix: prune checkpoint states at syncing time * fix: lint * fix: check-types in test --- packages/beacon-node/src/chain/chain.ts | 1 - .../stateCache/persistentCheckpointsCache.ts | 27 +++---------------- .../persistentCheckpointsCache.test.ts | 18 +++++-------- 3 files changed, 9 insertions(+), 37 deletions(-) diff --git a/packages/beacon-node/src/chain/chain.ts b/packages/beacon-node/src/chain/chain.ts index 144b73f0c01d..9141aa994224 100644 --- a/packages/beacon-node/src/chain/chain.ts +++ b/packages/beacon-node/src/chain/chain.ts @@ -293,7 +293,6 @@ export class BeaconChain implements IBeaconChain { metrics, logger, clock, - shufflingCache: this.shufflingCache, blockStateCache, bufferPool: this.bufferPool, datastore: fileDataStore diff --git a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts index 0719efcfd309..8663c0533ed8 100644 --- a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts @@ -6,7 +6,6 @@ import {loadCachedBeaconState} from "@lodestar/state-transition"; import {INTERVALS_PER_SLOT} from "@lodestar/params"; import {Metrics} from "../../metrics/index.js"; import {IClock} from "../../util/clock.js"; -import {ShufflingCache} from "../shufflingCache.js"; import {AllocSource, BufferPool, BufferWithKey} from "../../util/bufferPool.js"; import {StateCloneOpts} from "../regen/interface.js"; import {serializeState} from "../serializeState.js"; @@ -17,8 +16,6 @@ import {CheckpointHex, CacheItemType, CheckpointStateCache, BlockStateCache} fro export type PersistentCheckpointStateCacheOpts = { /** Keep max n states in memory, persist the rest to disk */ maxCPStateEpochsInMemory?: number; - /** for testing only */ - processLateBlock?: boolean; }; type PersistentCheckpointStateCacheModules = { @@ -26,7 +23,6 @@ type PersistentCheckpointStateCacheModules = { logger: Logger; clock?: IClock | null; signal?: AbortSignal; - shufflingCache: ShufflingCache; datastore: CPStateDatastore; blockStateCache: BlockStateCache; bufferPool?: BufferPool | null; @@ -102,24 +98,12 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { private preComputedCheckpoint: string | null = null; private preComputedCheckpointHits: number | null = null; private readonly maxEpochsInMemory: number; - // only for testing, default false for production - private readonly processLateBlock: boolean; private readonly datastore: CPStateDatastore; - private readonly shufflingCache: ShufflingCache; private readonly blockStateCache: BlockStateCache; private readonly bufferPool?: BufferPool | null; constructor( - { - metrics, - logger, - clock, - signal, - shufflingCache, - datastore, - blockStateCache, - bufferPool, - }: PersistentCheckpointStateCacheModules, + {metrics, logger, clock, signal, datastore, blockStateCache, bufferPool}: PersistentCheckpointStateCacheModules, opts: PersistentCheckpointStateCacheOpts ) { this.cache = new MapTracker(metrics?.cpStateCache); @@ -153,10 +137,8 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { throw new Error("maxEpochsInMemory must be >= 0"); } this.maxEpochsInMemory = opts.maxCPStateEpochsInMemory ?? DEFAULT_MAX_CP_STATE_EPOCHS_IN_MEMORY; - this.processLateBlock = opts.processLateBlock ?? false; // Specify different datastore for testing this.datastore = datastore; - this.shufflingCache = shufflingCache; this.blockStateCache = blockStateCache; this.bufferPool = bufferPool; } @@ -487,12 +469,9 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { // 2/3 of slot is the most free time of every slot, take that chance to persist checkpoint states // normally it should only persist checkpoint states at 2/3 of slot 0 of epoch await sleep(secToTwoThirdsSlot * 1000, this.signal); - } else if (!this.processLateBlock) { - // normally the block persist happens at 2/3 of slot 0 of epoch, if it's already late then just skip to allow other tasks to run - // there are plenty of chances in the same epoch to persist checkpoint states, also if block is late it could be reorged - this.logger.verbose("Skip persist checkpoint states", {blockSlot, root: blockRootHex}); - return 0; } + // at syncing time, it's critical to persist checkpoint states as soon as possible to avoid OOM during unfinality time + // if node is synced this is not a hot time because block comes late, we'll likely miss attestation already, or the block is orphaned const persistEpochs = sortedEpochs.slice(0, sortedEpochs.length - this.maxEpochsInMemory); for (const lowestEpoch of persistEpochs) { diff --git a/packages/beacon-node/test/unit/chain/stateCache/persistentCheckpointsCache.test.ts b/packages/beacon-node/test/unit/chain/stateCache/persistentCheckpointsCache.test.ts index f98b180fa983..aecec564bd92 100644 --- a/packages/beacon-node/test/unit/chain/stateCache/persistentCheckpointsCache.test.ts +++ b/packages/beacon-node/test/unit/chain/stateCache/persistentCheckpointsCache.test.ts @@ -90,10 +90,9 @@ describe("PersistentCheckpointStateCache", () => { { datastore, logger: testLogger(), - shufflingCache: new ShufflingCache(), blockStateCache: new FIFOBlockStateCache({}, {}), }, - {maxCPStateEpochsInMemory: 2, processLateBlock: true} + {maxCPStateEpochsInMemory: 2} ); cache.add(cp0a, states["cp0a"]); cache.add(cp0b, states["cp0b"]); @@ -165,10 +164,9 @@ describe("PersistentCheckpointStateCache", () => { { datastore, logger: testLogger(), - shufflingCache: new ShufflingCache(), blockStateCache: new FIFOBlockStateCache({}, {}), }, - {maxCPStateEpochsInMemory: 2, processLateBlock: true} + {maxCPStateEpochsInMemory: 2} ); cache.add(cp0a, states["cp0a"]); cache.add(cp0b, states["cp0b"]); @@ -242,10 +240,9 @@ describe("PersistentCheckpointStateCache", () => { { datastore, logger: testLogger(), - shufflingCache: new ShufflingCache(), blockStateCache: new FIFOBlockStateCache({}, {}), }, - {maxCPStateEpochsInMemory: 2, processLateBlock: true} + {maxCPStateEpochsInMemory: 2} ); cache.add(cp0a, states["cp0a"]); cache.add(cp0b, states["cp0b"]); @@ -548,10 +545,9 @@ describe("PersistentCheckpointStateCache", () => { { datastore, logger: testLogger(), - shufflingCache: new ShufflingCache(), blockStateCache: new FIFOBlockStateCache({}, {}), }, - {maxCPStateEpochsInMemory: 1, processLateBlock: true} + {maxCPStateEpochsInMemory: 1} ); cache.add(cp0a, states["cp0a"]); cache.add(cp0b, states["cp0b"]); @@ -820,10 +816,9 @@ describe("PersistentCheckpointStateCache", () => { { datastore, logger: testLogger(), - shufflingCache: new ShufflingCache(), blockStateCache: new FIFOBlockStateCache({}, {}), }, - {maxCPStateEpochsInMemory: 0, processLateBlock: true} + {maxCPStateEpochsInMemory: 0} ); cache.add(cp0a, states["cp0a"]); cache.add(cp0b, states["cp0b"]); @@ -911,10 +906,9 @@ describe("PersistentCheckpointStateCache", () => { { datastore, logger: testLogger(), - shufflingCache: new ShufflingCache(), blockStateCache: new FIFOBlockStateCache({}, {}), }, - {maxCPStateEpochsInMemory: 0, processLateBlock: true} + {maxCPStateEpochsInMemory: 0} ); const root1a = Buffer.alloc(32, 100); From 2c2326f2debd6b181669967aff17ead5e5c02826 Mon Sep 17 00:00:00 2001 From: twoeths Date: Wed, 27 Nov 2024 15:52:43 +0700 Subject: [PATCH 09/43] fix: sync cached isCompoundingValidatorArr at epoch transition (#7247) --- .../src/cache/epochTransitionCache.ts | 47 ++++++++++++------- .../epoch/processEffectiveBalanceUpdates.ts | 7 +-- .../src/epoch/processPendingDeposits.ts | 5 +- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index 2a35317fbc93..b21f940c28d5 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -1,27 +1,32 @@ -import {phase0, Epoch, RootHex, ValidatorIndex} from "@lodestar/types"; -import {intDiv, toRootHex} from "@lodestar/utils"; import { EPOCHS_PER_SLASHINGS_VECTOR, FAR_FUTURE_EPOCH, ForkSeq, - SLOTS_PER_HISTORICAL_ROOT, MIN_ACTIVATION_BALANCE, + SLOTS_PER_HISTORICAL_ROOT, } from "@lodestar/params"; +import {Epoch, RootHex, ValidatorIndex} from "@lodestar/types"; +import {intDiv, toRootHex} from "@lodestar/utils"; +import {processPendingAttestations} from "../epoch/processPendingAttestations.js"; import { - hasMarkers, - FLAG_UNSLASHED, + CachedBeaconStateAllForks, + CachedBeaconStateAltair, + CachedBeaconStatePhase0, + hasCompoundingWithdrawalCredential, +} from "../index.js"; +import {computeBaseRewardPerIncrement} from "../util/altair.js"; +import { + FLAG_CURR_HEAD_ATTESTER, + FLAG_CURR_SOURCE_ATTESTER, + FLAG_CURR_TARGET_ATTESTER, FLAG_ELIGIBLE_ATTESTER, + FLAG_PREV_HEAD_ATTESTER, FLAG_PREV_SOURCE_ATTESTER, FLAG_PREV_TARGET_ATTESTER, - FLAG_PREV_HEAD_ATTESTER, - FLAG_CURR_SOURCE_ATTESTER, - FLAG_CURR_TARGET_ATTESTER, - FLAG_CURR_HEAD_ATTESTER, + FLAG_UNSLASHED, + hasMarkers, } from "../util/attesterStatus.js"; -import {CachedBeaconStateAllForks, CachedBeaconStateAltair, CachedBeaconStatePhase0} from "../index.js"; -import {computeBaseRewardPerIncrement} from "../util/altair.js"; -import {processPendingAttestations} from "../epoch/processPendingAttestations.js"; export type EpochTransitionCacheOpts = { /** @@ -133,11 +138,7 @@ export interface EpochTransitionCache { flags: number[]; - /** - * Validators in the current epoch, should use it for read-only value instead of accessing state.validators directly. - * Note that during epoch processing, validators could be updated so need to use it with care. - */ - validators: phase0.Validator[]; + isCompoundingValidatorArr: boolean[]; /** * balances array will be populated by processRewardsAndPenalties() and consumed by processEffectiveBalanceUpdates(). @@ -209,6 +210,8 @@ const inclusionDelays = new Array(); /** WARNING: reused, never gc'd */ const flags = new Array(); /** WARNING: reused, never gc'd */ +const isCompoundingValidatorArr = new Array(); +/** WARNING: reused, never gc'd */ const nextEpochShufflingActiveValidatorIndices = new Array(); export function beforeProcessEpoch( @@ -262,6 +265,10 @@ export function beforeProcessEpoch( // TODO: optimize by combining the two loops // likely will require splitting into phase0 and post-phase0 versions + if (forkSeq >= ForkSeq.electra) { + isCompoundingValidatorArr.length = validatorCount; + } + // Clone before being mutated in processEffectiveBalanceUpdates epochCtx.beforeEpochTransition(); @@ -298,6 +305,10 @@ export function beforeProcessEpoch( flags[i] = flag; + if (forkSeq >= ForkSeq.electra) { + isCompoundingValidatorArr[i] = hasCompoundingWithdrawalCredential(validator.withdrawalCredentials); + } + if (isActiveCurr) { totalActiveStakeByIncrement += effectiveBalancesByIncrements[i]; } else { @@ -511,7 +522,7 @@ export function beforeProcessEpoch( proposerIndices, inclusionDelays, flags, - validators, + isCompoundingValidatorArr, // Will be assigned in processRewardsAndPenalties() balances: undefined, }; diff --git a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts index 1fe5c92eea1a..6e71d8ccdc73 100644 --- a/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts +++ b/packages/state-transition/src/epoch/processEffectiveBalanceUpdates.ts @@ -5,10 +5,11 @@ import { HYSTERESIS_QUOTIENT, HYSTERESIS_UPWARD_MULTIPLIER, MAX_EFFECTIVE_BALANCE, + MAX_EFFECTIVE_BALANCE_ELECTRA, + MIN_ACTIVATION_BALANCE, TIMELY_TARGET_FLAG_INDEX, } from "@lodestar/params"; import {EpochTransitionCache, CachedBeaconStateAllForks, BeaconStateAltair} from "../types.js"; -import {getMaxEffectiveBalance} from "../util/validator.js"; /** Same to https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.5/specs/altair/beacon-chain.md#has_flag */ const TIMELY_TARGET = 1 << TIMELY_TARGET_FLAG_INDEX; @@ -43,7 +44,7 @@ export function processEffectiveBalanceUpdates( // and updated in processPendingDeposits() and processPendingConsolidations() // so it's recycled here for performance. const balances = cache.balances ?? state.balances.getAll(); - const currentEpochValidators = cache.validators; + const isCompoundingValidatorArr = cache.isCompoundingValidatorArr; let numUpdate = 0; for (let i = 0, len = balances.length; i < len; i++) { @@ -58,7 +59,7 @@ export function processEffectiveBalanceUpdates( effectiveBalanceLimit = MAX_EFFECTIVE_BALANCE; } else { // from electra, effectiveBalanceLimit is per validator - effectiveBalanceLimit = getMaxEffectiveBalance(currentEpochValidators[i].withdrawalCredentials); + effectiveBalanceLimit = isCompoundingValidatorArr[i] ? MAX_EFFECTIVE_BALANCE_ELECTRA : MIN_ACTIVATION_BALANCE; } if ( diff --git a/packages/state-transition/src/epoch/processPendingDeposits.ts b/packages/state-transition/src/epoch/processPendingDeposits.ts index 53af3ab38763..b25cf1d040a4 100644 --- a/packages/state-transition/src/epoch/processPendingDeposits.ts +++ b/packages/state-transition/src/epoch/processPendingDeposits.ts @@ -2,8 +2,9 @@ import {FAR_FUTURE_EPOCH, ForkSeq, GENESIS_SLOT, MAX_PENDING_DEPOSITS_PER_EPOCH} import {PendingDeposit} from "@lodestar/types/lib/electra/types.js"; import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js"; import {increaseBalance} from "../util/balance.js"; -import {getActivationExitChurnLimit} from "../util/validator.js"; +import {hasCompoundingWithdrawalCredential} from "../util/electra.js"; import {computeStartSlotAtEpoch} from "../util/epoch.js"; +import {getActivationExitChurnLimit} from "../util/validator.js"; import {addValidatorToRegistry, isValidDepositSignature} from "../block/processDeposit.js"; /** @@ -106,6 +107,8 @@ function applyPendingDeposit( // Verify the deposit signature (proof of possession) which is not checked by the deposit contract if (isValidDepositSignature(state.config, pubkey, withdrawalCredentials, amount, signature)) { addValidatorToRegistry(ForkSeq.electra, state, pubkey, withdrawalCredentials, amount); + const newValidatorIndex = state.validators.length - 1; + cache.isCompoundingValidatorArr[newValidatorIndex] = hasCompoundingWithdrawalCredential(withdrawalCredentials); } } else { // Increase balance From f1e81982a3df27a88d107137e79556225db46015 Mon Sep 17 00:00:00 2001 From: twoeths Date: Wed, 27 Nov 2024 17:01:15 +0700 Subject: [PATCH 10/43] fix: handle outOfRangeData when range sync Deneb (#7249) * fix: handle outOfRangeData for beaconBlocksMaybeBlobsByRange() * fix: lint * fix: archiveBlocks - handle deneb outOfRangeData block --- .../src/chain/archiver/archiveBlocks.ts | 44 ++++++++++++++----- .../reqresp/beaconBlocksMaybeBlobsByRange.ts | 8 ++-- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/packages/beacon-node/src/chain/archiver/archiveBlocks.ts b/packages/beacon-node/src/chain/archiver/archiveBlocks.ts index 8e1ac456579a..320715f09dda 100644 --- a/packages/beacon-node/src/chain/archiver/archiveBlocks.ts +++ b/packages/beacon-node/src/chain/archiver/archiveBlocks.ts @@ -59,8 +59,8 @@ export async function archiveBlocks( }); if (finalizedPostDeneb) { - await migrateBlobSidecarsFromHotToColdDb(config, db, finalizedCanonicalBlockRoots); - logger.verbose("Migrated blobSidecars from hot DB to cold DB"); + const migrate = await migrateBlobSidecarsFromHotToColdDb(config, db, finalizedCanonicalBlockRoots, currentEpoch); + logger.verbose(migrate ? "Migrated blobSidecars from hot DB to cold DB" : "Skip blobSidecars migration"); } } @@ -157,22 +157,36 @@ async function migrateBlocksFromHotToColdDb(db: IBeaconDb, blocks: BlockRootSlot } } +/** + * Migrate blobSidecars from hot db to cold db. + * @returns true if we do that, false if block is out of range data. + */ async function migrateBlobSidecarsFromHotToColdDb( config: ChainForkConfig, db: IBeaconDb, - blocks: BlockRootSlot[] -): Promise { + blocks: BlockRootSlot[], + currentEpoch: Epoch +): Promise { + let result = false; + for (let i = 0; i < blocks.length; i += BLOB_SIDECAR_BATCH_SIZE) { const toIdx = Math.min(i + BLOB_SIDECAR_BATCH_SIZE, blocks.length); const canonicalBlocks = blocks.slice(i, toIdx); // processCanonicalBlocks - if (canonicalBlocks.length === 0) return; + if (canonicalBlocks.length === 0) return false; // load Buffer instead of ssz deserialized to improve performance const canonicalBlobSidecarsEntries: KeyValue[] = await Promise.all( canonicalBlocks - .filter((block) => config.getForkSeq(block.slot) >= ForkSeq.deneb) + .filter((block) => { + const blockSlot = block.slot; + const blockEpoch = computeEpochAtSlot(blockSlot); + return ( + config.getForkSeq(blockSlot) >= ForkSeq.deneb && + blockEpoch >= currentEpoch - config.MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS + ); + }) .map(async (block) => { const bytes = await db.blobSidecars.getBinary(block.root); if (!bytes) { @@ -182,12 +196,20 @@ async function migrateBlobSidecarsFromHotToColdDb( }) ); - // put to blockArchive db and delete block db - await Promise.all([ - db.blobSidecarsArchive.batchPutBinary(canonicalBlobSidecarsEntries), - db.blobSidecars.batchDelete(canonicalBlocks.map((block) => block.root)), - ]); + const migrate = canonicalBlobSidecarsEntries.length > 0; + + if (migrate) { + // put to blockArchive db and delete block db + await Promise.all([ + db.blobSidecarsArchive.batchPutBinary(canonicalBlobSidecarsEntries), + db.blobSidecars.batchDelete(canonicalBlocks.map((block) => block.root)), + ]); + } + + result = result || migrate; } + + return result; } /** diff --git a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts index 3d71087c8fd8..041f1c59cc0a 100644 --- a/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts +++ b/packages/beacon-node/src/network/reqresp/beaconBlocksMaybeBlobsByRange.ts @@ -36,8 +36,9 @@ export async function beaconBlocksMaybeBlobsByRange( return blocks.map((block) => getBlockInput.preData(config, block.data, BlockSource.byRange, block.bytes)); } + // From Deneb // Only request blobs if they are recent enough - if (computeEpochAtSlot(startSlot) >= currentEpoch - config.MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS) { + if (startEpoch >= currentEpoch - config.MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS) { const [allBlocks, allBlobSidecars] = await Promise.all([ network.sendBeaconBlocksByRange(peerId, request), network.sendBlobSidecarsByRange(peerId, request), @@ -46,8 +47,9 @@ export async function beaconBlocksMaybeBlobsByRange( return matchBlockWithBlobs(config, allBlocks, allBlobSidecars, endSlot, BlockSource.byRange, BlobsSource.byRange); } - // Post Deneb but old blobs - throw Error("Cannot sync blobs outside of blobs prune window"); + // Data is out of range, only request blocks + const blocks = await network.sendBeaconBlocksByRange(peerId, request); + return blocks.map((block) => getBlockInput.outOfRangeData(config, block.data, BlockSource.byRange, block.bytes)); } // Assumes that the blobs are in the same sequence as blocks, doesn't require block to be sorted From 7f96e709366cbcfe860d656ca5787328debc5b2d Mon Sep 17 00:00:00 2001 From: twoeths Date: Thu, 28 Nov 2024 14:20:20 +0700 Subject: [PATCH 11/43] fix: sync cached balance when adding new validator to registry (#7255) * fix: sync cached balance when adding new validator to registry * chore: add more comments * fix: remove persisted checkpoint states from the previous run at startup --- .../chain/stateCache/persistentCheckpointsCache.ts | 11 +++++------ .../src/epoch/processPendingDeposits.ts | 6 ++++++ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts index 8663c0533ed8..5573366e3523 100644 --- a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts @@ -151,12 +151,11 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { await this.datastore.init(); } const persistedKeys = await this.datastore.readKeys(); - for (const persistedKey of persistedKeys) { - const cp = datastoreKeyToCheckpoint(persistedKey); - this.cache.set(toCacheKey(cp), {type: CacheItemType.persisted, value: persistedKey}); - this.epochIndex.getOrDefault(cp.epoch).add(toRootHex(cp.root)); - } - this.logger.info("Loaded persisted checkpoint states from the last run", { + // all checkpoint states from the last run are not trusted, remove them + // otherwise if we have a bad checkpoint state from the last run, the node get stucked + // this was found during mekong devnet, see https://github.com/ChainSafe/lodestar/pull/7255 + await Promise.all(persistedKeys.map((key) => this.datastore.remove(key))); + this.logger.info("Removed persisted checkpoint states from the last run", { count: persistedKeys.length, maxEpochsInMemory: this.maxEpochsInMemory, }); diff --git a/packages/state-transition/src/epoch/processPendingDeposits.ts b/packages/state-transition/src/epoch/processPendingDeposits.ts index b25cf1d040a4..8384295350f8 100644 --- a/packages/state-transition/src/epoch/processPendingDeposits.ts +++ b/packages/state-transition/src/epoch/processPendingDeposits.ts @@ -109,6 +109,12 @@ function applyPendingDeposit( addValidatorToRegistry(ForkSeq.electra, state, pubkey, withdrawalCredentials, amount); const newValidatorIndex = state.validators.length - 1; cache.isCompoundingValidatorArr[newValidatorIndex] = hasCompoundingWithdrawalCredential(withdrawalCredentials); + // set balance, so that the next deposit of same pubkey will increase the balance correctly + // this is to fix the double deposit issue found in mekong + // see https://github.com/ChainSafe/lodestar/pull/7255 + if (cachedBalances) { + cachedBalances[newValidatorIndex] = amount; + } } } else { // Increase balance From 9f4e7e6703866721202c68a7c740fd59786c0b66 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Wed, 27 Nov 2024 07:46:14 +0100 Subject: [PATCH 12/43] chore: skip web3_provider unit tests (#7252) --- packages/prover/test/unit/web3_provider.node.test.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/prover/test/unit/web3_provider.node.test.ts b/packages/prover/test/unit/web3_provider.node.test.ts index d1f281175b56..896715699e95 100644 --- a/packages/prover/test/unit/web3_provider.node.test.ts +++ b/packages/prover/test/unit/web3_provider.node.test.ts @@ -7,7 +7,8 @@ import {ProofProvider} from "../../src/proof_provider/proof_provider.js"; import {LCTransport, Web3ProviderType} from "../../src/interfaces.js"; import {JsonRpcRequest, JsonRpcRequestOrBatch, JsonRpcResponse} from "../../src/types.js"; -describe("web3_provider", () => { +// https://github.com/ChainSafe/lodestar/issues/7250 +describe.skip("web3_provider", () => { afterEach(() => { vi.clearAllMocks(); }); From aaac34aeab632615ceb76ced2b89ca535371557a Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 29 Nov 2024 11:18:21 +0100 Subject: [PATCH 13/43] fix: do not throw error when trying to prune missing directory (#7257) --- packages/cli/src/util/pruneOldFilesInDir.ts | 4 ++++ packages/cli/test/unit/util/pruneOldFilesInDir.test.ts | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/packages/cli/src/util/pruneOldFilesInDir.ts b/packages/cli/src/util/pruneOldFilesInDir.ts index 3d5cc2b60bcb..8b8dfe754059 100644 --- a/packages/cli/src/util/pruneOldFilesInDir.ts +++ b/packages/cli/src/util/pruneOldFilesInDir.ts @@ -2,6 +2,10 @@ import fs from "node:fs"; import path from "node:path"; export function pruneOldFilesInDir(dirpath: string, maxAgeMs: number): number { + if (!fs.existsSync(dirpath)) { + return 0; // Nothing to prune + } + let deletedFileCount = 0; for (const entryName of fs.readdirSync(dirpath)) { const entryPath = path.join(dirpath, entryName); diff --git a/packages/cli/test/unit/util/pruneOldFilesInDir.test.ts b/packages/cli/test/unit/util/pruneOldFilesInDir.test.ts index a50c59547688..ee5532a358ee 100644 --- a/packages/cli/test/unit/util/pruneOldFilesInDir.test.ts +++ b/packages/cli/test/unit/util/pruneOldFilesInDir.test.ts @@ -55,6 +55,10 @@ describe("pruneOldFilesInDir", () => { expect(fs.existsSync(emptyDir)).toBe(false); }); + it("should handle missing directories", () => { + expect(() => pruneOldFilesInDir(path.join(dataDir, "does-not-exist"), DAYS_TO_MS)).not.toThrowError(); + }); + function createFileWithAge(path: string, ageInDays: number): void { // Create a new empty file fs.closeSync(fs.openSync(path, "w")); From c9af6c37f985928491ed1060c4fba453bc9dd0c3 Mon Sep 17 00:00:00 2001 From: Phil Ngo <58080811+philknows@users.noreply.github.com> Date: Fri, 29 Nov 2024 14:17:43 -0500 Subject: [PATCH 14/43] docs: update documentation Oct 2024 (#7178) * docs update oct 2024 init * Reconfig quickstart nav and minor fixes * fix lint * spelling fixes * minor fixes and add to wordlist * prettier fix * add to wordlist * sort wordlist * modify dominance to include lighthouse * fix typescript casing and add recommendation * add selection and boost_factor with keymanager notice * update wordlist * remove builder enabled and add keymanager api * spelling --------- Co-authored-by: Nico Flaig --- .wordlist.txt | 26 + docs/docusaurus.config.ts | 32 +- docs/pages/google0c42298b7ec08b7e.html | 1 - docs/pages/index.md | 24 +- docs/pages/introduction.md | 24 +- .../run/beacon-management/starting-a-node.md | 126 +-- .../pages/run/getting-started/installation.md | 6 +- .../quick-start-custom-guide.md | 728 ++++++++++++++++++ docs/pages/run/getting-started/quick-start.md | 28 +- .../logging-and-metrics/client-monitoring.md | 2 +- .../run/logging-and-metrics/dashboards.md | 0 .../run/logging-and-metrics/log-management.md | 3 - .../logging-and-metrics/metrics-management.md | 0 .../logging-and-metrics/prometheus-grafana.md | 10 +- .../validator-management/proposer-config.md | 48 ++ .../validator-management/vc-configuration.md | 4 + docs/pages/supporting-libraries/libraries.md | 4 +- docs/pages/trouble-shooting.md | 1 - docs/sidebars.ts | 21 +- packages/light-client/README.md | 2 +- packages/prover/README.md | 2 +- 21 files changed, 954 insertions(+), 138 deletions(-) delete mode 100644 docs/pages/google0c42298b7ec08b7e.html create mode 100644 docs/pages/run/getting-started/quick-start-custom-guide.md delete mode 100644 docs/pages/run/logging-and-metrics/dashboards.md delete mode 100644 docs/pages/run/logging-and-metrics/log-management.md delete mode 100644 docs/pages/run/logging-and-metrics/metrics-management.md create mode 100644 docs/pages/run/validator-management/proposer-config.md delete mode 100644 docs/pages/trouble-shooting.md diff --git a/.wordlist.txt b/.wordlist.txt index a2e642a926a0..2c28fc784061 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -1,3 +1,4 @@ +API APIs Andreas Antonopoulos @@ -13,6 +14,7 @@ Casper Chai ChainSafe Codespaces +CoinCashew Corepack Customizations DPoS @@ -29,6 +31,7 @@ ENRs ETH Edgington Erigon +Esat EthStaker EtherScan Ethereum @@ -37,8 +40,11 @@ FINDNODE FX Flamegraph Flamegraphs +GPG Geth Github +Goerli +Golang Gossipsub Grafana Grandine @@ -46,6 +52,7 @@ HTTPS HackMD Hashicorp Homebrew +Hyperledger IPFS IPv Infura @@ -60,15 +67,18 @@ LGPLv LMD LPoS LTS +LVM Lerna MEV MacOS Metamask +MevBoost ModuleNotFoundError Monorepo NPM NVM Nethermind +Nim NodeJS NodeSource OSI @@ -81,9 +91,11 @@ Quickstart RPC Reth Ryzen +SFTP SHA SSD SSZ +Somer Stakehouse TOC TTD @@ -103,6 +115,7 @@ backfill beaconcha blockRoot blockchain +blockspace blst bootnode bootnodes @@ -110,6 +123,7 @@ bundlers chainConfig chainsafe chiado +chmod cli cmd codebase @@ -125,6 +139,8 @@ dApp dApps ddos decrypt +decrypted +derypted deserialization dev devcontainer @@ -139,30 +155,38 @@ env envs ephemery ethers +feeRecipient flamegraph flamegraphs floodsub +fsSL getNetworkIdentity gnosis +gpg heapdump heaptrack holesky interop js +keymanager keypair +keyrings keystore keystores libp lightclient linter +liveness lldb llnode lockfile mainnet malloc +mbps mdns merkle merkleization +misconfiguration mmeshsub monorepo multiaddr @@ -199,6 +223,7 @@ ssz stakers subnet subnets +sudo tcp testnet testnets @@ -215,6 +240,7 @@ vite vitest webpack wip +xRelayPubKey xcode yaml yamux diff --git a/docs/docusaurus.config.ts b/docs/docusaurus.config.ts index e37cee619e81..3e57867009f8 100644 --- a/docs/docusaurus.config.ts +++ b/docs/docusaurus.config.ts @@ -97,29 +97,23 @@ const config: Config = { style: "dark", links: [ { - title: "Docs", - items: [ - { - label: "Introduction", - to: "/introduction", - }, - ], + label: 'Lodestar Website', + href: 'https://lodestar.chainsafe.io', }, { - title: "Community", - items: [ - { - label: "Discord", - href: "https://discord.com/invite/yjyvFRP", - }, - { - label: "Twitter", - href: "https://twitter.com/lodestar_eth", - }, - ], + label: 'Discord', + href: 'https://discord.com/invite/yjyvFRP', + }, + { + label: 'Twitter/X', + href: 'https://x.com/lodestar_eth', + }, + { + label: 'Github', + href: 'https://github.com/ChainSafe/lodestar', }, ], - copyright: `Copyright © ${new Date().getFullYear()} ChainSafe, Inc.`, + copyright: `Copyright © ${new Date().getFullYear()} ChainSafe. Built with Docusaurus.` , }, colorMode: { respectPrefersColorScheme: false, diff --git a/docs/pages/google0c42298b7ec08b7e.html b/docs/pages/google0c42298b7ec08b7e.html deleted file mode 100644 index 7edebde149af..000000000000 --- a/docs/pages/google0c42298b7ec08b7e.html +++ /dev/null @@ -1 +0,0 @@ -google-site-verification: google0c42298b7ec08b7e.html \ No newline at end of file diff --git a/docs/pages/index.md b/docs/pages/index.md index c74ad6470d7d..2bca344b84cb 100644 --- a/docs/pages/index.md +++ b/docs/pages/index.md @@ -4,14 +4,14 @@ title: Home ![lodestar logo](../../assets/lodestar_icon_text_black_stroke.png) -## Welcome to the Lodestar documentation +## Welcome to the Lodestar Documentation -> **Lodestar is an open-source Ethereum Consensus client and Typescript ecosystem, maintained by ChainSafe Systems** +> **Lodestar is an open-source Ethereum Consensus client and TypeScript ecosystem, maintained by ChainSafe Systems** -### Getting started +### Getting Started - Follow the instructions for [build from source](./run/getting-started/installation#build-from-source), [binaries](./run/getting-started/installation#binaries), or [Docker](./run/getting-started/installation#docker-installation) to install Lodestar. Or use our [Lodestar Quickstart scripts](https://github.com/ChainSafe/lodestar-quickstart). -- Use [Lodestar libraries](./supporting-libraries/index.md) in your next Ethereum Typescript project. +- Use [Lodestar libraries](./supporting-libraries/index.md) in your next Ethereum TypeScript project. - Run a beacon node on [mainnet or a public testnet](./run/beacon-management/starting-a-node.md). - Utilize the whole stack by [starting a local testnet](./contribution/advanced-topics/setting-up-a-testnet.md). - View the Lodestar Beacon [CLI commands and options](./run/beacon-management/beacon-cli.md) @@ -26,14 +26,18 @@ Hardware specifications minimum / recommended, to run the Lodestar client. | | Minimum | Recommended | | --------- | -------------------------------------- | -------------------------------------- | | Processor | Intel Core i3–9100 or AMD Ryzen 5 3450 | Intel Core i7–9700 or AMD Ryzen 7 4700 | -| Memory | 16GB RAM | 32GB RAM | -| Storage | 100GB available space SSD | 1TB available space SSD | -| Internet | Broadband connection | Broadband connection | +| Memory | 8 GB RAM | 16 GB RAM | +| Storage | 130 GB available space SSD | 200 GB available space SSD | +| Internet | Reliable broadband with 10mbps upload | Reliable broadband with >10mbps upload | -## About these docs +### Execution Client -This documentation is open source, contribute at [Github Lodestar repository /docs](https://github.com/ChainSafe/lodestar/tree/unstable/docs). +If you run the [execution client](https://ethereum.org/en/developers/docs/nodes-and-clients/#execution-clients) on the same host, you will need to check their requirements and add them to the above requirements. Broadly, to run both an execution and a consensus client on the same machine, we recommend a 4 TB SSD and 32 GB RAM. -## Need assistance? +## About These Docs + +This documentation is open source, contribute on our [Github Lodestar repository /docs](https://github.com/ChainSafe/lodestar/tree/unstable/docs). + +## Need Assistance? If you have questions about this documentation, feel free to talk to us on our [ChainSafe Discord](https://discord.gg/yjyvFRP) or [open an issue](https://github.com/ChainSafe/lodestar/issues/new/choose) and a member of the team or our community will be happy to assist you. diff --git a/docs/pages/introduction.md b/docs/pages/introduction.md index ab864b8a9df8..f74d0b70768d 100644 --- a/docs/pages/introduction.md +++ b/docs/pages/introduction.md @@ -1,29 +1,29 @@ # Introduction -Ethereum is one of the most profoundly important inventions in recent history. It is a decentralized, open-source blockchain featuring smart contract functionality. It is the second-largest cryptocurrency by market capitalization, after Bitcoin, and is the most actively used blockchain. Ethereum was proposed in 2013 by programmer Vitalik Buterin. Development was crowdfunded in 2014, and the network went live on 30 July 2015, with 72 million coins premined. ChainSafe was founded not too long afterwards and has been actively working in the Ethereum space ever since. We are proud to develop Lodestar and to present this documentation as a resource for the Ethereum community. +Ethereum is one of the most profoundly important inventions in recent history. It is a decentralized, open-source blockchain featuring smart contract functionality. It is the second-largest cryptocurrency by market capitalization, after Bitcoin, and is the second largest blockchain by market capitalization. Ethereum was proposed in 2013 by programmer Vitalik Buterin. Development was crowdfunded in 2014, and the network went live on 30 July 2015, with 72 million coins premined. ChainSafe was founded not too long afterwards in 2017 and has been actively working in the Ethereum ecosystem ever since. We are proud to develop Lodestar, the only TypeScript based consensus client, and to present this documentation as a resource for the Ethereum community. ## Proof of Stake -In Ethereum's Proof of Stake (PoS) model, validators replace miners from the Proof of Work (PoW) system. Validators are Ethereum stakeholders who lock up a portion of their Ether as a stake. The protocol randomly selects these validators to propose new blocks. The chance of being chosen is tied to the size of their stake: the more Ether staked, the higher the probability of being selected to propose the block. Proposers receive transaction fees and block rewards as incentives. Validators are also responsible for voting on the validity of blocks proposed by other validators. However, they face penalties, known as slashing, for actions like double-signing, votes on a block that is not in the majority or going offline, ensuring network integrity and reliability. The PoS mechanism significantly reduces energy consumption compared to PoW, because it does not require extensive computational power. Moreover, PoS tends to facilitate faster transaction validations and block creations, enhancing the overall performance and scalability of the network. +In Ethereum's Proof of Stake (PoS) model, validators replace miners from the Proof of Work (PoW) system. Validators are Ethereum stakeholders who lock up a portion of their Ether as a stake. The protocol randomly selects these validators to propose new blocks. The chance of being chosen is tied to the size of their stake: the more Ether staked, the higher the probability of being selected to propose the block. Proposers receive transaction fees and block rewards as incentives. Validators are also responsible for voting on the validity of blocks proposed by other validators. However, they also face penalties, known as slashing, for actions like signing two different block proposals in the same slot or voting on two different attestations for the same target epoch, which creates conflicting states. The PoS mechanism significantly reduces energy consumption compared to PoW, because it does not require extensive computational power. Moreover, PoS tends to facilitate faster transaction validations and block creations, enhancing the overall performance and scalability of the network. ## Consensus Clients -In an effort to promote client diversity there are several beacon-nodes being developed. Each is programmed in a different language and by a different team. The following is a list of the current beacon-node clients: +In an effort to promote client diversity there are several consensus beacon nodes being developed. Each is programmed in a different language and by a different team. The following is a list of the current open source consensus clients in alphabetical order: -- [Lodestar](https://lodestar.chainsafe.io/) -- [Prysm](https://prysmaticlabs.com/) -- [Lighthouse](https://lighthouse.sigmaprime.io/) -- [Teku](https://consensys.net/knowledge-base/ethereum-2/teku/) -- [Nimbus](https://nimbus.team/) -- [Grandine](https://grandine.io) +- [Grandine (Rust)](https://grandine.io) +- [Lighthouse (Rust)](https://lighthouse.sigmaprime.io/) +- [Lodestar (TypeScript)](https://lodestar.chainsafe.io/) +- [Nimbus (Nim)](https://nimbus.team/) +- [Prysm (Golang)](https://prysmaticlabs.com/) +- [Teku (Java)](https://consensys.net/knowledge-base/ethereum-2/teku/) ## Why Client Diversity? -The Ethereum network's robustness is significantly enhanced by its client diversity, whereby multiple, independently-developed clients conforming to a common specification facilitate seamless interaction and function equivalently across nodes. This client variety not only fosters a rich ecosystem but also provides a buffer against network-wide issues stemming from bugs or malicious attacks targeted at particular clients. For instance, during the Shanghai denial-of-service attack in 2016, the diversified client structure enabled the network to withstand the assault, underscoring the resilience afforded by multiple client configurations. +The Ethereum network's robustness is significantly enhanced by its client diversity, whereby multiple, independently-developed clients conforming to a common specification, facilitating seamless interaction and function equivalently across different nodes. This client variety not only fosters a rich ecosystem but also provides a buffer against network-wide issues stemming from bugs or malicious attacks targeted at particular clients. For instance, during the Shanghai denial-of-service attack in 2016, the diversified client structure enabled the network to withstand the assault, underscoring the resilience afforded by multiple client configurations. -On the consensus layer, client distribution is crucial for maintaining network integrity and finality, ensuring transactions are irreversible once validated. A balanced spread of nodes across various clients helps mitigate risks associated with potential bugs or attacks that could, in extreme cases, derail the consensus process or lead to incorrect chain splits, thereby jeopardizing the network's stability and trust. While the data suggests a dominance of Prysm client on the consensus layer, efforts are ongoing to promote a more even distribution among others like Lighthouse, Teku, Nimbus and Grandine. Encouraging the adoption of minority clients, bolstering their documentation, and leveraging real-time client diversity dashboards are among the strategies being employed to enhance client diversity, which in turn fortifies the Ethereum consensus layer against adversities and fosters a healthier decentralized network ecosystem. +On the consensus layer, client distribution is crucial for maintaining network integrity and finality, ensuring transactions are irreversible once validated. A balanced spread of nodes across various clients help to mitigate risks associated with potential bugs or attacks that could, in extreme cases, derail the consensus process (liveness failure) or lead to incorrect chain splits (forking), thereby jeopardizing the network's stability and trust. While the data suggests a [dominance of the Prysm and Lighthouse clients](https://clientdiversity.org) on the consensus layer, efforts are ongoing to promote a more even distribution among others clients. Encouraging the adoption of minority clients, bolstering their documentation, and leveraging real-time client diversity dashboards are among the strategies being employed to enhance client diversity, which in turn fortifies the Ethereum consensus layer against adversities and fosters a healthier decentralized network. -The non-finality event in May 2023 on the Ethereum network posed a significant challenge. The issue arose from attestations for a fork, which necessitated state replays to validate the attestations, causing a notable strain on system resources. As a result, nodes fell out of sync, which deterred the accurate tracking of the actual head of the chain. This situation was exacerbated by a decline in attestations during specific epochs, further hampering the consensus mechanism. The Lodestar team noticed late attestations several weeks prior to the event and implemented a feature that attempted to address such challenges by not processing untimely attestations, and thus not requiring expensive state replays​. While it was done for slightly different reasons, the result was the same. Lodestar was able to follow the chain correctly and helped to stabilize the network. This example underscored the importance of client diversity and network resilience against potential forks and replay attacks. These are considered realistic threats, especially in the context of system complexity like in Ethereum's consensus mechanism. +The [non-finality event of May 2023](https://medium.com/offchainlabs/post-mortem-report-ethereum-mainnet-finality-05-11-2023-95e271dfd8b2) on the Ethereum network posed a significant challenge. The issue arose from attestations for a fork, which necessitated state replays to validate the attestations, causing a notable strain on system resources. As a result, nodes fell out of sync, which deterred the accurate tracking of the actual head of the chain. This situation was exacerbated by a decline in attestations during specific epochs, further hampering the consensus mechanism from reaching finality. The Lodestar team noticed late attestations several weeks prior to the event and implemented a feature that attempted to address such challenges by not processing untimely attestations, and thus not requiring expensive state replays​. While it was done for slightly different reasons, the result was the same. Lodestar was able to follow the chain correctly and helped to stabilize the network. This example underscored the importance of client diversity and network resilience against potential forks and replay attacks. These are considered realistic threats, especially in the context of system complexity like in Ethereum's consensus mechanism. ## Ethereum Reading List diff --git a/docs/pages/run/beacon-management/starting-a-node.md b/docs/pages/run/beacon-management/starting-a-node.md index 6838e14304d6..7aa0799b6e83 100644 --- a/docs/pages/run/beacon-management/starting-a-node.md +++ b/docs/pages/run/beacon-management/starting-a-node.md @@ -2,15 +2,13 @@ title: Starting a Node --- -# Beacon management +# Beacon Management -The following instructions are required to setup and run a Lodestar beacon node. +Running a Lodestar node on mainnet or a testnet only requires basic familiarity with the terminal. The following instructions are required to configure and run the Lodestar beacon node. This page assumes you have already setup an Ethereum execution client. -## Connect to mainnet or a public testnet +## Connect to Mainnet or a Public Testnet -Running a Lodestar node on mainnet or a testnet only requires basic familiarity with the terminal. - -Make sure Lodestar is installed in your local environment, following the chosen install method. The following command should return a non error message. +Make sure Lodestar is installed in your local environment, following the chosen [Installation](../getting-started/installation.md) method. The following command should return a non-error message. ```bash ./lodestar --help @@ -18,25 +16,21 @@ Make sure Lodestar is installed in your local environment, following the chosen For a complete list of beacon node CLI commands and options, see the [`beacon` CLI Command](../beacon-management/beacon-cli.md) section. -To select a known testnet or mainnet, use the `--network` flag. `mainnet` is selected by default, and a list of available networks is listed with the `--help` flag. Setting the `--network` flag will conveniently configure the beacon node or validator client for the selected network. For power users, any configuration option should be able to be overridden. +To select a known testnet or mainnet, use the `--network` flag. The option `mainnet` is selected by default, and a list of available networks is listed with the `--help` flag. Setting the `--network` flag will conveniently configure the beacon node for the selected network. For power users, any configuration option should be able to be overridden. -## Configure the Lodestar JWT authentication token +## Configure the Lodestar JWT Authentication Token -Post-Merge Ethereum will require [secure authentication with the Engine API](https://github.com/ethereum/execution-apis/blob/main/src/engine/authentication.md) connection on your chosen Execution node. +Ethereum requires a [secure authentication with the Engine API](https://github.com/ethereum/execution-apis/blob/main/src/engine/authentication.md) for connecting to your chosen execution client on port 8551. -:::info -Post-Merge Ethereum **requires** a secure, authenticated connection to the Execution client on port 8551. We recommend setting this up now to ensure a proper configuration before the Merge. -::: - -### Generate a secret key +### Generate a Secret Key You must generate a secret 32-byte (64 characters) hexadecimal string that will be used to authenticate with an execution node. You can use the following command in most terminals to generate a random secret: `openssl rand -hex 32`. Or you can use an [online generator](https://codebeautify.org/generate-random-hexadecimal-numbers). Save this secret key into a text file and note where you store this file. -### Configure Lodestar to locate the JWT secret +### Configure Lodestar to Locate the JWT Secret When starting up a Lodestar beacon node in any configuration, ensure you add the `--jwtSecret $JWT_SECRET_PATH` flag to point to the saved secret key file. -### Ensure JWT is configured with your execution node +### Configure the Execution Client with the JWT Secret **For Go Ethereum:** Use the `--authrpc.jwtsecret /path/to/jwtsecret.hex` flag to configure the secret. Use their documentation [here](https://geth.ethereum.org/docs/getting-started#start-geth). @@ -53,59 +47,68 @@ Use the `--authrpc.jwtsecret /path/to/jwtsecret.hex` flag to configure the secre **For Reth:** Use the `--authrpc.jwtsecret /path/to/jwtsecret.hex` flag to configure the secret. Use their documentation [here](https://reth.rs/run/mainnet.html?highlight=jwt#running-the-reth-node). -## Run a beacon node +## Run the Beacon Node -To start a Lodestar beacon run the command: +To start the Lodestar beacon, run the command: ```bash ./lodestar beacon --network $NETWORK_NAME --jwtSecret $JWT_SECRET_PATH ``` -This will assume an execution-layer client is available at the default -location of `https://localhost:8545`. +This will assume an execution client is available at the default location of `https://localhost:8545`. -In case execution-layer clients are available at different locations, use `--execution.urls` to specify these locations in the command: +If the execution clients are available at different locations, use the flag `--execution.urls` to specify these locations in the command: ```bash ./lodestar beacon --network $NETWORK_NAME --jwtSecret $JWT_SECRET_PATH --execution.urls $EL_URL1 $EL_URL2 ``` -Immediately you should see confirmation that the node has started +Your initial logs should confirm that the node has started. ```txt -Apr-20 15:12:45.274[] info: Lodestar network=mainnet, version=v1.7.2, commit= -Apr-20 15:12:45.327[] info: Connected to LevelDB database path=/data/mt1/chain-db -Apr-20 15:12:57.747[] info: Initializing beacon from a valid db state slot=6264480, epoch=195765, stateRoot=0x8133cd4d0be59c3e94405f902fe0ad68ffaa5013b525dddb6285b91ad79716f6, isWithinWeakSubjectivityPeriod=true -Apr-20 15:13:18.077[network] info: PeerId 16Uiu2HAmDsGet67va6VCnaW2Tu1Ae2yujiDMnmURMMWNvssER7ZQ, Multiaddrs /ip4/127.0.0.1/tcp/9000/p2p/16Uiu2HAmDsGet67va6VCnaW2Tu1Ae2yujiDMnmURMMWNvssER7ZQ,/ip4/10.244.0.199/tcp/9000/p2p/16Uiu2HAmDsGet67va6VCnaW2Tu1Ae2yujiDMnmURMMWNvssER7ZQ -Apr-20 15:13:18.270[rest] info: Started REST API server address=http://127.0.0.1:9596 -Apr-20 15:13:18.271[] warn: Low peer count peers=0 -Apr-20 15:13:18.280[] info: Searching peers - peers: 0 - slot: 6264964 - head: (slot - 484) 0x7ee6…2a15 - exec-block: syncing(17088043 0x9442…) - finalized: 0xe359…4d7e:195763 -Apr-20 15:13:23.009[chain] info: Validated transition configuration with execution client terminalTotalDifficulty=0xc70d808a128d7380000, terminalBlockHash=0x0000000000000000000000000000000000000000000000000000000000000000, terminalBlockNumber=0x0 -Apr-20 15:13:29.287[] info: Syncing - ? left - 0.00 slots/s - slot: 6264965 - head: (slot - 485) 0x7ee6…2a15 - exec-block: syncing(17088043 0x9442…) - finalized: 0xe359…4d7e:195763 - peers: 1 -Apr-20 15:14:41.003[] info: Syncing - 22 seconds left - 4.92 slots/s - slot: 6264971 - head: (slot - 108) 0xd15f…b605 - exec-block: valid(17088414 0x3dba…) - finalized: 0x70fd…5157:195775 - peers: 4 -Apr-20 15:14:53.001[] info: Syncing - 9 seconds left - 5.00 slots/s - slot: 6264972 - head: (slot - 45) 0x44e4…20a4 - exec-block: valid(17088475 0xca61…) - finalized: 0x9cbd…ba83:195776 - peers: 8 -Apr-20 15:15:01.443[network] info: Subscribed gossip core topics -Apr-20 15:15:01.446[sync] info: Subscribed gossip core topics -Apr-20 15:15:05.000[] info: Synced - slot: 6264973 - head: 0x90ea…c655 - exec-block: valid(17088521 0xca9b…) - finalized: 0x6981…682f:195778 - peers: 6 +Jul-31 13:35:27.967[] info: Lodestar network=mainnet, version=v1.21.0/ff35faa, commit=ff35faae4ad1697b86d708a0367a95a71648ab6e +Jul-31 13:35:28.344[] info: Connected to LevelDB database path=/data/lodestar/chain-db +Jul-31 13:35:49.828[] info: Initializing beacon from a valid db state slot=9633504, epoch=301047, stateRoot=0xfa2845a6877b98555906a1654941c97d9c05bdd41e61cc0870a967dc9030b156, isWithinWeakSubjectivityPeriod=true +Jul-31 13:35:51.955[chain] info: Historical state worker started +Jul-31 13:35:51.969[eth1] info: Eth1 provider urls=http://localhost:8551 +Jul-31 13:35:51.975[execution] info: Execution client urls=http://localhost:8551 +Jul-31 13:35:51.977[] info: External builder url=http://localhost:8661 +Jul-31 13:36:21.128[network] info: running libp2p instance in worker thread +Jul-31 13:36:21.727[network] info: libp2p worker started peer=15Uiu2HAmACcmCEXcgt3zCtJL2rqJZ2Mvdjh6U6fe26HgD2FoNRwW +Jul-31 13:36:27.677[network] info: discv5 worker started peerId=16Uiu2HAmACcmCEXcgt3zCtJL2rqJZ2Mvdjh6U6fe26HgD2FoNRwW, initialENR=enr:-IO4QHGTUd1Zg8LAhUAioOz_ySTKoJLIOa6zltSP_AvvhTFVYw6M6YB35IxsiKxQG7nUgCpUB5SIsNxMntCNlTK9sMEBgmlkgnY0iXNlY3AyNTZrMaEC24cdmzuGnWqSwF-8Hw2gbkAZDzMWW3LsHJfp_kDhy-GDdGNwgiMog3VkcIIeWH, bindAddr4=/ip4/0.0.0.0/udp/9000 +Jul-31 13:36:28.134[network] info: PeerId 16Uiu2HAmACcmCEXcgt3zCtJL2rqJZ2Mvdjh6U6fe26HgD2FoNRwW, Multiaddrs /ip4/0.0.0.0/tcp/9000 +Jul-31 13:36:28.137[metrics] info: Started metrics HTTP server address=http://127.0.0.1:8008 +Jul-31 13:36:28.256[rest] info: Started REST API server address=http://0.0.0.0:9596 +Jul-31 13:36:28.257[] info: Searching peers - peers: 0 - slot: 9634080 - head: (slot -576) 0x9d88…d02a - exec-block: syncing(20426302 0xcec4…) - finalized: 0x7feb…c130:301045 +Jul-31 13:36:36.461[execution] info: Execution client is synced oldState=ONLINE, newState=SYNCED +Jul-31 13:36:53.019[] info: Syncing - 3.7 minutes left - 2.32 slots/s - slot: 9634082 - head: (slot -515) 0x792f…f8aa - exec-block: valid(20426365 0x58b1…) - finalized: 0x9d88…d02a:301047 - peers: 11 +Jul-31 13:38:53.168[] info: Syncing - 11 seconds left - 4.01 slots/s - slot: 9634092 - head: (slot -44) 0x7491…f63e - exec-block: valid(20426841 0xd4b2…) - finalized: 0x1e00…6e6b:301062 - peers: 59 +Jul-31 13:38:58.051[network] info: Subscribed gossip core topics +Jul-31 13:38:58.132[sync] info: Subscribed gossip core topics +Jul-31 13:39:05.001[] info: Synced - slot: 9634093 - head: 0x35de…1f0e - exec-block: valid(20426886 0x10ff…) - finalized: 0x88f8…5375:301063 - peers: 70 +Jul-31 13:39:17.000[] info: Synced - slot: 9634094 - head: 0x7844…3b3e - exec-block: valid(20426887 0x67d1…) - finalized: 0x88f8…5375:301063 - peers: 69 +Jul-31 13:39:29.000[] info: Synced - slot: 9634095 - head: 0x5516…ba12 - exec-block: valid(20426888 0x4ceb…) - finalized: 0x88f8…5375:301063 - peers: 74 ``` :::info -If your node is stuck with `Searching for peers` review your network configuration to make sure your ports are open. +If your node is stuck with `Searching peers`, review your network configuration to make sure your ports are open and forwarded to your host machine. ::: By default, Lodestar stores all configuration and chain data at the path `$XDG_DATA_HOME/lodestar/$NETWORK_NAME`. -A young testnet should take a few hours to sync. If you see multiple or consistent errors in the logs, please open a [Github issue](https://github.com/ChainSafe/lodestar/issues/new/choose) or reach out to us in [Discord](https://discord.gg/yjyvFRP). Just by reporting anomalies you are helping accelerate the progress of Ethereum Consensus, thanks for contributing! +A young testnet should take a few hours to sync. If you see multiple or consistent errors in the logs, please open a [Github issue](https://github.com/ChainSafe/lodestar/issues/new/choose) or reach out to us in [Discord](https://discord.gg/yjyvFRP). By reporting anomalies, you are helping to accelerate the progress of Ethereum consensus and we thank you for contributing! :::warning -It is dangerous to expose your Beacon APIs publicly as there is no default authentication mechanism provided. Ensure your beacon node host is not exposing ports 8545 or 9596 outside of your internal network. +It is dangerous to expose your Beacon or Execution APIs publicly as there is no default authentication mechanism provided. Ensure your beacon node host is not exposing ports 8545 or 9596 outside of your internal network. ::: ### Checkpoint Sync -If you are starting your node from a blank db, like starting from genesis, or from the last saved state in db and the network is now far ahead, your node will be susceptible to "long range attacks." Ethereum's solution to this is via something called weak subjectivity. [Read Vitalik's illuminating post explaining weak subjectivity.](https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/). +If you are starting your node from a blank database, or from a last saved database state that is too old (outside of the weak subjectivity period), your node will be susceptible to "long range attacks." Ethereum's solution to this attack is via something called weak subjectivity. [Read Vitalik's illuminating post explaining weak subjectivity.](https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/). -If you have a synced beacon node available (e.g., your friend's node or an infrastructure provider) and a trusted checkpoint you can rely on, you can start off your beacon node in under a minute! And at the same time kicking the "long range attack" in its butt! +If you have a synced beacon node available (e.g., your friend's node or a trusted infrastructure provider) to serve a trusted checkpoint you can rely on, you can start syncing your beacon node from that available checkpoint with the flag `--checkpointSyncUrl` and passing in the URL of the checkpoint provider. This will allow your beacon node to sync within minutes rather than several days. + +The Ethereum community has maintained a set of [public beacon chain checkpoints](https://eth-clients.github.io/checkpoint-sync-endpoints/) that serve these sync endpoints to the larger community. You can correlate the state root and the block root with more than one provider to verify the checkpoints being served follow the same canonical chain. Just supply these **extra arguments** to your beacon node command: @@ -113,28 +116,27 @@ Just supply these **extra arguments** to your beacon node command: --checkpointSyncUrl [--wssCheckpoint ] ``` -In case you really trust `checkpointSyncUrl` then you may skip providing `wssCheckpoint`, which will then result into your beacon node syncing and starting off the recently finalized state from the trusted URL. +In case you really trust the `--checkpointSyncUrl` provider, then you may skip providing `--wssCheckpoint`, which will then result into your beacon node syncing and starting off the recently finalized state from the trusted URL. :::warning -Please use this option very carefully (and at your own risk), a malicious server URL can put you on the wrong chain with a danger of you losing your funds by social engineering. -If possible, validate your `wssCheckpoint` from multiple places (e.g. different client distributions) or from other trusted sources. This will highly reduce the risk of starting off on a malicious chain. -This list of [public endpoints](https://eth-clients.github.io/checkpoint-sync-endpoints/) maintained by the Ethereum community may be used for reference. +Please be aware that a malicious checkpoint sync server URL can put you on the wrong chain with a danger of you losing your funds by social engineering. +If possible, validate your `wssCheckpoint` state from multiple places (e.g. different client distributions) or from other trusted sources. This will highly reduce the risk of starting off on a malicious chain. This list of [public endpoints](https://eth-clients.github.io/checkpoint-sync-endpoints/) maintained by the Ethereum community may be used for reference. ::: -**Taking too long to sync?** +#### Still Taking Long to Sync? -After your node has been offline for a while, it might be the case that it takes a long time to sync even though a `checkpointSyncUrl` is specified. -This is due to the fact that the last db state is still within the weak subjectivity period (~15 days on mainnet) which causes the node -to sync from the db state instead of the checkpoint state. +After your node has been offline for a while, it might be the case that it takes a long time to sync even though a `--checkpointSyncUrl` is specified. +This is due to the fact that the last database state is still within the weak subjectivity period (~15 days on mainnet) which causes the node +to sync from the database state instead of the checkpoint state. -It is possible to force syncing from checkpoint state by supplying the `--forceCheckpointSync` flag. This option is only recommended if it is absolutely +It is possible to force syncing from a checkpoint state by supplying the `--forceCheckpointSync` flag. This option is only recommended if it is absolutely necessary for the node to be synced right away to fulfill its duties as there is an inherent risk each time the state is obtained from an external source. -### Guide to the sync logs +### Sync Log Guide -Lodestar beacon sync log aims to provide information of utmost importance about your node and yet be succinct at the same time. You may see the sync logs in the following format: +The Lodestar beacon sync log aims to provide information of utmost importance about the state of your node and be succinct at the same time. You may see the sync logs in the following format: -`[Sync status] - [ Slot info ] - [Head info] - [Exec block info] - [Finalized info] - [Peers info]` +`[Sync status] - [ Slot info ] - [Head info] - [Execution block info] - [Finalized info] - [Peers info]` See the following example of different kinds of sync log: @@ -171,16 +173,20 @@ Apr-20 15:16:05.000[] info: Synced - slot: 6264978 - head: 0xc9f Apr-20 15:16:17.017[] info: Synced - slot: 6264979 - head: 0xde91…d4cb - exec-block: valid(17088527 0xa488…) - finalized: 0x6981…682f:195778 - peers: 7 ``` -1. Sync status: Takes three values : `Synced` or `Syncing` (along with sync speed info) or `Searching` if node is is still looking for viable peers from where it can download blocks. +1. Sync status: This status takes three values: + +- `Synced`: The node is currently synced +- `Syncing` The node is currently in the syncing process +- `Searching`: The node is is still looking for viable peers from where it can download blocks -2. Slot (clock) info: What is the current ongoing slot as per the chain genesis +2. Slot (clock) info: The current ongoing slot as per the chain genesis -3. Head info: It specifies where the local chain head hash is. In case its far behind the Slot (clock) then it independently shows the head slot else it show how far behind from the Slot it is if difference < 1000. +3. Head info: Specifies where the local beacon chain head hash is. In case it's far behind the Slot (clock), then it independently shows the head slot. Else, it will show how far behind the node is from the Slot (if the difference is < 1000) -4. Execution block info: It provides the execution information about the head whether its confirmed `valid` or execution layer is still `syncing` to it, as well as its number and a short hash to easy identification. +4. Execution block info: Provides the information about the execution block head, whether its confirmed `valid` or still `syncing` to it. In parenthesis, it shows the current execution block number and a short hash for easy identification -5. Finalized info: What is the current local `finalized` checkpoint in the format of `[checkpoint root]:[checkpoint epoch]`, for e.g.: `0xd7ba…8386:189636` +5. Finalized info: Shows the current local `finalized` checkpoint in the format of `[checkpoint root]:[checkpoint epoch]`. For example: `0xd7ba…8386:189636` shows a checkpoint root of `0xd7ba…8386` in epoch `189636` -6. Peer info: Current total number of outbound or inbound peers, for e.g.: `peers: 27` +6. Peer info: Current total number of outbound and inbound peers -For more insight into how a Lodestar beacon node is functioning, you may setup lodestar metrics and use the prepared Grafana dashboards that are found in the repository. Check out our section on [Prometheus and Grafana](../logging-and-metrics/prometheus-grafana.md) for more details. +For more insight into how a Lodestar beacon node is functioning, you may setup Lodestar metrics with the `--metrics` flag and use the prepared Grafana dashboards that are found in the repository. Check out our section on [Prometheus and Grafana](../logging-and-metrics/prometheus-grafana.md) for more details. diff --git a/docs/pages/run/getting-started/installation.md b/docs/pages/run/getting-started/installation.md index 52cb2770725b..40c4865f7726 100644 --- a/docs/pages/run/getting-started/installation.md +++ b/docs/pages/run/getting-started/installation.md @@ -1,8 +1,8 @@ -# Installation +# Install Options ## Binaries -Binaries can be downloaded from [the release page](https://github.com/ChainSafe/lodestar/releases/latest) under the `Assets` section. +Binaries can be downloaded from the Lodestar [release page](https://github.com/ChainSafe/lodestar/releases/latest) under the `Assets` section. ## Docker Installation @@ -100,5 +100,5 @@ pip3 install setuptools --force-reinstall --user ## Install from NPM [not recommended] :::danger -For mainnet (production) usage, we only recommend installing with docker due to [NPM supply chain attacks](https://hackaday.com/2021/10/22/supply-chain-attack-npm-library-used-by-facebook-and-others-was-compromised/). Until a [safer installation method has been found](https://github.com/ChainSafe/lodestar/issues/3596), do not use this install method except for experimental purposes only. +For mainnet (production) usage, we only recommend installing with Docker, using binaries or building from source due to [NPM supply chain attacks](https://hackaday.com/2021/10/22/supply-chain-attack-npm-library-used-by-facebook-and-others-was-compromised/). Until a [safer installation method has been found](https://github.com/ChainSafe/lodestar/issues/3596), do not use this install method except for experimental purposes only. ::: diff --git a/docs/pages/run/getting-started/quick-start-custom-guide.md b/docs/pages/run/getting-started/quick-start-custom-guide.md new file mode 100644 index 000000000000..8afc11c00ebf --- /dev/null +++ b/docs/pages/run/getting-started/quick-start-custom-guide.md @@ -0,0 +1,728 @@ +# Quick Start Custom Setup Guide + +This is a step-by-step guide to utilize [@ChainSafe/lodestar-quickstart](https://github.com/ChainSafe/lodestar-quickstart) to setup a Ubuntu-based full Ethereum node using a local execution client and ChainSafe's Lodestar consensus client via Docker (the recommended method to use Lodestar for production environments). This is an adaptation of [Somer Esat's guides](https://someresat.medium.com/) for the Ethereum staking community. + +This guide will provide instructions which include running a local execution node. This guide uses Lodestar's `stable` release branch and supports **Holesky** testnet setups and **Mainnet**. + +:::info +This guide specifically focuses on using Lodestar's Quickstart scripts which allows for near instant setup with the following technologies: + +- [Ubuntu v22.04 (LTS) x64 server](https://releases.ubuntu.com/22.04/) +- Ethereum Execution (eth1) clients: + - [Erigon](https://github.com/ledgerwatch/erigon/releases) | [Github](https://github.com/ledgerwatch/erigon) + - [Go-Ethereum (Geth)](https://geth.ethereum.org/) | [Github](https://github.com/ethereum/go-ethereum/releases/) + - [Hyperledger Besu](https://www.hyperledger.org/) | [Github](https://github.com/hyperledger/besu) + - [Nethermind](https://nethermind.io/) | [Github](https://github.com/NethermindEth/nethermind) + - [Rust](https://reth.rs) | [Github](https://github.com/paradigmxyz/reth) +- [ChainSafe's Lodestar Ethereum Consensus Client](https://lodestar.chainsafe.io/) | [Github](https://github.com/ChainSafe/lodestar) +- [Docker Engine](https://docs.docker.com/engine/) + ::: + +:::danger +This guide **_does not_** assist with securing your server such as secure SSH logins or enabling firewalls. Ensure you have limited access to your server and blocked unused ports with guides such as [CoinCashew's Security Best Practices for your ETH staking validator node](https://www.coincashew.com/coins/overview-eth/guide-or-how-to-setup-a-validator-on-eth2-mainnet/part-i-installation/guide-or-security-best-practices-for-a-eth2-validator-beaconchain-node) before continuing with this guide. +::: + +:::warning +This guide is for informational purposes only and does not constitute professional advice. The author does not guarantee accuracy of the information in this article and the author is not responsible for any damages or losses incurred by following this article. A full disclaimer can be found at the bottom of this page — please read before continuing. +::: + +## Support + +For technical support please reach out to: + +- The Lodestar team actively develops and collaborates on the [ChainSafe Discord Server](https://discord.gg/642wB3XC3Q) under **_#:star2:-lodestar-general_** channel. +- Please subscribe to our Discord server announcements on the [ChainSafe Discord Server](https://discord.gg/642wB3XC3Q) under **_#lodestar-announcements_** channel. + +## Prerequisites + +This guide assumes knowledge of Ethereum (ETH), Docker, staking and Linux. + +You require the following before getting started: + +- [Ubuntu Server v22.04 (LTS) amd64](https://releases.ubuntu.com/22.04/) or newer, installed and running on a local machine or in the cloud. _A locally running machine is encouraged for greater decentralization — if the cloud provider goes down then all nodes hosted with that provider go down._ + +- 32 ETH to run a solo validator with Lodestar. If running on testnet, contact us in our [ChainSafe Discord Server](https://discord.gg/642wB3XC3Q) for testnet Ether. + +## Testnet to Mainnet + +If moving from a testnet setup to a mainnet setup it is strongly recommended that you start on a fresh (newly installed) server instance. This guide has not been tested for migration scenarios and does not guarantee success if you are using an existing instance with previously installed testnet software. + +## Hardware Requirements + +| | Minimum | Recommended | +| --------- | -------------------------------------- | -------------------------------------- | +| Processor | Intel Core i3–9100 or AMD Ryzen 5 3450 | Intel Core i7–9700 or AMD Ryzen 7 4700 | +| Memory | 8 GB RAM | 16 GB RAM | +| Storage | 130 GB available space SSD | 200 GB available space SSD | +| Internet | Reliable broadband with 10mbps upload | Reliable broadband with >10mbps upload | + +:::info +Check your available disk space. Even you have a large SSD there are cases where Ubuntu is reporting only 100GB free. If this applies to you then take a look at [**_Appendix A — Expanding the Logical Volume._**](#appendix-a---expanding-the-logical-volume) +::: + +--- + +## Setup Machine & Repository + +### Install Docker Engine & Docker Compose + +We must install Docker Engine to run the images on your local machine. + +#### Add Docker's GPG keyrings + +Run each line one at a time. + +```bash= +sudo apt-get update +sudo apt-get install ca-certificates curl gnupg +sudo install -m 0755 -d /etc/apt/keyrings +curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg +sudo chmod a+r /etc/apt/keyrings/docker.gpg +``` + +#### Add the repository to Apt sources + +Copy and paste the entire command below. + +```bash +echo \ + "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ + $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null +``` + +#### Update Ubuntu + +Ensure all updates to your Ubuntu Server are complete. + +```bash= +sudo apt-get update +sudo apt-get upgrade -y +``` + +Hit `Enter` if required to restart services. + +#### Install Docker Engine + +```bash +sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin +``` + +#### Test Docker + +```bash +sudo docker run hello-world +``` + +If you see the message `Hello from Docker! +This message shows that your installation appears to be working correctly.`, you can move on to the next step. + +#### Clone lodestar-quickstart repository + +Clone the [lodestar-quickstart](https://github.com/ChainSafe/lodestar-quickstart) from Github into your local server. + +```bash +cd ~ && git clone https://github.com/ChainSafe/lodestar-quickstart.git +``` + +## Configure Lodestar Quick Scripts + +### Navigate to the root directory + +The script and required files are located within the `lodestar-quickstart` folder. + +``` +cd lodestar-quickstart +``` + +### Create your own JWT Secret + +We will generate a JWT secret that is shared by the Execution client and Lodestar in order to have a required secure connection for the `Engine API` on port `8551`. + +``` +openssl rand -hex 32 | tr -d "\n" > "jwtsecret" +``` + +Confirm that your JWT token created. + +``` +cat jwtsecret ; echo +``` + +Your terminal should display the secret. Copy the token for the next step. Be careful to only copy the 64 characters corresponding to the secret and nothing else. + +:::danger +:rotating_light: **WARNING:** Do not share this secret as it protects your authenticated port 8551. +::: + +### Input your JWT Secret into the `import-args.sh` script + +Edit the `import-args.sh` file. + +```sh +nano import-args.sh +``` + +Replace the 64 characters after `0x` with your token. + +If you are not running validators, press `CTRL` + `x` then `y` then `Enter` to save and exit. Proceed to Configuring your Network. + +### Configure feeRecipient + +:::warning +If you are running validators, Ethereum requires validators to set a **Fee Recipient** which allows you to receive priority fees and MEV rewards when proposing blocks. If you do not set this address, your rewards will be sent to the [burn address by default](https://etherscan.io/address/0x0000000000000000000000000000000000000000). +::: + +Configure your validator client's feeRecipient address by changing the `FEE_RECIPIENT` line. Ensure you specify an Ethereum address you control. + +An example of a fee recipient set with the address `0xB7576e9d314Df41EC5506494293Afb1bd5D3f65d`, you would change the configuration to: + +``` +FEE_RECIPIENT="0xB7576e9d314Df41EC5506494293Afb1bd5D3f65d" +``` + +If you would like to run [MEV-Boost](https://boost.flashbots.net) with your validators, proceed to the next section. + +If you do not want to run MEV-Boost, press `CTRL` + `x` then `y` then `Enter` to save and exit. Proceed to [Configuring your Network](#configuring-your-network). + +### Set minimum bid for MEV-Boost validators + +:::info +(Optional): If you are running validators and would like to use MEV-Boost, follow this section. Otherwise, skip this section. +::: + +Validators running MEV-Boost maximize their staking reward by selling blockspace to an open market of builders. MEV-Boost v1.4+ allows you to set a minimum bid threshold to only use an externally built block if it meets or exceeds this parameter. + +The `min-bid` parameter is denominated in ETH. For example, if you want to set your threshold to 0.03 ETH, set your configuration to `MIN_BUILDERBID=0.03` + +When complete, press `CTRL` + `x` then `y` then `Enter` to save and exit. + +### Configuring your Network + +When using the quick scripts, each supported network has a `.vars` file to define the parameters required for configuring the clients to the specified network. + +To view the available files, use the command: + +``` +ls *.vars +``` + +### Select your Network + +Each network has specifics variables that you may want to setup for use. We will use `Holesky` to demonstrate connecting to a public testnet. + +Open the `holesky.vars` file. + +```bash +nano holesky.vars +``` + +### Configure MEV-boost relays + +:::info +(Optional): If you have validators you intend to use for MEV-boost, you can input the relays you want to connect here. Otherwise, skip this section. +::: + +You can list multiple relays simply by pasting the relay URL as a variable in this file. + +```shell= +RELAY_A=https://0xRelayPubKey@relay.com +RELAY_B=https://0xRelayPubKey@relay2.com +``` + +Make sure to identify the ones you want to use by editing the line: + +```shell= +RELAYS="$RELAY_A,$RELAY_B" +``` + +### Configure Lodestar version + +The lodestar-quickstart scripts currently defaults to using our `stable` release branch. To use our nightly `unstable` release instead, replace `LODESTAR_IMAGE=chainsafe/lodestar:latest` with `LODESTAR_IMAGE=chainsafe/lodestar:next` in the `import-images.sh` file. + +You may also choose to use a specific version release of Lodestar. To select a specific version, replace the image with `LODESTAR_IMAGE=chainsafe/lodestar:v1.x.x` + +:::warning +:warning: We do not recommend using the `unstable` branch or `@chainsafe/lodestar:next` docker versions of Lodestar for production related tasks. +::: + +### Modify your weak subjectivity (checkpoint sync) provider + +:::note +(Optional): We use ChainSafe's Lodestar checkpoints by default. You may choose to point your trusted checkpoint at another source or verify the checkpoints with other providers. If you would rather sync from genesis (not recommended), you can skip this step. +::: + +Weak subjectivity (checkpoint sync) allows your beacon node to sync within minutes by utilizing a trusted checkpoint from a trusted provider. + +**We highly recommend using this feature** so you do not need to wait days to sync from genesis and will mitigate your susceptibility to [long-range attacks](https://blog.ethereum.org/2014/11/25/proof-stake-learned-love-weak-subjectivity/). + +Minimize your risk of syncing a malicious chain from a malicious checkpoint by verifying the trusted checkpoint from multiple sources. + +1. View the community maintained list of [Beacon Chain checkpoint sync endpoints](https://eth-clients.github.io/checkpoint-sync-endpoints/) +2. Verify multiple endpoint links and ensure the latest finalized and latest justified block roots are the same +3. Choose one of those endpoint URLs +4. Replace the `--checkpointSyncUrl` address with your chosen provider. + +:::info +**NOTE**: Ensure you use checkpoint URLs from the list above corresponding to the network you are trying to sync or you **will** receive errors. +::: + +When complete, press `CTRL` + `x` then `y` then `Enter` to save and exit. + +## Modify other client parameters (For advanced users) + +:::info +(Optional): We have already set fixed parameters for a seamless setup. If you are looking to customize the default parameters of the clients you are using, follow this section. Otherwise, skip this section. +::: + +Fixed parameters for clients can be modified under the `fixed.vars` configuration file. + +Under the selected client, modify or add the custom arguments on their corresponding line. + +:::note +The following are links to client documentation for CLI commands: + +- [**Lodestar CLI Commands**](https://chainsafe.github.io/lodestar/reference/cli/) +- [**Nethermind CLI Commands**](https://docs.nethermind.io/fundamentals/configuration#command-line-options) +- [**Besu CLI Commands**](https://besu.hyperledger.org/en/stable/Reference/CLI/CLI-Syntax/) +- [**Go Ethereum CLI commands**](https://geth.ethereum.org/docs/interface/command-line-options) +- [**Erigon CLI commands**](https://github.com/ledgerwatch/erigon#beacon-chain) +- [**Reth CLI commands**](https://reth.rs/cli/reth.html) + ::: + +Once complete, press `CTRL` + `x` then `y` then `Enter` to save and exit. + +--- + +## Setup Validators + +:::info +Optional: Skip this entire section if you do not intend to run validators. +::: + +### Create validator keystore password + +Make sure you are in your main quickstart directory. Create the `pass.txt` file containing your validator's decryption password for use. + +``` +cd ~/lodestar-quickstart +``` + +``` +nano pass.txt +``` + +Enter the password for your validators. + +:::info +Once the validator container is running, you can delete this file from your server. Note that every time you restart this container, you will need this password to decrypt your keystore.json files. +::: + +Once complete, press `CTRL` + `x` then `y` then `Enter` to save and exit. + +### Option 1: Setup validators with keystores + +If you want to setup validators with your `keystores.json` files follow this section. Otherwise, skip this step. + +#### Copy/Move keystores to `lodestar-quickstart/keystores` directory + +Your `keystore.json` file(s) generated from the [`staking-deposit-cli`](https://github.com/ethereum/staking-deposit-cli) or similar generator for validator keys will be placed in the `lodestar-quickstart/keystores` directory using the `cp` command to copy or `mv` command to move the files. + +``` +mkdir keystores +``` + +:::info +You may choose to use your own method (e.g. SFTP) for copying/uploading keys to your server. This is only a guide. +::: + +The format of the command to use is below: + +``` +cp +``` + +An example usage of this command is: + +``` +cp /home/user/validator_keys/keystore-x.json ~/lodestar-quickstart/keystores +``` + +Ensure your `keystore.json` files are in the `lodestar-quickstart/keystores` directory using `ls` command. + +``` +ls -lsah ~/lodestar-quickstart/keystores/ +``` + +You should see the keystore files within the directory. + +:::info +Ensure the `/keystores` directory only has the `keystore-m_xxxxx.json` files and nothing else. If you copied in the `deposit_data-xxxxx.json` files, you can remove them by using the `sudo rm ` command. + +Example: + +``` +sudo rm deposit_data-1552658472.json +``` + +::: + +Continue to the [**Startup Quickstart Script**](#startup-quickstart-script) section. + +### Option 2: Setup multiple validator sets with keystores encrypted under different passwords + +Optional: If you want to setup validators with your `keystores.json` files but they are not encrypted with the same password, follow this section. Otherwise, skip this step. + +This option will allow you to run multiple validator clients corresponding to each validator keystore set encrypted with the same password. Therefore, we will setup `validatorset1` with one decryption password and `validatorset2` with another decryption password. You can repeat these steps to create subsequent validator sets with different keystore decryption passwords. + +#### Create validator keystore set directory + +Ensure you are in the `lodestar-quickstart` directory and create a folder for your first validator keystore set. + +``` +cd ~/lodestar-quickstart +``` + +Make the new directory for set one. + +``` +mkdir validatorset1 +``` + +Navigate into the directory. + +``` +cd validatorset1 +``` + +#### Create validator keystore password + +Create the `pass.txt` file containing your validator's decryption password for use. + +``` +nano pass.txt +``` + +Enter the password for your validators. + +:::info +Once the validator container is running, you can delete this file from your server. Note that every time you restart this container, you will need this password to decrypt your keystore.json files. +::: + +Once complete, press `CTRL` + `x` then `y` then `Enter` to save and exit. + +#### Copy/Move keystores to `lodestar-quickstart/validatorset1/keystores` directory + +Your `keystore.json` file(s) generated from the [`staking-deposit-cli`](https://github.com/ethereum/staking-deposit-cli) or similar generator for validator keys will be placed in the `lodestar-quickstart/validatorset1/keystores` directory using the `sudo cp` command to copy or `sudo mv` command to move the files. + +``` +mkdir keystores +``` + +The format of the command to use is below: + +``` +cp +``` + +An example usage of this command is: + +``` +cp /home/user/validator_keys/keystore-x.json ~/lodestar-quickstart/validatorset1/keystores +``` + +Ensure your `keystore.json` files are in the `lodestar-quickstart/validatorset1/keystores` directory using `ls` command. + +``` +ls -lsah ~/lodestar-quickstart/validatorset1/keystores/ +``` + +You should see the keystore files within the directory. + +:::info +Ensure the `/keystores` directory only has the `keystore-m_xxxxx.json` files and nothing else. If you copied in the `deposit_data-xxxxx.json` files, you can remove them by using the `sudo rm ` command. + +Example: + +``` +sudo rm deposit_data-1552658472.json +``` + +::: + +Repeat the same steps above for `validatorset2` and any subsequent sets of validators you require. When complete you should have a similar looking directory tree such as the one below: + +Then, continue to the [**Startup Quickstart Script**](#startup-quickstart-script) section. Pay particular attention to startup script example five (5) and (6). + +### Option 3: Setup validators with mnemonic + +:::warning +**TESTNET USE ONLY:** Do not use this method unless you're validating on a testnet. Your mnemonic will be stored in plaintext on your server, which is unsafe. +::: + +Optional: If you want to setup validators with your mnemonic. Otherwise, skip this step. + +#### Setup Mnemonic + +Select the `.vars` file corresponding to the network you want to run. For Holesky, select `holesky.vars`. Open the file with the `nano` text editor and edit the configuration: + +``` +nano holesky.vars +``` + +We will modify the `LODESTAR_VALIDATOR_MNEMONIC_ARGS=`. Specifically, the mnemonic located after the `--fromMnemonic` flag. + +- Replace the default mnemonic with your mnemonic. Ensure it is between the quotations + +- Indicate which indexes of the mnemonic you wish Lodestar to run. Specify a specific index number `--mnemonicIndexes 0` or a range of numbers `--mnemonicIndexes 0..5` + +:::info +If you created your mnemonic with one key, it is likely located in index 0. If you've added to it, the generated keys are likely the subsequent indexes. + +Therefore, if you generated one key, it is likely in index 0, so you would use `--mnemonicIndexes 0`. If you generated five keys, it is likely in index 0 to 4, so you would use `--mnemonicIndexes 0..4` +::: + +Once complete, press `CTRL` + `x` then `y` then `Enter` to save and exit. + +Continue to the [**Startup Quickstart Script**](#startup-quickstart-script) section. + +--- + +## Startup Quickstart Script + +Ensure you are in the `~/lodestar-quickstart folder. + +``` +cd ~/lodestar-quickstart +``` + +The following are **_example commands_** as a template for initiating the quickstart script: + +1. Startup a Sepolia beacon node with no validators and Go Ethereum (Geth) execution client with terminals attached: + +``` +./setup.sh --dataDir sepolia-data --elClient geth --network sepolia --dockerWithSudo --withTerminal "gnome-terminal --disable-factory --" +``` + +2. Startup Mainnet beacon node with no validators and Nethermind execution client detached from containers (Recommended only when you've verified the setup has initiated properly with terminals attached): + +``` +./setup.sh --dataDir mainnet-data --elClient nethermind --network mainnet --dockerWithSudo --detached +``` + +3. Startup Holesky beacon node with validator client (using mnemonic in /keystores) and Erigon execution client detached from containers: + +``` +./setup.sh --dataDir holesky-data --elClient erigon --network holesky --dockerWithSudo --detached --withValidatorMnemonic ~/lodestar-quickstart/ +``` + +4. Startup Mainnet beacon node with validator client (using keystores) with MEV-Boost and Hyperledger Besu execution client detached from containers: + +``` +./setup.sh --dataDir mainnet-data --elClient besu --network mainnet --dockerWithSudo --detached --withValidatorKeystore ~/lodestar-quickstart/ --withMevBoost +``` + +5. Startup Holesky beacon node with validator client set one (using keystores) and execution client Geth detached from containers: + +``` +./setup.sh --dataDir holesky-data --elClient geth --network holesky --dockerWithSudo --detached --withValidatorKeystore ~/lodestar-quickstart/validatorset1 +``` + +:::warning +You can only start up one set of validator keystores per validator client on the same command. Use the below command (#6) to startup another validator client for another set of validator keys. +::: + +6. Startup validator client only with validator client set two (using keystores) and execution client Geth detached from containers: + +``` +./setup.sh --dataDir holesky-data --elClient geth --network holesky --dockerWithSudo --detached --withValidatorKeystore ~/lodestar-quickstart/validatorset2 --justVC +``` + +:::info +The script will standardize naming your containers so running the `setup.sh` twice, will not create two instances of the same containers. The script will standardize naming your containers so running the `./setup.sh ` a second time, will not create two instances of the same containers. +::: + +Configure the above commands with what you intend to run using the Quickstart Script Help table below. + +## Quickstart Script Help + +| Command | Required/Optional | Description | +| ------------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `dataDir` | Required | File location (volume) of the configuration & data for setup. This directory should be non-existent for the first run. If the directory exists, it will skip fetching the configuration, assuming it has been done previously. You can also clean individual directors of CL/EL between the re-runs. | +| `elClient` | Required | The selected EL client you want to run with Lodestar. Options are `nethermind`, `besu`, `erigon` or `geth`. | +| `network` | Required | The network/chain you want to load, reads the corresponding `.vars` (for e.g. `holesky.vars`) network configuration , like images, or urls for EL/CL to interact. Example: Default for Holesky is `--network holesky` using `holesky.vars`. | +| `dockerWithSudo` | Optional | Provide this argument if your Docker needs a `sudo` prefix. | +| `--withTerminal` | Optional\* | Provide the terminal command prefix for CL and EL processes to run in your favourite terminal. You may use an alias or a terminal launching script as long as it waits for the command it runs till ends and then closes. If not provided, it will launch the docker processes in in-terminal mode. | +| `--detached` | Optional\* | By default the script will wait for processes and use user input (ctrl +c) to end the processes, however you can pass this option to skip this behavior and just return, for e.g. in case you just want to leave it running. | +| `--withValidatorKeystore` | Optional\*\* | Launch a validator client using `LODESTAR_VALIDATOR_MNEMONIC_ARGS` (`--withValidatorMnemonic`) or using a folder (`--withValidatorKeystore --justVC` connecting to same beacon node. | +| `--withValidatorMnemonic` | Optional\*\* | Launch a validator client using mnemonic method.(`LODESTAR_VALIDATOR_MNEMONIC_ARGS`) as set in the network vars file. | +| `--withMevBoost` | Optional | Launch a MEV-Boost container to interface with multiple relays picked for the corresponding network vars file. When paired with `--justCL` or `--justVC` this only activates the builder arguments in the beacon/validator and use the builder url set in MEVBOOST_URL variable in fixed.vars | +| `--justEL` | Optional | Launch only the Execution Layer client. | +| `--justCL` | Optional | Launch only the Lodestar beacon node. | +| `--justVC` | Optional | Launch only the Lodestar validator. | +| `--skipImagePull` | Optional | Launch with only the local Docker images. Do not update them on this run. | + +:::note +`*` : Only one of the two options should be provided. +`**` : Only one of the two options should be provided. +::: + +### Check Containers + +You can check the status and get the name of your containers by using the `docker ps` command: + +``` +sudo docker ps +``` + +The containers should not constantly restart. If they restart, likely a misconfiguration occurred. + +### Check Container Logs + +You can check the status of what your container is logging to diagnose a problem or follow along the status of your container output. + +Check the logs by using the `docker logs` command: + +``` +sudo docker logs +``` + +Follow along the logs by adding the `-f` flag: + +``` +sudo docker logs -f +``` + +Limit the fetched logs by indicating the latest container out puts by number of lines using the `-n ` flag. For the last 10 lines: + +``` +sudo docker logs -n 10 +``` + +### Check beacon node is progressing + +Your beacon node should initialize and you should see something similar to: + +``` +Jul-31 13:35:27.967[] info: Lodestar network=mainnet, version=v1.21.0/ff35faa, commit=ff35faae4ad1697b86d708a0367a95a71648ab6e +Jul-31 13:35:28.344[] info: Connected to LevelDB database path=/data/lodestar/chain-db +Jul-31 13:35:49.828[] info: Initializing beacon from a valid db state slot=9633504, epoch=301047, stateRoot=0xfa2845a6877b98555906a1654941c97d9c05bdd41e61cc0870a967dc9030b156, isWithinWeakSubjectivityPeriod=true +Jul-31 13:35:51.955[chain] info: Historical state worker started +Jul-31 13:35:51.969[eth1] info: Eth1 provider urls=http://localhost:8551 +Jul-31 13:35:51.975[execution] info: Execution client urls=http://localhost:8551 +Jul-31 13:35:51.977[] info: External builder url=http://localhost:8661 +Jul-31 13:36:21.128[network] info: running libp2p instance in worker thread +Jul-31 13:36:21.727[network] info: libp2p worker started peer=15Uiu2HAmACcmCEXcgt3zCtJL2rqJZ2Mvdjh6U6fe26HgD2FoNRwW +Jul-31 13:36:27.677[network] info: discv5 worker started peerId=16Uiu2HAmACcmCEXcgt3zCtJL2rqJZ2Mvdjh6U6fe26HgD2FoNRwW, initialENR=enr:-IO4QHGTUd1Zg8LAhUAioOz_ySTKoJLIOa6zltSP_AvvhTFVYw6M6YB35IxsiKxQG7nUgCpUB5SIsNxMntCNlTK9sMEBgmlkgnY0iXNlY3AyNTZrMaEC24cdmzuGnWqSwF-8Hw2gbkAZDzMWW3LsHJfp_kDhy-GDdGNwgiMog3VkcIIeWH, bindAddr4=/ip4/0.0.0.0/udp/9000 +Jul-31 13:36:28.134[network] info: PeerId 16Uiu2HAmACcmCEXcgt3zCtJL2rqJZ2Mvdjh6U6fe26HgD2FoNRwW, Multiaddrs /ip4/0.0.0.0/tcp/9000 +Jul-31 13:36:28.137[metrics] info: Started metrics HTTP server address=http://127.0.0.1:8008 +Jul-31 13:36:28.256[rest] info: Started REST API server address=http://0.0.0.0:9596 +Jul-31 13:36:28.257[] info: Searching peers - peers: 0 - slot: 9634080 - head: (slot -576) 0x9d88…d02a - exec-block: syncing(20426302 0xcec4…) - finalized: 0x7feb…c130:301045 +Jul-31 13:36:36.461[execution] info: Execution client is synced oldState=ONLINE, newState=SYNCED +Jul-31 13:36:53.019[] info: Syncing - 3.7 minutes left - 2.32 slots/s - slot: 9634082 - head: (slot -515) 0x792f…f8aa - exec-block: valid(20426365 0x58b1…) - finalized: 0x9d88…d02a:301047 - peers: 11 +Jul-31 13:38:53.168[] info: Syncing - 11 seconds left - 4.01 slots/s - slot: 9634092 - head: (slot -44) 0x7491…f63e - exec-block: valid(20426841 0xd4b2…) - finalized: 0x1e00…6e6b:301062 - peers: 59 +Jul-31 13:38:58.051[network] info: Subscribed gossip core topics +Jul-31 13:38:58.132[sync] info: Subscribed gossip core topics +Jul-31 13:39:05.001[] info: Synced - slot: 9634093 - head: 0x35de…1f0e - exec-block: valid(20426886 0x10ff…) - finalized: 0x88f8…5375:301063 - peers: 70 +Jul-31 13:39:17.000[] info: Synced - slot: 9634094 - head: 0x7844…3b3e - exec-block: valid(20426887 0x67d1…) - finalized: 0x88f8…5375:301063 - peers: 69 +Jul-31 13:39:29.000[] info: Synced - slot: 9634095 - head: 0x5516…ba12 - exec-block: valid(20426888 0x4ceb…) - finalized: 0x88f8…5375:301063 - peers: 74 +``` + +### Check validators are detected and decrypted + +> OPTIONAL: If you are running validators, you can check the validator client logs to ensure the validator keys exist, has been detected and decrypted. + +Here is an example command if you are running validators on Goerli with the lodestar-quickstart script: + +``` +sudo docker logs goerli-validator +``` + +You should see something similar to: + +``` +Mar-01 03:06:35.048[] info: Lodestar network=holesky, version=v1.16.0/6ad9740, commit=6ad9740a085574306cf46c7642e749d6ec9a4264 +Mar-01 03:06:35.050[] info: Connecting to LevelDB database path=/keystoresDir/validator-db-holesky +Mar-01 03:06:35.697[] info: 100% of keystores imported. current=2 total=2 rate=1318.68keys/m +Mar-01 03:06:35.698[] info: 2 local keystores +Mar-01 03:06:35.698[] info: 0xa6fcfca12e1db6c7341d82327010cd57224dc239d1c5e4fb18286cc32edb877d813c5af1c870d474aef7b3ff7ab927ea +Mar-01 03:06:35.698[] info: 0x8f868e53bbe1451bcf6d42c9ab6d292cbd7fbfa09c59b6b99c1dd6a4977e2e7b4b752c328784ca2788dd6f63ffcbdb7e +Mar-01 03:06:35.732[] info: Beacon node urls=http://127.0.0.1:9596 +Mar-01 03:09:23.813[] info: Genesis fetched from the beacon node +Mar-01 03:09:23.816[] info: Verified connected beacon node and validator have same the config +Mar-01 03:09:23.818[] info: Verified connected beacon node and validator have the same genesisValidatorRoot +Mar-01 03:09:23.818[] info: Initializing validator useProduceBlockV3=deneb+, broadcastValidation=gossip, defaultBuilderSelection=executiononly, suggestedFeeRecipient=0xeeef273281fB83F56182eE960aA4bAfe7fE075DE, strictFeeRecipientCheck=false +Mar-01 03:09:23.830[] info: Validator seen on beacon chain validatorIndex=1234567, pubKey=0xa6fcfca12e1db6c7341d82327010cd57224dc239d1c5e4fb18286cc32edb877d813c5af1c870d474aef7b3ff7ab927ea +Mar-01 03:09:23.830[] info: Validator seen on beacon chain validatorIndex=1234568, pubKey=0x8f868e53bbe1451bcf6d42c9ab6d292cbd7fbfa09c59b6b99c1dd6a4977e2e7b4b752c328784ca2788dd6f63ffcbdb7e +Mar-01 03:09:23.830[] info: Validator statuses active=2, total=2 +Mar-01 03:15:50.191[] info: Published attestations slot=1113379, count=1 +Mar-01 03:16:02.728[] info: Published attestations slot=1113380, count=1 +``` + +:::info +It is normal to see `Error on getProposerDuties` in your validator logs as your beacon node and execution node sync up. Give it time. +::: + +## Stop Containers + +You can stop the running containers by using the `docker stop` command and apply it to more than one container if necessary. + +``` +sudo docker stop +``` + +Ensure to remove the container if you don't plan to restart it with the same parameters. + +``` +sudo docker rm +``` + +--- + +# Appendix + +## Appendix A - Expanding the Logical Volume + +There are cases where Ubuntu is provisioning only 200GB of a larger SSD causing users to run out of disk space when syncing their Eth1 node. The error message is similar to: + +`Fatal: Failed to register the Ethereum service: write /var/lib/goethereum/geth/chaindata/383234.ldb: no space left on device` + +To address this issue, assuming you have a SSD that is larger than 200GB, expand the space allocation for the LVM by following these steps: + +``` +sudo lvdisplay <-- Check your logical volume size +sudo lvm +lvextend -l +100%FREE /dev/ubuntu-vg/ubuntu-lv +lvextend -l +100%FREE -r /dev/ubuntu-vg/ubuntu-lv +exit +sudo resize2fs /dev/ubuntu-vg/ubuntu-lv +df -h <-- Check results +``` + +That should resize your disk to the maximum available space. + +If you need support, please check with the [ChainSafe Discord](https://discord.gg/642wB3XC3Q) under the #:star2:-lodestar-general channel. + +## Appendix B - Update client images + +To update client images, you just need to stop all the containers, remove them and restart the lodestar-quickstart script to automatically check for new images. + +You can stop the running containers by using the `docker stop` command with the container names. + +``` +sudo docker stop +``` + +Remove the containers by using the `docker rm` command. + +``` +sudo docker rm +``` + +Restart your containers using your [Startup Quickstart Script](#startup-quickstart-script) command. + +--- + +## Full Disclaimer + +This article (the guide) is for informational purposes only and does not constitute professional advice. The author does not warrant or guarantee the accuracy, integrity, quality, completeness, currency, or validity of any information in this article. All information herein is provided “as is” without warranty of any kind and is subject to change at any time without notice. The author disclaims all express, implied, and statutory warranties of any kind, including warranties as to accuracy, timeliness, completeness, or fitness of the information in this article for any particular purpose. The author is not responsible for any direct, indirect, incidental, consequential or any other damages arising out of or in connection with the use of this article or in reliance on the information available on this article. This includes any personal injury, business interruption, loss of use, lost data, lost profits, or any other pecuniary loss, whether in an action of contract, negligence, or other misuse, even if the author has been informed of the possibility. diff --git a/docs/pages/run/getting-started/quick-start.md b/docs/pages/run/getting-started/quick-start.md index a28de8014b6a..e1a77face695 100644 --- a/docs/pages/run/getting-started/quick-start.md +++ b/docs/pages/run/getting-started/quick-start.md @@ -1,26 +1,20 @@ --- -title: Quick Start +title: Lodestar Quick Start Scripts --- -## Lodestar Quickstart +In order to make things easier and quicker for all types of users to bootstrap the Lodestar consensus client with a variety of execution clients, we have come up with [Lodestar Quickstart](https://github.com/ChainSafe/lodestar-quickstart) Docker scripts! -In order to make things easy for users to onboard and try the Ethereum **Proof of Stake** we have come up with [Lodestar quick start](https://github.com/ChainSafe/lodestar-quickstart) scripts! +- ✅ Zero configuration +- ✅ Single command startup +- ✅ All testnets supported along with mainnet +- ✅ All mainstream execution clients integrated -✅ Zero Configuration -✅ All testnets supported along with `mainnet` -✅ All mainstream Execution Clients integrated - -With just single command you can run lodestar with various execution engines, switch them up to see the Optimistic sync work its magic and eventually brings lodestar and the execution engine in sync - -### Customizations - -You can adapt them to your production setups with ease! Here is a simple guide for you to follow along: +### Support -👉 [Lodestar Quick Setup Guide](https://hackmd.io/@philknows/rJegZyH9q) +We actively maintain and update the configurations of running Lodestar with the most commonly used execution clients for various test/production networks so there is minimal configuration required for a standard setup. -### Support +If you have questions about these scripts, documentation or repository, feel free to talk to us on our [ChainSafe Discord](https://discord.gg/yjyvFRP) or [open an issue](https://github.com/ChainSafe/lodestar-quickstart/issues/new) and a member of the team or our community will be happy to assist you. -We actively maintain and update the configurations of running lodestar with the top of the line execution engines for various PoS networks so you have the minimum possible figuring out to do. +### Customizations (Optional) -In case you are facing any issues with the quick start guide, do reach us out on lodestar discord! -Happy to help! 🙏🙏🙏 +You can further adapt our Quickstart Docker scripts to your node setups with ease! Use our [Quickstart Custom Setup Guide](./quick-start-custom-guide.md)! diff --git a/docs/pages/run/logging-and-metrics/client-monitoring.md b/docs/pages/run/logging-and-metrics/client-monitoring.md index dc707f2b31d0..3d9ede1d8464 100644 --- a/docs/pages/run/logging-and-metrics/client-monitoring.md +++ b/docs/pages/run/logging-and-metrics/client-monitoring.md @@ -1,4 +1,4 @@ -# Client monitoring +# Client Monitoring Lodestar has the ability to send client stats to a remote service for collection. At the moment, the main service offering remote monitoring is [beaconcha.in](https://beaconcha.in/). diff --git a/docs/pages/run/logging-and-metrics/dashboards.md b/docs/pages/run/logging-and-metrics/dashboards.md deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/docs/pages/run/logging-and-metrics/log-management.md b/docs/pages/run/logging-and-metrics/log-management.md deleted file mode 100644 index a0ee1d5fec07..000000000000 --- a/docs/pages/run/logging-and-metrics/log-management.md +++ /dev/null @@ -1,3 +0,0 @@ -# Log Management - -Check back soon for more information!! diff --git a/docs/pages/run/logging-and-metrics/metrics-management.md b/docs/pages/run/logging-and-metrics/metrics-management.md deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/docs/pages/run/logging-and-metrics/prometheus-grafana.md b/docs/pages/run/logging-and-metrics/prometheus-grafana.md index f49a44f37209..c36c52a72a0c 100644 --- a/docs/pages/run/logging-and-metrics/prometheus-grafana.md +++ b/docs/pages/run/logging-and-metrics/prometheus-grafana.md @@ -1,8 +1,12 @@ -# Prometheus and Grafana +# Prometheus and Grafana Setup Prometheus is an open-source monitoring system with efficient time series database and a modern alerting approach. Together with Grafana it's the recommended way to make sure that your node and validator(s) are performing correctly. -## Prometheus +## Localized Docker Metrics Script + +The Lodestar team has setup a script which will copy the latest dashboards compiled by our team for development purposes. By utilizing the script located in `/docker/docker-compose.local_dev.sh`, you can instantly setup the latest dockerized metrics alongside your local beacon node. + +## Prometheus Setup To start, download Prometheus from https://prometheus.io/download/. Unzip the downloaded .zip file and run Prometheus from its installed location with the lodestar `prometheus.yml` passed in as the configuration file @@ -23,7 +27,7 @@ lodestar --metrics=true --metrics.port=8008 Navigate to http://localhost:9090/ in your browser to verify that Prometheus is monitoring Lodestar -## Grafana +## Grafana Setup Download and install Grafana from its official repository https://grafana.com/docs/grafana/latest/installation/debian/ diff --git a/docs/pages/run/validator-management/proposer-config.md b/docs/pages/run/validator-management/proposer-config.md new file mode 100644 index 000000000000..444789a9f8b0 --- /dev/null +++ b/docs/pages/run/validator-management/proposer-config.md @@ -0,0 +1,48 @@ +# Proposer Configuration + +:::warning +This is an alpha feature. The feature and its format are subject to change. +::: + +With Lodestar's validator client, you can assign specific metadata for each proposer/public key using a proposer configuration file written in YAML file. This will allow you to set specific graffiti, fee recipients and builder settings per validator key. + +### Example proposer_config.yaml + +```yaml +proposer_config: + "0xa99a76ed7796f7be22d5b7e85deeb7c5677e88e511e0b337618f8c4eb61349b4bf2d153f649f7b53359fe8b94a38e44c": + graffiti: "graffiti" + strict_fee_recipient_check: false + fee_recipient: "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + builder: + gas_limit: "30000000" + selection: "executionalways" + boost_factor: "0" + "0xa4855c83d868f772a579133d9f23818008417b743e8447e235d8eb78b1d8f8a9f63f98c551beb7de254400f89592314d": + fee_recipient: "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb" + builder: + gas_limit: "3000000" + selection: "maxprofit" + boost_factor: "100" +default_config: + graffiti: "default graffiti" + strict_fee_recipient_check: true + fee_recipient: "0xcccccccccccccccccccccccccccccccccccccccc" + builder: + gas_limit: "30000000" + selection: "default" + boost_factor: "90" +``` + +### Enable Proposer Configuration + +After you have configured your proposer configuration YAML file, you can start Lodestar with an additional CLI flag option pointing to the file: `--proposerSettingsFile /path/to/proposer_config.yaml`. + +:::info +The proposer configuration can also be retrieved via the keymanager API endpoint: + +``` +GET /eth/v0/validator/{pubkey}/proposer_config +``` + +::: diff --git a/docs/pages/run/validator-management/vc-configuration.md b/docs/pages/run/validator-management/vc-configuration.md index ce6db52b3a66..a51791eff494 100644 --- a/docs/pages/run/validator-management/vc-configuration.md +++ b/docs/pages/run/validator-management/vc-configuration.md @@ -74,6 +74,10 @@ Configure your validator client's fee recipient address by using the [`--suggest You may choose to use the [`--strictFeeRecipientCheck`](./validator-cli.md#--strictfeerecipientcheck) flag to enable a strict check of the fee recipient address with the one returned by the beacon node for added reassurance. +:::note +If you would like to set unique proposer metadata (e.g. fee recipient address) for each validator you are running, see the [Proposer Configuration](./proposer-config.md) feature. This feature is also available via the keymanager API. +::: + ### Configure your builder selection and/or builder boost factor If you are running a beacon node with connected builder relays, you may use these validator configurations to signal which block (builder vs. local execution) the beacon node should produce. diff --git a/docs/pages/supporting-libraries/libraries.md b/docs/pages/supporting-libraries/libraries.md index e76ccc2f9ec7..58796e56cd15 100644 --- a/docs/pages/supporting-libraries/libraries.md +++ b/docs/pages/supporting-libraries/libraries.md @@ -1,13 +1,13 @@ # Lodestar libraries -The Lodestar project is divided into Typescript packages that can be used independently of the CLI. These packages span the breadth of the Ethereum Consensus layer, and are perfect for Typescript developers looking to build around Ethereum. +The Lodestar project is divided into TypeScript packages that can be used independently of the CLI. These packages span the breadth of the Ethereum Consensus layer, and are perfect for TypeScript developers looking to build around Ethereum. ## Monorepo libraries Several useful Ethereum consensus libraries are developed as part of the [Lodestar monorepo](https://github.com/ChainSafe/lodestar) and may be useful when used individually. - [`params`](https://github.com/ChainSafe/lodestar/tree/unstable/packages/params) - Ethereum consensus constants and fork names -- [`types`](https://github.com/ChainSafe/lodestar/tree/unstable/packages/types) - Ethereum consensus types, Typescript interfaces and SSZ type objects +- [`types`](https://github.com/ChainSafe/lodestar/tree/unstable/packages/types) - Ethereum consensus types, TypeScript interfaces and SSZ type objects - [`config`](https://github.com/ChainSafe/lodestar/tree/unstable/packages/config) - Ethereum consensus run-time network configuration - [`api`](https://github.com/ChainSafe/lodestar/tree/unstable/packages/api) - Ethereum consensus REST API client - [`flare`](https://github.com/ChainSafe/lodestar/tree/unstable/packages/flare) - Beacon chain multi-purpose and debugging tool diff --git a/docs/pages/trouble-shooting.md b/docs/pages/trouble-shooting.md deleted file mode 100644 index 144aeb90ce20..000000000000 --- a/docs/pages/trouble-shooting.md +++ /dev/null @@ -1 +0,0 @@ -# Trouble Shooting diff --git a/docs/sidebars.ts b/docs/sidebars.ts index d9a4839a90c4..1b6eb1ac055d 100644 --- a/docs/sidebars.ts +++ b/docs/sidebars.ts @@ -10,11 +10,18 @@ const sidebars: SidebarsConfig = { label: "Run A Node", collapsed: false, items: [ - "run/getting-started/quick-start", "run/getting-started/installation", { type: "category", - label: "Beacon node", + label: "Quick Start", + items: [ + "run/getting-started/quick-start", + "run/getting-started/quick-start-custom-guide", + ], + }, + { + type: "category", + label: "Beacon Node", items: [ "run/beacon-management/starting-a-node", "run/beacon-management/beacon-cli", @@ -31,17 +38,23 @@ const sidebars: SidebarsConfig = { "run/validator-management/vc-configuration", "run/validator-management/validator-cli", "run/validator-management/external-signer", + "run/validator-management/proposer-config", ], }, { type: "category", label: "Logging and Metrics", - items: ["run/logging-and-metrics/prometheus-grafana", "run/logging-and-metrics/client-monitoring"], + items: [ + "run/logging-and-metrics/prometheus-grafana", + "run/logging-and-metrics/client-monitoring", + ], }, { type: "category", label: "Discv5 Bootnode", - items: ["run/bootnode/bootnode-cli"], + items: [ + "run/bootnode/bootnode-cli", + ], }, ], }, diff --git a/packages/light-client/README.md b/packages/light-client/README.md index 0323e8fc4326..3cab0c12f13b 100644 --- a/packages/light-client/README.md +++ b/packages/light-client/README.md @@ -31,7 +31,7 @@ You can find more information about the light-client protocol in the [specificat ## Getting started -- Follow the [installation guide](https://chainsafe.github.io/lodestar/getting-started/installation) or [Docker install](https://chainsafe.github.io/lodestar/getting-started/installation/#docker-installation) to install Lodestar. +- Follow the [installation guide](https://chainsafe.github.io/lodestar/run/getting-started/installation) to install Lodestar. - Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/advanced-topics/setting-up-a-testnet). ## Light-Client CLI Example diff --git a/packages/prover/README.md b/packages/prover/README.md index ce1f33dd8efb..477da5140c4b 100644 --- a/packages/prover/README.md +++ b/packages/prover/README.md @@ -152,7 +152,7 @@ You will need to go over the [specification](https://github.com/ethereum/beacon- ## Getting started -- Follow the [installation guide](https://chainsafe.github.io/lodestar/getting-started/installation) to install Lodestar. +- Follow the [installation guide](https://chainsafe.github.io/lodestar/run/getting-started/installation) to install Lodestar. - Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/advanced-topics/setting-up-a-testnet). ## Contributors From 8922d55a236db211a56fe6a74d8b19d315da8b08 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Nov 2024 20:59:19 +0100 Subject: [PATCH 15/43] chore(deps): bump cross-spawn from 7.0.3 to 7.0.6 in /docs (#7268) Bumps [cross-spawn](https://github.com/moxystudio/node-cross-spawn) from 7.0.3 to 7.0.6. - [Changelog](https://github.com/moxystudio/node-cross-spawn/blob/master/CHANGELOG.md) - [Commits](https://github.com/moxystudio/node-cross-spawn/compare/v7.0.3...v7.0.6) --- updated-dependencies: - dependency-name: cross-spawn dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/yarn.lock b/docs/yarn.lock index 046829e3530b..80cd3a321a5a 100644 --- a/docs/yarn.lock +++ b/docs/yarn.lock @@ -3684,9 +3684,9 @@ cosmiconfig@^8.3.5: path-type "^4.0.0" cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" From 64eb0153dd97b7525cb61625b102a6a521869c05 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Mon, 2 Dec 2024 17:11:28 +0100 Subject: [PATCH 16/43] feat: add error log to notifier if execution client auth failed (#7239) * feat: add error log to notifier if execution client auth failed * Update packages/beacon-node/src/node/notifier.ts --------- Co-authored-by: NC <17676176+ensi321@users.noreply.github.com> --- packages/beacon-node/src/node/notifier.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/beacon-node/src/node/notifier.ts b/packages/beacon-node/src/node/notifier.ts index c0d9354197ae..cd316516259b 100644 --- a/packages/beacon-node/src/node/notifier.ts +++ b/packages/beacon-node/src/node/notifier.ts @@ -54,12 +54,12 @@ export async function runNodeNotifier(modules: NodeNotifierModules): Promise= config.BELLATRIX_FORK_EPOCH && - computeStartSlotAtEpoch(clockEpoch) === clockSlot && - chain.executionEngine.state === ExecutionEngineState.OFFLINE - ) { - logger.warn("Execution client is offline"); + if (clockEpoch >= config.BELLATRIX_FORK_EPOCH && computeStartSlotAtEpoch(clockEpoch) === clockSlot) { + if (chain.executionEngine.state === ExecutionEngineState.OFFLINE) { + logger.warn("Execution client is offline"); + } else if (chain.executionEngine.state === ExecutionEngineState.AUTH_FAILED) { + logger.error("Execution client authentication failed. Verify if the JWT secret matches on both clients"); + } } const headInfo = chain.forkChoice.getHead(); From 84e069930156b1fabb2e2cd9261d58fcd6eff6b1 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 3 Dec 2024 15:44:29 +0100 Subject: [PATCH 17/43] docs: display rcConfig flag on CLI reference page (#7270) * docs: display rcConfig flag on CLI reference page * Update word list --- .wordlist.txt | 2 ++ packages/cli/src/options/globalOptions.ts | 8 +++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/.wordlist.txt b/.wordlist.txt index 2c28fc784061..5070aafcf9a5 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -110,6 +110,7 @@ Vitalik Vitest Wagyu api +args async backfill beaconcha @@ -244,3 +245,4 @@ xRelayPubKey xcode yaml yamux +yml diff --git a/packages/cli/src/options/globalOptions.ts b/packages/cli/src/options/globalOptions.ts index 58ae396b67d1..6a6998dc76cc 100644 --- a/packages/cli/src/options/globalOptions.ts +++ b/packages/cli/src/options/globalOptions.ts @@ -10,6 +10,7 @@ type GlobalSingleArgs = { paramsFile?: string; preset: string; presetFile?: string; + rcConfig?: string; }; export const defaultNetwork: NetworkName = "mainnet"; @@ -44,11 +45,16 @@ const globalSingleOptions: CliCommandOptions = { description: "Preset configuration file to override the active preset with custom values", type: "string", }, + + rcConfig: { + description: "RC file to supplement command line args, accepted formats: .yml, .yaml, .json", + type: "string", + }, }; export const rcConfigOption: [string, string, (configPath: string) => Record] = [ "rcConfig", - "RC file to supplement command line args, accepted formats: .yml, .yaml, .json", + globalSingleOptions.rcConfig.description as string, (configPath: string): Record => readFile(configPath, ["json", "yml", "yaml"]), ]; From 376fe2a3c37823c93cdab70fda11c859280e72a2 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 3 Dec 2024 18:04:26 +0100 Subject: [PATCH 18/43] chore: remove prettier as default formatter for all file types (#7275) --- .vscode/settings.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fbc0552fe61c..d36535917835 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,5 @@ { "window.title": "${activeEditorShort}${separator}${rootName}${separator}${profileName}${separator}[${activeRepositoryBranchName}]", - "editor.defaultFormatter": "esbenp.prettier-vscode", // For `sysoev.vscode-open-in-github` extension "openInGitHub.defaultBranch": "unstable", "editor.formatOnSave": true, @@ -18,5 +17,11 @@ }, "[jsonc]": { "editor.defaultFormatter": "biomejs.biome" - } + }, + "[yaml]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[markdown]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, } From 37c42875390f8fd8a0c2d872e160709eb5e8858f Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 3 Dec 2024 21:22:02 +0100 Subject: [PATCH 19/43] chore: unhide flags relevant for devnets / testing (#7271) --- packages/cli/src/cmds/beacon/options.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/cli/src/cmds/beacon/options.ts b/packages/cli/src/cmds/beacon/options.ts index a623ca99512c..7f2cdddfc4ff 100644 --- a/packages/cli/src/cmds/beacon/options.ts +++ b/packages/cli/src/cmds/beacon/options.ts @@ -33,7 +33,6 @@ export const beaconExtraOptions: CliCommandOptions = { }, genesisStateFile: { - hidden: true, description: "Path or URL to download a genesis state file in ssz-encoded format", type: "string", }, @@ -80,7 +79,6 @@ export const beaconExtraOptions: CliCommandOptions = { ignoreWeakSubjectivityCheck: { description: "Ignore the checkpoint sync state failing the weak subjectivity check. This is relevant in testnets where the weak subjectivity period is too small for even few epochs of non finalization causing last finalized to be out of range. This flag is not recommended for mainnet use.", - hidden: true, type: "boolean", group: "weak subjectivity", }, @@ -120,7 +118,6 @@ export const beaconExtraOptions: CliCommandOptions = { }, persistNetworkIdentity: { - hidden: true, description: "Whether to reuse the same peer-id across restarts", type: "boolean", }, From dbe2188a37135b2ec34b0322459cdd012395dcc9 Mon Sep 17 00:00:00 2001 From: Matthew Keil Date: Wed, 4 Dec 2024 01:50:26 -0500 Subject: [PATCH 20/43] feat: debug too many shuffling promises (#7251) * feat: add asyncShufflingCalculation to StateTransitionOpts * feat: add asyncShufflingCalculation to all regen / processSlots consumers * fix: default to false for async shuffling and remove unnecessary props * fix: remove unnecessary flags from stateTransition * feat: implement conditional build of shuffling for prepareNextSlot * fix: spec test bug where shufflingCache is present from BeaconChain constructor * feat: sync build next shuffling if not queued async * fix: use getSync to pull next shuffling correctly * docs: add comment to prepareNextSlot * refactor: rename StateCloneOpts to StateRegenerationOpts * feat: pass asyncShufflingCalculation through to afterProcessEpoch and refactor conditional to run purely sync * docs: add issue number to comment * chore: lint --- .../beacon-node/src/chain/prepareNextSlot.ts | 7 ++- .../beacon-node/src/chain/regen/interface.ts | 18 ++++-- .../beacon-node/src/chain/regen/queued.ts | 18 ++++-- packages/beacon-node/src/chain/regen/regen.ts | 14 ++--- .../chain/stateCache/blockStateCacheImpl.ts | 4 +- .../chain/stateCache/fifoBlockStateCache.ts | 4 +- .../stateCache/inMemoryCheckpointsCache.ts | 10 ++-- .../stateCache/persistentCheckpointsCache.ts | 12 ++-- .../beacon-node/src/chain/stateCache/types.ts | 12 ++-- .../state-transition/src/cache/epochCache.ts | 59 ++++++++++--------- .../src/cache/epochTransitionCache.ts | 17 +++++- 11 files changed, 105 insertions(+), 70 deletions(-) diff --git a/packages/beacon-node/src/chain/prepareNextSlot.ts b/packages/beacon-node/src/chain/prepareNextSlot.ts index 40cadb3774c7..f78c1842bd78 100644 --- a/packages/beacon-node/src/chain/prepareNextSlot.ts +++ b/packages/beacon-node/src/chain/prepareNextSlot.ts @@ -114,7 +114,12 @@ export class PrepareNextSlotScheduler { // the slot 0 of next epoch will likely use this Previous Root Checkpoint state for state transition so we transfer cache here // the resulting state with cache will be cached in Checkpoint State Cache which is used for the upcoming block processing // for other slots dontTransferCached=true because we don't run state transition on this state - {dontTransferCache: !isEpochTransition}, + // + // Shuffling calculation will be done asynchronously when passing asyncShufflingCalculation=true. Shuffling will be queued in + // beforeProcessEpoch and should theoretically be ready immediately after the synchronous epoch transition finished and the + // event loop is free. In long periods of non-finality too many forks will cause the shufflingCache to throw an error for + // too many queued shufflings so only run async during normal epoch transition. See issue ChainSafe/lodestar#7244 + {dontTransferCache: !isEpochTransition, asyncShufflingCalculation: true}, RegenCaller.precomputeEpoch ); diff --git a/packages/beacon-node/src/chain/regen/interface.ts b/packages/beacon-node/src/chain/regen/interface.ts index b70fbc059875..b9a4e38b5b68 100644 --- a/packages/beacon-node/src/chain/regen/interface.ts +++ b/packages/beacon-node/src/chain/regen/interface.ts @@ -28,8 +28,12 @@ export enum RegenFnName { getCheckpointState = "getCheckpointState", } -export type StateCloneOpts = { +export type StateRegenerationOpts = { dontTransferCache: boolean; + /** + * Do not queue shuffling calculation async. Forces sync JIT calculation in afterProcessEpoch if not passed as `true` + */ + asyncShufflingCalculation?: boolean; }; export interface IStateRegenerator extends IStateRegeneratorInternal { @@ -56,7 +60,11 @@ export interface IStateRegeneratorInternal { * Return a valid pre-state for a beacon block * This will always return a state in the latest viable epoch */ - getPreState(block: BeaconBlock, opts: StateCloneOpts, rCaller: RegenCaller): Promise; + getPreState( + block: BeaconBlock, + opts: StateRegenerationOpts, + rCaller: RegenCaller + ): Promise; /** * Return a valid checkpoint state @@ -64,7 +72,7 @@ export interface IStateRegeneratorInternal { */ getCheckpointState( cp: phase0.Checkpoint, - opts: StateCloneOpts, + opts: StateRegenerationOpts, rCaller: RegenCaller ): Promise; @@ -74,12 +82,12 @@ export interface IStateRegeneratorInternal { getBlockSlotState( blockRoot: RootHex, slot: Slot, - opts: StateCloneOpts, + opts: StateRegenerationOpts, rCaller: RegenCaller ): Promise; /** * Return the exact state with `stateRoot` */ - getState(stateRoot: RootHex, rCaller: RegenCaller, opts?: StateCloneOpts): Promise; + getState(stateRoot: RootHex, rCaller: RegenCaller, opts?: StateRegenerationOpts): Promise; } diff --git a/packages/beacon-node/src/chain/regen/queued.ts b/packages/beacon-node/src/chain/regen/queued.ts index b5084d593356..9069b384fd58 100644 --- a/packages/beacon-node/src/chain/regen/queued.ts +++ b/packages/beacon-node/src/chain/regen/queued.ts @@ -8,7 +8,13 @@ import {JobItemQueue} from "../../util/queue/index.js"; import {CheckpointHex, toCheckpointHex} from "../stateCache/index.js"; import {BlockStateCache, CheckpointStateCache} from "../stateCache/types.js"; import {RegenError, RegenErrorCode} from "./errors.js"; -import {IStateRegenerator, IStateRegeneratorInternal, RegenCaller, RegenFnName, StateCloneOpts} from "./interface.js"; +import { + IStateRegenerator, + IStateRegeneratorInternal, + RegenCaller, + RegenFnName, + StateRegenerationOpts, +} from "./interface.js"; import {RegenModules, StateRegenerator} from "./regen.js"; const REGEN_QUEUE_MAX_LEN = 256; @@ -86,7 +92,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { */ getPreStateSync( block: BeaconBlock, - opts: StateCloneOpts = {dontTransferCache: true} + opts: StateRegenerationOpts = {dontTransferCache: true} ): CachedBeaconStateAllForks | null { const parentRoot = toRootHex(block.parentRoot); const parentBlock = this.forkChoice.getBlockHex(parentRoot); @@ -212,7 +218,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { */ async getPreState( block: BeaconBlock, - opts: StateCloneOpts, + opts: StateRegenerationOpts, rCaller: RegenCaller ): Promise { this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getPreState}); @@ -231,7 +237,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { async getCheckpointState( cp: phase0.Checkpoint, - opts: StateCloneOpts, + opts: StateRegenerationOpts, rCaller: RegenCaller ): Promise { this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getCheckpointState}); @@ -256,7 +262,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { async getBlockSlotState( blockRoot: RootHex, slot: Slot, - opts: StateCloneOpts, + opts: StateRegenerationOpts, rCaller: RegenCaller ): Promise { this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getBlockSlotState}); @@ -268,7 +274,7 @@ export class QueuedStateRegenerator implements IStateRegenerator { async getState( stateRoot: RootHex, rCaller: RegenCaller, - opts: StateCloneOpts = {dontTransferCache: true} + opts: StateRegenerationOpts = {dontTransferCache: true} ): Promise { this.metrics?.regenFnCallTotal.inc({caller: rCaller, entrypoint: RegenFnName.getState}); diff --git a/packages/beacon-node/src/chain/regen/regen.ts b/packages/beacon-node/src/chain/regen/regen.ts index 073556d8162f..06d1cee71332 100644 --- a/packages/beacon-node/src/chain/regen/regen.ts +++ b/packages/beacon-node/src/chain/regen/regen.ts @@ -20,7 +20,7 @@ import {getCheckpointFromState} from "../blocks/utils/checkpoint.js"; import {ChainEvent, ChainEventEmitter} from "../emitter.js"; import {BlockStateCache, CheckpointStateCache} from "../stateCache/types.js"; import {RegenError, RegenErrorCode} from "./errors.js"; -import {IStateRegeneratorInternal, RegenCaller, StateCloneOpts} from "./interface.js"; +import {IStateRegeneratorInternal, RegenCaller, StateRegenerationOpts} from "./interface.js"; export type RegenModules = { db: IBeaconDb; @@ -51,7 +51,7 @@ export class StateRegenerator implements IStateRegeneratorInternal { */ async getPreState( block: BeaconBlock, - opts: StateCloneOpts, + opts: StateRegenerationOpts, regenCaller: RegenCaller ): Promise { const parentBlock = this.modules.forkChoice.getBlock(block.parentRoot); @@ -84,7 +84,7 @@ export class StateRegenerator implements IStateRegeneratorInternal { */ async getCheckpointState( cp: phase0.Checkpoint, - opts: StateCloneOpts, + opts: StateRegenerationOpts, regenCaller: RegenCaller, allowDiskReload = false ): Promise { @@ -99,7 +99,7 @@ export class StateRegenerator implements IStateRegeneratorInternal { async getBlockSlotState( blockRoot: RootHex, slot: Slot, - opts: StateCloneOpts, + opts: StateRegenerationOpts, regenCaller: RegenCaller, allowDiskReload = false ): Promise { @@ -146,7 +146,7 @@ export class StateRegenerator implements IStateRegeneratorInternal { async getState( stateRoot: RootHex, caller: RegenCaller, - opts?: StateCloneOpts, + opts?: StateRegenerationOpts, // internal option, don't want to expose to external caller allowDiskReload = false ): Promise { @@ -322,7 +322,7 @@ async function processSlotsByCheckpoint( preState: CachedBeaconStateAllForks, slot: Slot, regenCaller: RegenCaller, - opts: StateCloneOpts + opts: StateRegenerationOpts ): Promise { let postState = await processSlotsToNearestCheckpoint(modules, preState, slot, regenCaller, opts); if (postState.slot < slot) { @@ -343,7 +343,7 @@ async function processSlotsToNearestCheckpoint( preState: CachedBeaconStateAllForks, slot: Slot, regenCaller: RegenCaller, - opts: StateCloneOpts + opts: StateRegenerationOpts ): Promise { const preSlot = preState.slot; const postSlot = slot; diff --git a/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts b/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts index f57c9a411923..7d87675b7bbc 100644 --- a/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts +++ b/packages/beacon-node/src/chain/stateCache/blockStateCacheImpl.ts @@ -3,7 +3,7 @@ import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {Epoch, RootHex} from "@lodestar/types"; import {toRootHex} from "@lodestar/utils"; import {Metrics} from "../../metrics/index.js"; -import {StateCloneOpts} from "../regen/interface.js"; +import {StateRegenerationOpts} from "../regen/interface.js"; import {MapTracker} from "./mapMetrics.js"; import {BlockStateCache} from "./types.js"; @@ -39,7 +39,7 @@ export class BlockStateCacheImpl implements BlockStateCache { } } - get(rootHex: RootHex, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { + get(rootHex: RootHex, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null { this.metrics?.lookups.inc(); const item = this.head?.stateRoot === rootHex ? this.head.state : this.cache.get(rootHex); if (!item) { diff --git a/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts b/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts index eec1fce5d6c2..a119efe66887 100644 --- a/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts +++ b/packages/beacon-node/src/chain/stateCache/fifoBlockStateCache.ts @@ -4,7 +4,7 @@ import {RootHex} from "@lodestar/types"; import {toRootHex} from "@lodestar/utils"; import {Metrics} from "../../metrics/index.js"; import {LinkedList} from "../../util/array.js"; -import {StateCloneOpts} from "../regen/interface.js"; +import {StateRegenerationOpts} from "../regen/interface.js"; import {MapTracker} from "./mapMetrics.js"; import {BlockStateCache} from "./types.js"; @@ -93,7 +93,7 @@ export class FIFOBlockStateCache implements BlockStateCache { /** * Get a state from this cache given a state root hex. */ - get(rootHex: RootHex, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { + get(rootHex: RootHex, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null { this.metrics?.lookups.inc(); const item = this.cache.get(rootHex); if (!item) { diff --git a/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts index 4caa6779f697..81562d669365 100644 --- a/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/inMemoryCheckpointsCache.ts @@ -3,7 +3,7 @@ import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {Epoch, RootHex, phase0} from "@lodestar/types"; import {MapDef, toRootHex} from "@lodestar/utils"; import {Metrics} from "../../metrics/index.js"; -import {StateCloneOpts} from "../regen/interface.js"; +import {StateRegenerationOpts} from "../regen/interface.js"; import {MapTracker} from "./mapMetrics.js"; import {CacheItemType, CheckpointStateCache} from "./types.js"; @@ -42,7 +42,7 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { this.maxEpochs = maxEpochs; } - async getOrReload(cp: CheckpointHex, opts?: StateCloneOpts): Promise { + async getOrReload(cp: CheckpointHex, opts?: StateRegenerationOpts): Promise { return this.get(cp, opts); } @@ -54,7 +54,7 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { async getOrReloadLatest( rootHex: string, maxEpoch: number, - opts?: StateCloneOpts + opts?: StateRegenerationOpts ): Promise { return this.getLatest(rootHex, maxEpoch, opts); } @@ -64,7 +64,7 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { return 0; } - get(cp: CheckpointHex, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { + get(cp: CheckpointHex, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null { this.metrics?.lookups.inc(); const cpKey = toCheckpointKey(cp); const item = this.cache.get(cpKey); @@ -98,7 +98,7 @@ export class InMemoryCheckpointStateCache implements CheckpointStateCache { /** * Searches for the latest cached state with a `root`, starting with `epoch` and descending */ - getLatest(rootHex: RootHex, maxEpoch: Epoch, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { + getLatest(rootHex: RootHex, maxEpoch: Epoch, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null { // sort epochs in descending order, only consider epochs lte `epoch` const epochs = Array.from(this.epochIndex.keys()) .sort((a, b) => b - a) diff --git a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts index a7e0a7cdecfe..9a08aaa75461 100644 --- a/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts +++ b/packages/beacon-node/src/chain/stateCache/persistentCheckpointsCache.ts @@ -7,7 +7,7 @@ import {Logger, MapDef, fromHex, sleep, toHex, toRootHex} from "@lodestar/utils" import {Metrics} from "../../metrics/index.js"; import {AllocSource, BufferPool, BufferWithKey} from "../../util/bufferPool.js"; import {IClock} from "../../util/clock.js"; -import {StateCloneOpts} from "../regen/interface.js"; +import {StateRegenerationOpts} from "../regen/interface.js"; import {serializeState} from "../serializeState.js"; import {CPStateDatastore, DatastoreKey} from "./datastore/index.js"; import {MapTracker} from "./mapMetrics.js"; @@ -168,7 +168,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { * - Get block for processing * - Regen head state */ - async getOrReload(cp: CheckpointHex, opts?: StateCloneOpts): Promise { + async getOrReload(cp: CheckpointHex, opts?: StateRegenerationOpts): Promise { const stateOrStateBytesData = await this.getStateOrLoadDb(cp, opts); if (stateOrStateBytesData === null || isCachedBeaconState(stateOrStateBytesData)) { return stateOrStateBytesData?.clone(opts?.dontTransferCache) ?? null; @@ -240,7 +240,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { */ async getStateOrLoadDb( cp: CheckpointHex, - opts?: StateCloneOpts + opts?: StateRegenerationOpts ): Promise { const cpKey = toCacheKey(cp); const inMemoryState = this.get(cpKey, opts); @@ -272,7 +272,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { /** * Similar to get() api without reloading from disk */ - get(cpOrKey: CheckpointHex | string, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { + get(cpOrKey: CheckpointHex | string, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null { this.metrics?.cpStateCache.lookups.inc(); const cpKey = typeof cpOrKey === "string" ? cpOrKey : toCacheKey(cpOrKey); const cacheItem = this.cache.get(cpKey); @@ -323,7 +323,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { /** * Searches in-memory state for the latest cached state with a `root` without reload, starting with `epoch` and descending */ - getLatest(rootHex: RootHex, maxEpoch: Epoch, opts?: StateCloneOpts): CachedBeaconStateAllForks | null { + getLatest(rootHex: RootHex, maxEpoch: Epoch, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null { // sort epochs in descending order, only consider epochs lte `epoch` const epochs = Array.from(this.epochIndex.keys()) .sort((a, b) => b - a) @@ -349,7 +349,7 @@ export class PersistentCheckpointStateCache implements CheckpointStateCache { async getOrReloadLatest( rootHex: RootHex, maxEpoch: Epoch, - opts?: StateCloneOpts + opts?: StateRegenerationOpts ): Promise { // sort epochs in descending order, only consider epochs lte `epoch` const epochs = Array.from(this.epochIndex.keys()) diff --git a/packages/beacon-node/src/chain/stateCache/types.ts b/packages/beacon-node/src/chain/stateCache/types.ts index 403b469dd352..19f05c23ee35 100644 --- a/packages/beacon-node/src/chain/stateCache/types.ts +++ b/packages/beacon-node/src/chain/stateCache/types.ts @@ -1,7 +1,7 @@ import {routes} from "@lodestar/api"; import {CachedBeaconStateAllForks} from "@lodestar/state-transition"; import {Epoch, RootHex, phase0} from "@lodestar/types"; -import {StateCloneOpts} from "../regen/interface.js"; +import {StateRegenerationOpts} from "../regen/interface.js"; export type CheckpointHex = {epoch: Epoch; rootHex: RootHex}; @@ -21,7 +21,7 @@ export type CheckpointHex = {epoch: Epoch; rootHex: RootHex}; * The cache key is state root */ export interface BlockStateCache { - get(rootHex: RootHex, opts?: StateCloneOpts): CachedBeaconStateAllForks | null; + get(rootHex: RootHex, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null; add(item: CachedBeaconStateAllForks): void; setHeadState(item: CachedBeaconStateAllForks | null): void; /** @@ -60,15 +60,15 @@ export interface BlockStateCache { */ export interface CheckpointStateCache { init?: () => Promise; - getOrReload(cp: CheckpointHex, opts?: StateCloneOpts): Promise; + getOrReload(cp: CheckpointHex, opts?: StateRegenerationOpts): Promise; getStateOrBytes(cp: CheckpointHex): Promise; - get(cpOrKey: CheckpointHex | string, opts?: StateCloneOpts): CachedBeaconStateAllForks | null; + get(cpOrKey: CheckpointHex | string, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null; add(cp: phase0.Checkpoint, state: CachedBeaconStateAllForks): void; - getLatest(rootHex: RootHex, maxEpoch: Epoch, opts?: StateCloneOpts): CachedBeaconStateAllForks | null; + getLatest(rootHex: RootHex, maxEpoch: Epoch, opts?: StateRegenerationOpts): CachedBeaconStateAllForks | null; getOrReloadLatest( rootHex: RootHex, maxEpoch: Epoch, - opts?: StateCloneOpts + opts?: StateRegenerationOpts ): Promise; updatePreComputedCheckpoint(rootHex: RootHex, epoch: Epoch): number | null; prune(finalizedEpoch: Epoch, justifiedEpoch: Epoch): void; diff --git a/packages/state-transition/src/cache/epochCache.ts b/packages/state-transition/src/cache/epochCache.ts index 86e63c672024..af9e79bc831c 100644 --- a/packages/state-transition/src/cache/epochCache.ts +++ b/packages/state-transition/src/cache/epochCache.ts @@ -49,6 +49,7 @@ import { import {computeBaseRewardPerIncrement, computeSyncParticipantReward} from "../util/syncCommittee.js"; import {sumTargetUnslashedBalanceIncrements} from "../util/targetUnslashedBalance.js"; import {EffectiveBalanceIncrements, getEffectiveBalanceIncrementsWithLen} from "./effectiveBalanceIncrements.js"; +import {EpochTransitionCache} from "./epochTransitionCache.js"; import {Index2PubkeyCache, syncPubkeys} from "./pubkeyCache.js"; import {CachedBeaconStateAllForks} from "./stateCache.js"; import { @@ -605,14 +606,7 @@ export class EpochCache { * Steps for afterProcessEpoch * 1) update previous/current/next values of cached items */ - afterProcessEpoch( - state: CachedBeaconStateAllForks, - epochTransitionCache: { - nextShufflingDecisionRoot: RootHex; - nextShufflingActiveIndices: Uint32Array; - nextEpochTotalActiveBalanceByIncrement: number; - } - ): void { + afterProcessEpoch(state: CachedBeaconStateAllForks, epochTransitionCache: EpochTransitionCache): void { // Because the slot was incremented before entering this function the "next epoch" is actually the "current epoch" // in this context but that is not actually true because the state transition happens in the last 4 seconds of the // epoch. For the context of this function "upcoming epoch" is used to denote the epoch that will begin after this @@ -657,28 +651,35 @@ export class EpochCache { this.nextDecisionRoot = epochTransitionCache.nextShufflingDecisionRoot; this.nextActiveIndices = epochTransitionCache.nextShufflingActiveIndices; if (this.shufflingCache) { - this.nextShuffling = null; - // This promise will resolve immediately after the synchronous code of the state-transition runs. Until - // the build is done on a worker thread it will be calculated immediately after the epoch transition - // completes. Once the work is done concurrently it should be ready by time this get runs so the promise - // will resolve directly on the next spin of the event loop because the epoch transition and shuffling take - // about the same time to calculate so theoretically its ready now. Do not await here though in case it - // is not ready yet as the transition must not be asynchronous. - this.shufflingCache - .get(epochAfterUpcoming, this.nextDecisionRoot) - .then((shuffling) => { - if (!shuffling) { - throw new Error("EpochShuffling not returned from get in afterProcessEpoch"); - } - this.nextShuffling = shuffling; - }) - .catch((err) => { - this.shufflingCache?.logger?.error( - "EPOCH_CONTEXT_SHUFFLING_BUILD_ERROR", - {epoch: epochAfterUpcoming, decisionRoot: epochTransitionCache.nextShufflingDecisionRoot}, - err - ); + if (!epochTransitionCache.asyncShufflingCalculation) { + this.nextShuffling = this.shufflingCache.getSync(epochAfterUpcoming, this.nextDecisionRoot, { + state, + activeIndices: this.nextActiveIndices, }); + } else { + this.nextShuffling = null; + // This promise will resolve immediately after the synchronous code of the state-transition runs. Until + // the build is done on a worker thread it will be calculated immediately after the epoch transition + // completes. Once the work is done concurrently it should be ready by time this get runs so the promise + // will resolve directly on the next spin of the event loop because the epoch transition and shuffling take + // about the same time to calculate so theoretically its ready now. Do not await here though in case it + // is not ready yet as the transition must not be asynchronous. + this.shufflingCache + .get(epochAfterUpcoming, this.nextDecisionRoot) + .then((shuffling) => { + if (!shuffling) { + throw new Error("EpochShuffling not returned from get in afterProcessEpoch"); + } + this.nextShuffling = shuffling; + }) + .catch((err) => { + this.shufflingCache?.logger?.error( + "EPOCH_CONTEXT_SHUFFLING_BUILD_ERROR", + {epoch: epochAfterUpcoming, decisionRoot: epochTransitionCache.nextShufflingDecisionRoot}, + err + ); + }); + } } else { // Only for testing. shufflingCache should always be available in prod this.nextShuffling = computeEpochShuffling(state, this.nextActiveIndices, epochAfterUpcoming); diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts index b21f940c28d5..d159ca5f3763 100644 --- a/packages/state-transition/src/cache/epochTransitionCache.ts +++ b/packages/state-transition/src/cache/epochTransitionCache.ts @@ -33,6 +33,10 @@ export type EpochTransitionCacheOpts = { * Assert progressive balances the same to EpochTransitionCache */ assertCorrectProgressiveBalances?: boolean; + /** + * Do not queue shuffling calculation async. Forces sync JIT calculation in afterProcessEpoch + */ + asyncShufflingCalculation?: boolean; }; /** @@ -176,6 +180,12 @@ export interface EpochTransitionCache { */ nextEpochTotalActiveBalanceByIncrement: number; + /** + * Compute the shuffling sync or async. Defaults to synchronous. Need to pass `true` with the + * `EpochTransitionCacheOpts` + */ + asyncShufflingCalculation: boolean; + /** * Track by validator index if it's active in the prev epoch. * Used in metrics @@ -387,7 +397,11 @@ export function beforeProcessEpoch( for (let i = 0; i < nextEpochShufflingActiveIndicesLength; i++) { nextShufflingActiveIndices[i] = nextEpochShufflingActiveValidatorIndices[i]; } - state.epochCtx.shufflingCache?.build(epochAfterNext, nextShufflingDecisionRoot, state, nextShufflingActiveIndices); + + const asyncShufflingCalculation = opts?.asyncShufflingCalculation ?? false; + if (asyncShufflingCalculation) { + state.epochCtx.shufflingCache?.build(epochAfterNext, nextShufflingDecisionRoot, state, nextShufflingActiveIndices); + } if (totalActiveStakeByIncrement < 1) { totalActiveStakeByIncrement = 1; @@ -514,6 +528,7 @@ export function beforeProcessEpoch( indicesToEject, nextShufflingDecisionRoot, nextShufflingActiveIndices, + asyncShufflingCalculation, // to be updated in processEffectiveBalanceUpdates nextEpochTotalActiveBalanceByIncrement: 0, isActivePrevEpoch, From 69ae688bed4b17f93801494aa16919888935c780 Mon Sep 17 00:00:00 2001 From: Nazar Hussain Date: Wed, 4 Dec 2024 14:11:06 +0100 Subject: [PATCH 21/43] chore: unpin nodejs version from 22.4 (#6982) * Revert "chore: pin nodejs version to 22.4 (#6964)" This reverts commit f20484bb4b6b5f3a27c624556d1e6fb9f448b969. * Don't revert formatting changes --------- Co-authored-by: Nico Flaig Co-authored-by: Cayman --- .github/workflows/benchmark.yml | 2 +- .github/workflows/binaries.yml | 2 +- .github/workflows/docs-check.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/publish-dev.yml | 2 +- .github/workflows/publish-rc.yml | 2 +- .github/workflows/publish-stable.yml | 2 +- .github/workflows/test-sim-merge.yml | 2 +- .github/workflows/test-sim.yml | 12 ++++++------ .github/workflows/test.yml | 14 +++++++------- Dockerfile | 6 +++--- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 671434fe19ad..e515bef7f92a 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22.4 + node-version: 22 check-latest: true cache: yarn - name: Node.js version diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index 722894424b91..469b0803c378 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -42,7 +42,7 @@ jobs: sudo apt-get install -y build-essential python3 - uses: "./.github/actions/setup-and-build" with: - node: 22.4 + node: 22 - run: | mkdir -p dist yarn global add caxa@3.0.1 diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml index bd7310995d62..180f1c16fdaa 100644 --- a/.github/workflows/docs-check.yml +++ b/.github/workflows/docs-check.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22.4 + node-version: 22 cache: yarn - name: Node.js version id: node diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a4c0f18cdbe3..cec2bca86b4b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 22.4 + node-version: 22 check-latest: true cache: yarn diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml index 4e8c76e0dfdd..cb08c5c8fae0 100644 --- a/.github/workflows/publish-dev.yml +++ b/.github/workflows/publish-dev.yml @@ -20,7 +20,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-node@v4 with: - node-version: 22.4 + node-version: 22 registry-url: "https://registry.npmjs.org" check-latest: true cache: yarn diff --git a/.github/workflows/publish-rc.yml b/.github/workflows/publish-rc.yml index 936072de42c9..e16cbce814dc 100644 --- a/.github/workflows/publish-rc.yml +++ b/.github/workflows/publish-rc.yml @@ -61,7 +61,7 @@ jobs: - uses: "./.github/actions/setup-and-build" with: - node: 22.4 + node: 22 - name: Generate changelog run: node scripts/generate_changelog.mjs ${{ needs.tag.outputs.prev_tag }} ${{ needs.tag.outputs.tag }} CHANGELOG.md diff --git a/.github/workflows/publish-stable.yml b/.github/workflows/publish-stable.yml index c2909a7e4e24..26fee0ba6052 100644 --- a/.github/workflows/publish-stable.yml +++ b/.github/workflows/publish-stable.yml @@ -67,7 +67,7 @@ jobs: - uses: "./.github/actions/setup-and-build" with: - node: 22.4 + node: 22 - name: Generate changelog run: node scripts/generate_changelog.mjs ${{ needs.tag.outputs.prev_tag }} ${{ needs.tag.outputs.tag }} CHANGELOG.md diff --git a/.github/workflows/test-sim-merge.yml b/.github/workflows/test-sim-merge.yml index 0042a9337bc3..ad79bc2c0035 100644 --- a/.github/workflows/test-sim-merge.yml +++ b/.github/workflows/test-sim-merge.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22.4 + node-version: 22 check-latest: true cache: yarn - name: Node.js version diff --git a/.github/workflows/test-sim.yml b/.github/workflows/test-sim.yml index fbe2691da637..ff28149537d3 100644 --- a/.github/workflows/test-sim.yml +++ b/.github/workflows/test-sim.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" with: - node: 22.4 + node: 22 sim-test-multifork: name: Multifork sim test @@ -42,7 +42,7 @@ jobs: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" with: - node: 22.4 + node: 22 - name: Load env variables uses: ./.github/actions/dotenv - name: Download required docker images before running tests @@ -71,7 +71,7 @@ jobs: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" with: - node: 22.4 + node: 22 - name: Load env variables uses: ./.github/actions/dotenv - name: Download required docker images before running tests @@ -100,7 +100,7 @@ jobs: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" with: - node: 22.4 + node: 22 - name: Load env variables uses: ./.github/actions/dotenv - name: Download required docker images before running tests @@ -129,7 +129,7 @@ jobs: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" with: - node: 22.4 + node: 22 - name: Load env variables uses: ./.github/actions/dotenv - name: Download required docker images before running tests @@ -158,7 +158,7 @@ jobs: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" with: - node: 22.4 + node: 22 - name: Load env variables uses: ./.github/actions/dotenv - name: Download required docker images before running tests diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5514f6e896b7..47e17c56f042 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22.4] + node: [22] steps: # - Uses YAML anchors in the future - uses: actions/checkout@v4 @@ -42,7 +42,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22.4] + node: [22] steps: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" @@ -71,7 +71,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22.4] + node: [22] steps: - uses: actions/checkout@v4 @@ -92,7 +92,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22.4] + node: [22] steps: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" @@ -131,7 +131,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22.4] + node: [22] steps: # - Uses YAML anchors in the future - uses: actions/checkout@v4 @@ -168,7 +168,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22.4] + node: [22] steps: # - Uses YAML anchors in the future - uses: actions/checkout@v4 @@ -192,7 +192,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22.4] + node: [22] steps: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" diff --git a/Dockerfile b/Dockerfile index 0ee8083c85e2..5a1f51bfcee6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # --platform=$BUILDPLATFORM is used build javascript source with host arch # Otherwise TS builds on emulated archs and can be extremely slow (+1h) -FROM --platform=${BUILDPLATFORM:-amd64} node:22.4-slim AS build_src +FROM --platform=${BUILDPLATFORM:-amd64} node:22-slim AS build_src ARG COMMIT WORKDIR /usr/app RUN apt-get update && apt-get install -y g++ make python3 python3-setuptools && apt-get clean && rm -rf /var/lib/apt/lists/* @@ -21,7 +21,7 @@ RUN cd packages/cli && GIT_COMMIT=${COMMIT} yarn write-git-data # Copy built src + node_modules to build native packages for archs different than host. # Note: This step is redundant for the host arch -FROM node:22.4-slim AS build_deps +FROM node:22-slim AS build_deps WORKDIR /usr/app RUN apt-get update && apt-get install -y g++ make python3 python3-setuptools && apt-get clean && rm -rf /var/lib/apt/lists/* @@ -35,7 +35,7 @@ RUN cd node_modules/classic-level && yarn rebuild # Copy built src + node_modules to a new layer to prune unnecessary fs # Previous layer weights 7.25GB, while this final 488MB (as of Oct 2020) -FROM node:22.4-slim +FROM node:22-slim WORKDIR /usr/app COPY --from=build_deps /usr/app . From b5fb76c8e65c59397f34e1ba6b06d618b6845f38 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 5 Dec 2024 20:23:19 +0100 Subject: [PATCH 22/43] chore: update bootnodes file url for holesky and sepolia (#7276) --- packages/cli/src/networks/holesky.ts | 2 +- packages/cli/src/networks/sepolia.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/src/networks/holesky.ts b/packages/cli/src/networks/holesky.ts index 63bc6e07f8f2..ed1dd7c78462 100644 --- a/packages/cli/src/networks/holesky.ts +++ b/packages/cli/src/networks/holesky.ts @@ -3,7 +3,7 @@ export {holeskyChainConfig as chainConfig} from "@lodestar/config/networks"; export const depositContractDeployBlock = 0; export const genesisFileUrl = "https://media.githubusercontent.com/media/eth-clients/holesky/main/metadata/genesis.ssz"; export const bootnodesFileUrl = - "https://raw.githubusercontent.com/eth-clients/holesky/main/metadata/bootstrap_nodes.txt"; + "https://raw.githubusercontent.com/eth-clients/holesky/main/metadata/bootstrap_nodes.yaml"; export const bootEnrs = [ "enr:-Ku4QFo-9q73SspYI8cac_4kTX7yF800VXqJW4Lj3HkIkb5CMqFLxciNHePmMt4XdJzHvhrCC5ADI4D_GkAsxGJRLnQBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpAhnTT-AQFwAP__________gmlkgnY0gmlwhLKAiOmJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyk", diff --git a/packages/cli/src/networks/sepolia.ts b/packages/cli/src/networks/sepolia.ts index 9dfd5dc20a0f..6900ca493057 100644 --- a/packages/cli/src/networks/sepolia.ts +++ b/packages/cli/src/networks/sepolia.ts @@ -3,7 +3,7 @@ export {sepoliaChainConfig as chainConfig} from "@lodestar/config/networks"; export const depositContractDeployBlock = 1273020; export const genesisFileUrl = "https://github.com/eth-clients/sepolia/raw/main/metadata/genesis.ssz"; export const bootnodesFileUrl = - "https://raw.githubusercontent.com/eth-clients/sepolia/main/metadata/bootstrap_nodes.txt"; + "https://raw.githubusercontent.com/eth-clients/sepolia/main/metadata/bootstrap_nodes.yaml"; export const bootEnrs = [ "enr:-KO4QP7MmB3juk8rUjJHcUoxZDU9Np4FlW0HyDEGIjSO7GD9PbSsabu7713cWSUWKDkxIypIXg1A-6lG7ySRGOMZHeGCAmuEZXRoMpDTH2GRkAAAc___________gmlkgnY0gmlwhBSoyGOJc2VjcDI1NmsxoQNta5b_bexSSwwrGW2Re24MjfMntzFd0f2SAxQtMj3ueYN0Y3CCIyiDdWRwgiMo", From d55bb2da72533e5ce01b29620ad9cf7d923abc74 Mon Sep 17 00:00:00 2001 From: acolytec3 <17355484+acolytec3@users.noreply.github.com> Date: Thu, 5 Dec 2024 14:24:31 -0500 Subject: [PATCH 23/43] feat: add `debug_getHistoricalSummaries` endpoint (#7245) * feat: add new getHistoricalSummaries endpoint to debug namespace * Add JSON response * Restructure to use stateId and add proof to response * add test scaffolding * Address feedback * Move getHistoricalSummaries to lodestar namespace * add lodestar namespace unit test * update route name to lodestar namespace * cast state object as Capella state * Lint * json properties need to be lower case * Make it v1 since it's now part of lodestar namespace * Group with other /lodestar endpoints * Simplify beacon node impl * Rename return type * Update test description * Fix variable name --------- Co-authored-by: Nico Flaig --- packages/api/src/beacon/routes/lodestar.ts | 41 +++++++++++++- .../beacon/genericServerTest/lodestar.test.ts | 53 +++++++++++++++++++ .../beacon-node/src/api/impl/debug/index.ts | 2 +- .../src/api/impl/lodestar/index.ts | 29 +++++++++- packages/types/src/capella/sszTypes.ts | 6 ++- 5 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts diff --git a/packages/api/src/beacon/routes/lodestar.ts b/packages/api/src/beacon/routes/lodestar.ts index 81191efd2696..de05b7f2bb44 100644 --- a/packages/api/src/beacon/routes/lodestar.ts +++ b/packages/api/src/beacon/routes/lodestar.ts @@ -1,8 +1,11 @@ +import {ContainerType, ValueOf} from "@chainsafe/ssz"; import {ChainForkConfig} from "@lodestar/config"; -import {Epoch, RootHex, Slot} from "@lodestar/types"; +import {Epoch, RootHex, Slot, ssz} from "@lodestar/types"; import { + ArrayOf, EmptyArgs, EmptyMeta, + EmptyMetaCodec, EmptyRequest, EmptyRequestCodec, EmptyResponseCodec, @@ -10,6 +13,7 @@ import { JsonOnlyResponseCodec, } from "../../utils/codecs.js"; import {Endpoint, RouteDefinitions, Schema} from "../../utils/index.js"; +import {StateArgs} from "./beacon/state.js"; import {FilterGetPeers, NodePeer, PeerDirection, PeerState} from "./node.js"; // See /packages/api/src/routes/index.ts for reasoning and instructions to add new routes @@ -75,6 +79,16 @@ export type LodestarNodePeer = NodePeer & { export type LodestarThreadType = "main" | "network" | "discv5"; +const HistoricalSummariesResponseType = new ContainerType( + { + historicalSummaries: ssz.capella.HistoricalSummaries, + proof: ArrayOf(ssz.Bytes8), + }, + {jsonCase: "eth2"} +); + +export type HistoricalSummariesResponse = ValueOf; + export type Endpoints = { /** Trigger to write a heapdump to disk at `dirpath`. May take > 1min */ writeHeapdump: Endpoint< @@ -214,6 +228,16 @@ export type Endpoints = { {count: number} >; + /** Returns historical summaries and proof for a given state ID */ + getHistoricalSummaries: Endpoint< + // ⏎ + "GET", + StateArgs, + {params: {state_id: string}}, + HistoricalSummariesResponse, + EmptyMeta + >; + /** Dump Discv5 Kad values */ discv5GetKadValues: Endpoint< // ⏎ @@ -365,6 +389,21 @@ export function getDefinitions(_config: ChainForkConfig): RouteDefinitions ({params: {state_id: stateId.toString()}}), + parseReq: ({params}) => ({stateId: params.state_id}), + schema: { + params: {state_id: Schema.StringRequired}, + }, + }, + resp: { + data: HistoricalSummariesResponseType, + meta: EmptyMetaCodec, + }, + }, discv5GetKadValues: { url: "/eth/v1/debug/discv5_kad_values", method: "GET", diff --git a/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts b/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts new file mode 100644 index 000000000000..b0f78f4bd62c --- /dev/null +++ b/packages/api/test/unit/beacon/genericServerTest/lodestar.test.ts @@ -0,0 +1,53 @@ +import {config} from "@lodestar/config/default"; +import {FastifyInstance} from "fastify"; +import {afterAll, beforeAll, describe, expect, it, vi} from "vitest"; +import {getClient} from "../../../../src/beacon/client/lodestar.js"; +import {Endpoints, getDefinitions} from "../../../../src/beacon/routes/lodestar.js"; +import {getRoutes} from "../../../../src/beacon/server/lodestar.js"; +import {HttpClient} from "../../../../src/utils/client/httpClient.js"; +import {AnyEndpoint} from "../../../../src/utils/codecs.js"; +import {FastifyRoute} from "../../../../src/utils/server/index.js"; +import {WireFormat} from "../../../../src/utils/wireFormat.js"; +import {getMockApi, getTestServer} from "../../../utils/utils.js"; + +describe("beacon / lodestar", () => { + describe("get HistoricalSummaries as json", () => { + const mockApi = getMockApi(getDefinitions(config)); + let baseUrl: string; + let server: FastifyInstance; + + beforeAll(async () => { + const res = getTestServer(); + server = res.server; + for (const route of Object.values(getRoutes(config, mockApi))) { + server.route(route as FastifyRoute); + } + baseUrl = await res.start(); + }); + + afterAll(async () => { + if (server !== undefined) await server.close(); + }); + + it("getHistoricalSummaries", async () => { + mockApi.getHistoricalSummaries.mockResolvedValue({ + data: { + historicalSummaries: [], + proof: [], + }, + }); + + const httpClient = new HttpClient({baseUrl}); + const client = getClient(config, httpClient); + + const res = await client.getHistoricalSummaries({stateId: "head"}, {responseWireFormat: WireFormat.json}); + + expect(res.ok).toBe(true); + expect(res.wireFormat()).toBe(WireFormat.json); + expect(res.json().data).toStrictEqual({ + historical_summaries: [], + proof: [], + }); + }); + }); +}); diff --git a/packages/beacon-node/src/api/impl/debug/index.ts b/packages/beacon-node/src/api/impl/debug/index.ts index e6c104272237..a004bd80e8f2 100644 --- a/packages/beacon-node/src/api/impl/debug/index.ts +++ b/packages/beacon-node/src/api/impl/debug/index.ts @@ -2,7 +2,7 @@ import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import {ExecutionStatus} from "@lodestar/fork-choice"; import {ZERO_HASH_HEX} from "@lodestar/params"; -import {BeaconState} from "@lodestar/types"; +import {BeaconState, ssz} from "@lodestar/types"; import {isOptimisticBlock} from "../../../util/forkChoice.js"; import {getStateSlotFromBytes} from "../../../util/multifork.js"; import {getStateResponseWithRegen} from "../beacon/state/utils.js"; diff --git a/packages/beacon-node/src/api/impl/lodestar/index.ts b/packages/beacon-node/src/api/impl/lodestar/index.ts index 10787194f5f5..aeef2e11a83e 100644 --- a/packages/beacon-node/src/api/impl/lodestar/index.ts +++ b/packages/beacon-node/src/api/impl/lodestar/index.ts @@ -1,11 +1,12 @@ import fs from "node:fs"; import path from "node:path"; +import {Tree} from "@chainsafe/persistent-merkle-tree"; import {routes} from "@lodestar/api"; import {ApplicationMethods} from "@lodestar/api/server"; import {ChainForkConfig} from "@lodestar/config"; import {Repository} from "@lodestar/db"; -import {SLOTS_PER_EPOCH} from "@lodestar/params"; -import {getLatestWeakSubjectivityCheckpointEpoch} from "@lodestar/state-transition"; +import {ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; +import {BeaconStateCapella, getLatestWeakSubjectivityCheckpointEpoch, loadState} from "@lodestar/state-transition"; import {ssz} from "@lodestar/types"; import {toHex, toRootHex} from "@lodestar/utils"; import {BeaconChain} from "../../../chain/index.js"; @@ -13,6 +14,7 @@ import {QueuedStateRegenerator, RegenRequest} from "../../../chain/regen/index.j import {IBeaconDb} from "../../../db/interface.js"; import {GossipType} from "../../../network/index.js"; import {profileNodeJS, writeHeapSnapshot} from "../../../util/profile.js"; +import {getStateResponseWithRegen} from "../beacon/state/utils.js"; import {ApiModules} from "../types.js"; export function getLodestarApi({ @@ -187,6 +189,29 @@ export function getLodestarApi({ async dumpDbStateIndex() { return {data: await db.stateArchive.dumpRootIndexEntries()}; }, + + async getHistoricalSummaries({stateId}) { + const {state} = await getStateResponseWithRegen(chain, stateId); + + const stateView = ( + state instanceof Uint8Array ? loadState(config, chain.getHeadState(), state).state : state.clone() + ) as BeaconStateCapella; + + const fork = config.getForkName(stateView.slot); + if (ForkSeq[fork] < ForkSeq.capella) { + throw new Error("Historical summaries are not supported before Capella"); + } + + const {gindex} = ssz[fork].BeaconState.getPathInfo(["historicalSummaries"]); + const proof = new Tree(stateView.node).getSingleProof(gindex); + + return { + data: { + historicalSummaries: stateView.historicalSummaries.toValue(), + proof: proof, + }, + }; + }, }; } diff --git a/packages/types/src/capella/sszTypes.ts b/packages/types/src/capella/sszTypes.ts index 3110e59111d9..057bf97650fe 100644 --- a/packages/types/src/capella/sszTypes.ts +++ b/packages/types/src/capella/sszTypes.ts @@ -125,6 +125,10 @@ export const HistoricalSummary = new ContainerType( {typeName: "HistoricalSummary", jsonCase: "eth2"} ); +export const HistoricalSummaries = new ListCompositeType(HistoricalSummary, HISTORICAL_ROOTS_LIMIT, { + typeName: "HistoricalSummaries", +}); + // we don't reuse bellatrix.BeaconState fields since we need to replace some keys // and we cannot keep order doing that export const BeaconState = new ContainerType( @@ -168,7 +172,7 @@ export const BeaconState = new ContainerType( nextWithdrawalIndex: WithdrawalIndex, // [New in Capella] nextWithdrawalValidatorIndex: ValidatorIndex, // [New in Capella] // Deep history valid from Capella onwards - historicalSummaries: new ListCompositeType(HistoricalSummary, HISTORICAL_ROOTS_LIMIT), // [New in Capella] + historicalSummaries: HistoricalSummaries, // [New in Capella] }, {typeName: "BeaconState", jsonCase: "eth2"} ); From 86ed6f45d548f00ef54a1d35ac20387923762563 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 6 Dec 2024 09:38:42 +0100 Subject: [PATCH 24/43] chore: log sync committee signature errors as `error` (#7283) --- packages/beacon-node/src/api/impl/beacon/pool/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/api/impl/beacon/pool/index.ts b/packages/beacon-node/src/api/impl/beacon/pool/index.ts index 45fb763d9540..c283d6d3214c 100644 --- a/packages/beacon-node/src/api/impl/beacon/pool/index.ts +++ b/packages/beacon-node/src/api/impl/beacon/pool/index.ts @@ -257,7 +257,7 @@ export function getBeaconPoolApi({ } failures.push({index: i, message: (e as Error).message}); - logger.debug( + logger.error( `Error on submitPoolSyncCommitteeSignatures [${i}]`, {slot: signature.slot, validatorIndex: signature.validatorIndex}, e as Error From cd1211fef36976f459097ac6765b7822d5a86595 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 6 Dec 2024 09:56:25 +0100 Subject: [PATCH 25/43] fix: update engine_getClientVersionV1 commit encoding (#7282) --- packages/beacon-node/src/execution/engine/http.ts | 5 +++-- packages/beacon-node/src/util/kzg.ts | 9 +-------- packages/config/src/genesisConfig/index.ts | 6 +----- packages/utils/src/format.ts | 7 +++++++ 4 files changed, 12 insertions(+), 15 deletions(-) diff --git a/packages/beacon-node/src/execution/engine/http.ts b/packages/beacon-node/src/execution/engine/http.ts index ea064d2fe816..0d2656ae46e2 100644 --- a/packages/beacon-node/src/execution/engine/http.ts +++ b/packages/beacon-node/src/execution/engine/http.ts @@ -2,6 +2,7 @@ import {Logger} from "@lodestar/logger"; import {ForkName, ForkSeq, SLOTS_PER_EPOCH} from "@lodestar/params"; import {ExecutionPayload, ExecutionRequests, Root, RootHex, Wei} from "@lodestar/types"; import {BlobAndProof} from "@lodestar/types/deneb"; +import {strip0xPrefix} from "@lodestar/utils"; import { ErrorJsonRpcResponse, HttpRpcError, @@ -522,11 +523,11 @@ export class ExecutionEngineHttp implements IExecutionEngine { const response = await this.rpc.fetchWithRetries< EngineApiRpcReturnTypes[typeof method], EngineApiRpcParamTypes[typeof method] - >({method, params: [clientVersion]}); + >({method, params: [{...clientVersion, commit: `0x${clientVersion.commit}`}]}); const clientVersions = response.map((cv) => { const code = cv.code in ClientCode ? ClientCode[cv.code as keyof typeof ClientCode] : ClientCode.XX; - return {code, name: cv.name, version: cv.version, commit: cv.commit}; + return {code, name: cv.name, version: cv.version, commit: strip0xPrefix(cv.commit)}; }); if (clientVersions.length === 0) { diff --git a/packages/beacon-node/src/util/kzg.ts b/packages/beacon-node/src/util/kzg.ts index 36a7d19f8d2b..696eeae73370 100644 --- a/packages/beacon-node/src/util/kzg.ts +++ b/packages/beacon-node/src/util/kzg.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import path from "node:path"; import {fileURLToPath} from "node:url"; -import {fromHex, toHex} from "@lodestar/utils"; +import {fromHex, strip0xPrefix, toHex} from "@lodestar/utils"; // "c-kzg" has hardcoded the mainnet value, do not use params export const FIELD_ELEMENTS_PER_BLOB_MAINNET = 4096; @@ -154,10 +154,3 @@ export function trustedSetupJsonToTxt(data: TrustedSetupJSON): TrustedSetupTXT { ...data.setup_G2.map(strip0xPrefix), ].join("\n"); } - -function strip0xPrefix(hex: string): string { - if (hex.startsWith("0x")) { - return hex.slice(2); - } - return hex; -} diff --git a/packages/config/src/genesisConfig/index.ts b/packages/config/src/genesisConfig/index.ts index dad79df1a98d..992185461b58 100644 --- a/packages/config/src/genesisConfig/index.ts +++ b/packages/config/src/genesisConfig/index.ts @@ -1,6 +1,6 @@ import {DOMAIN_VOLUNTARY_EXIT, ForkName, SLOTS_PER_EPOCH} from "@lodestar/params"; import {DomainType, ForkDigest, Root, Slot, Version, phase0, ssz} from "@lodestar/types"; -import {toHex} from "@lodestar/utils"; +import {strip0xPrefix, toHex} from "@lodestar/utils"; import {ChainForkConfig} from "../beaconConfig.js"; import {CachedGenesis, ForkDigestHex} from "./types.js"; export type {ForkDigestContext} from "./types.js"; @@ -142,10 +142,6 @@ function toHexStringNoPrefix(hex: string | Uint8Array): string { return strip0xPrefix(typeof hex === "string" ? hex : toHex(hex)); } -function strip0xPrefix(hex: string): string { - return hex.startsWith("0x") ? hex.slice(2) : hex; -} - function computeForkDigest(currentVersion: Version, genesisValidatorsRoot: Root): ForkDigest { return computeForkDataRoot(currentVersion, genesisValidatorsRoot).slice(0, 4); } diff --git a/packages/utils/src/format.ts b/packages/utils/src/format.ts index b36412072720..94bf0d635ced 100644 --- a/packages/utils/src/format.ts +++ b/packages/utils/src/format.ts @@ -65,3 +65,10 @@ export function prettyMsToTime(timeMs: number): string { const date = new Date(0, 0, 0, 0, 0, 0, timeMs); return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}.${date.getMilliseconds()}`; } + +/** + * Remove 0x prefix from a string + */ +export function strip0xPrefix(hex: string): string { + return hex.startsWith("0x") ? hex.slice(2) : hex; +} From e353f67426581e622c6df83c30992b4a28dcb617 Mon Sep 17 00:00:00 2001 From: twoeths Date: Fri, 6 Dec 2024 16:30:33 +0700 Subject: [PATCH 26/43] fix: check pubkey or validator index known to a state (#7284) * fix: check pubkey or validator index known to a state * chore: add more comments --- .../src/block/processConsolidationRequest.ts | 7 ++++++- .../src/epoch/processPendingDeposits.ts | 6 +++--- packages/state-transition/src/util/electra.ts | 17 +++++++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/state-transition/src/block/processConsolidationRequest.ts b/packages/state-transition/src/block/processConsolidationRequest.ts index d0650135d0c6..1a1d83eee0be 100644 --- a/packages/state-transition/src/block/processConsolidationRequest.ts +++ b/packages/state-transition/src/block/processConsolidationRequest.ts @@ -3,7 +3,7 @@ import {electra, ssz} from "@lodestar/types"; import {CachedBeaconStateElectra} from "../types.js"; import {hasEth1WithdrawalCredential} from "../util/capella.js"; -import {hasExecutionWithdrawalCredential, switchToCompoundingValidator} from "../util/electra.js"; +import {hasExecutionWithdrawalCredential, isPubkeyKnown, switchToCompoundingValidator} from "../util/electra.js"; import {computeConsolidationEpochAndUpdateChurn} from "../util/epoch.js"; import {getConsolidationChurnLimit, isActiveValidator} from "../util/validator.js"; @@ -13,6 +13,10 @@ export function processConsolidationRequest( consolidationRequest: electra.ConsolidationRequest ): void { const {sourcePubkey, targetPubkey, sourceAddress} = consolidationRequest; + if (!isPubkeyKnown(state, sourcePubkey) || !isPubkeyKnown(state, targetPubkey)) { + return; + } + const sourceIndex = state.epochCtx.getValidatorIndex(sourcePubkey); const targetIndex = state.epochCtx.getValidatorIndex(targetPubkey); @@ -97,6 +101,7 @@ function isValidSwitchToCompoundRequest( // Verify pubkey exists if (sourceIndex === null) { + // this check is mainly to make the compiler happy, pubkey is checked by the consumer already return false; } diff --git a/packages/state-transition/src/epoch/processPendingDeposits.ts b/packages/state-transition/src/epoch/processPendingDeposits.ts index d925aa1cc741..441d5601a380 100644 --- a/packages/state-transition/src/epoch/processPendingDeposits.ts +++ b/packages/state-transition/src/epoch/processPendingDeposits.ts @@ -3,7 +3,7 @@ import {PendingDeposit} from "@lodestar/types/lib/electra/types.js"; import {addValidatorToRegistry, isValidDepositSignature} from "../block/processDeposit.js"; import {CachedBeaconStateElectra, EpochTransitionCache} from "../types.js"; import {increaseBalance} from "../util/balance.js"; -import {hasCompoundingWithdrawalCredential} from "../util/electra.js"; +import {hasCompoundingWithdrawalCredential, isValidatorKnown} from "../util/electra.js"; import {computeStartSlotAtEpoch} from "../util/epoch.js"; import {getActivationExitChurnLimit} from "../util/validator.js"; @@ -51,7 +51,7 @@ export function processPendingDeposits(state: CachedBeaconStateElectra, cache: E let isValidatorWithdrawn = false; const validatorIndex = state.epochCtx.getValidatorIndex(deposit.pubkey); - if (validatorIndex !== null) { + if (isValidatorKnown(state, validatorIndex)) { const validator = state.validators.getReadonly(validatorIndex); isValidatorExited = validator.exitEpoch < FAR_FUTURE_EPOCH; isValidatorWithdrawn = validator.withdrawableEpoch < nextEpoch; @@ -103,7 +103,7 @@ function applyPendingDeposit( const {pubkey, withdrawalCredentials, amount, signature} = deposit; const cachedBalances = cache.balances; - if (validatorIndex === null) { + if (!isValidatorKnown(state, validatorIndex)) { // Verify the deposit signature (proof of possession) which is not checked by the deposit contract if (isValidDepositSignature(state.config, pubkey, withdrawalCredentials, amount, signature)) { addValidatorToRegistry(ForkSeq.electra, state, pubkey, withdrawalCredentials, amount); diff --git a/packages/state-transition/src/util/electra.ts b/packages/state-transition/src/util/electra.ts index f5b899eadcab..a9736dc8161e 100644 --- a/packages/state-transition/src/util/electra.ts +++ b/packages/state-transition/src/util/electra.ts @@ -45,3 +45,20 @@ export function queueExcessActiveBalance(state: CachedBeaconStateElectra, index: state.pendingDeposits.push(pendingDeposit); } } + +/** + * Since we share pubkey2index, pubkey maybe added by other epoch transition but we don't have that validator in this state + */ +export function isPubkeyKnown(state: CachedBeaconStateElectra, pubkey: Uint8Array): boolean { + return isValidatorKnown(state, state.epochCtx.getValidatorIndex(pubkey)); +} + +/** + * Since we share pubkey2index, validatorIndex maybe not null but we don't have that validator in this state + */ +export function isValidatorKnown( + state: CachedBeaconStateElectra, + index: ValidatorIndex | null +): index is ValidatorIndex { + return index !== null && index < state.validators.length; +} From f87eb0b2c74e42ea72324392380f28c5047477a1 Mon Sep 17 00:00:00 2001 From: Varun Guleria <152203177+varunguleriaCodes@users.noreply.github.com> Date: Fri, 6 Dec 2024 20:19:45 +0530 Subject: [PATCH 27/43] feat: lodestar script setup (#7254) * feat: lodestar_setup * feat: script_updates + docs * feat: script_addition_in_docs + command_update * Remove duplicate script from docs folder * Minor script updates * Update script to prepare docs and ignore copied file * Update installation page * Wording --------- Co-authored-by: Nico Flaig --- .gitignore | 1 + .../pages/run/getting-started/installation.md | 6 ++ scripts/install-binary.sh | 91 +++++++++++++++++++ scripts/prepare-docs.sh | 3 + 4 files changed, 101 insertions(+) create mode 100755 scripts/install-binary.sh diff --git a/.gitignore b/.gitignore index 52d9bc66e5b6..e54f7440864d 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ docs/pages/libraries/lightclient-prover/lightclient.md docs/pages/libraries/lightclient-prover/prover.md docs/pages/api/api-reference.md docs/pages/contribution/getting-started.md +docs/static/install ## Docusaurus docs/.docusaurus/ docs/build/ diff --git a/docs/pages/run/getting-started/installation.md b/docs/pages/run/getting-started/installation.md index 40c4865f7726..b2b4003f90a5 100644 --- a/docs/pages/run/getting-started/installation.md +++ b/docs/pages/run/getting-started/installation.md @@ -4,6 +4,12 @@ Binaries can be downloaded from the Lodestar [release page](https://github.com/ChainSafe/lodestar/releases/latest) under the `Assets` section. +Run the following command to install the latest version + +```bash +curl -fsSL https://chainsafe.github.io/lodestar/install | bash +``` + ## Docker Installation The [`chainsafe/lodestar`](https://hub.docker.com/r/chainsafe/lodestar) Docker Hub repository is maintained actively. It contains the `lodestar` CLI preinstalled. diff --git a/scripts/install-binary.sh b/scripts/install-binary.sh new file mode 100755 index 000000000000..f6db9948eeda --- /dev/null +++ b/scripts/install-binary.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# ASCII art +echo " _ _ _ " +echo " | | | | | | " +echo " | | ___ __| | ___ ___| |_ __ _ _ __ " +echo " | |/ _ \ / _ |/ _ \/ __| __/ _ | __|" +echo " | | (_) | (_| | __/\__ \ || (_| | | " +echo " |_|\___/ \__ _|\___||___/\__\__ _|_| " +echo "" + +# Declare directories +TEMP_DIR=$(mktemp -d) +LOCAL_BIN="$HOME/.local/bin" + +# Ensure ~/.local/bin exists +mkdir -p "$LOCAL_BIN" + +# Inform the user about temporary directory usage +echo "Using temporary directory: $TEMP_DIR" + +# Fetch the latest release tag from GitHub +echo "Fetching the latest version information..." +VERSION=$(curl -s "https://api.github.com/repos/ChainSafe/lodestar/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') + +# Check if VERSION is empty +if [ -z "$VERSION" ]; then + echo "Failed to fetch the latest version. Exiting." + exit 1 +fi + +echo "Latest version detected: $VERSION" + +# Detect the operating system and architecture +OS=$(uname -s) +ARCH=$(uname -m) + +# Translate architecture to expected format +case $ARCH in + x86_64) ARCH="amd64" ;; + aarch64|arm64) ARCH="arm64" ;; + *) + echo "Unsupported architecture: $ARCH. Exiting." + exit 1 + ;; +esac + +# Translate OS to expected format +case $OS in + Linux) PLATFORM="linux-$ARCH" ;; + *) + echo "Unsupported operating system: $OS. Exiting." + exit 1 + ;; +esac + +# Construct the download URL +URL="https://github.com/ChainSafe/lodestar/releases/download/$VERSION/lodestar-$VERSION-$PLATFORM.tar.gz" +echo "Downloading from: $URL" + +# Download the tarball +if ! wget "$URL" -O "$TEMP_DIR/lodestar-$VERSION-$PLATFORM.tar.gz"; then + echo "Download failed. Exiting." + exit 1 +fi + +# Extract the tarball +echo "Extracting the binary..." +if ! tar -xzf "$TEMP_DIR/lodestar-$VERSION-$PLATFORM.tar.gz" -C "$TEMP_DIR"; then + echo "Extraction failed. Exiting." + exit 1 +fi + +# Move the binary to ~/.local/bin +echo "Moving the binary to $LOCAL_BIN..." +mv "$TEMP_DIR/lodestar" "$LOCAL_BIN/" +chmod +x "$LOCAL_BIN/lodestar" + +# Verify if ~/.local/bin is in PATH +if [[ ":$PATH:" != *":$LOCAL_BIN:"* ]]; then + echo "Adding $LOCAL_BIN to PATH..." + echo 'export PATH="$PATH:$HOME/.local/bin"' >> "$HOME/.bashrc" + echo "Run 'source ~/.bashrc' to apply changes to your shell." +fi + +# Clean up the temporary directory +rm -rf "$TEMP_DIR" + +# Inform the user of successful installation +echo "Installation complete!" +echo "Run 'lodestar --help' to get started." diff --git a/scripts/prepare-docs.sh b/scripts/prepare-docs.sh index d2bac519a7f2..2fa16a7cc759 100755 --- a/scripts/prepare-docs.sh +++ b/scripts/prepare-docs.sh @@ -17,3 +17,6 @@ cp -r packages/prover/README.md $DOCS_DIR/pages/libraries/lightclient-prover/pro # Copy visual assets rm -rf $DOCS_DIR/pages/assets $DOCS_DIR/pages/images cp -r $ASSETS_DIR $DOCS_DIR/pages/assets + +# Copy binary install script to docs +cp scripts/install-binary.sh $DOCS_DIR/static/install From dad9037e7739d5bcbccfe627e715ef40e9ba935b Mon Sep 17 00:00:00 2001 From: ClockworkYuzu Date: Fri, 6 Dec 2024 13:00:13 -0800 Subject: [PATCH 28/43] feat: add terminal-sized Electra giraffe banner (#7286) * Create giraffeBanners.ts * Wire in banner * Fix file name * lint * Address @nflaig's comment --------- Co-authored-by: NC <17676176+ensi321@users.noreply.github.com> --- .../src/chain/blocks/utils/giraffeBanner.ts | 27 +++++++++++++++++++ .../src/chain/blocks/verifyBlock.ts | 6 +++++ 2 files changed, 33 insertions(+) create mode 100644 packages/beacon-node/src/chain/blocks/utils/giraffeBanner.ts diff --git a/packages/beacon-node/src/chain/blocks/utils/giraffeBanner.ts b/packages/beacon-node/src/chain/blocks/utils/giraffeBanner.ts new file mode 100644 index 000000000000..d3987831a064 --- /dev/null +++ b/packages/beacon-node/src/chain/blocks/utils/giraffeBanner.ts @@ -0,0 +1,27 @@ +export const ELECTRA_GIRAFFE_BANNER = String.raw` + 2048 + :--: + :-@==+-: + :-=++#+#++#> + :-=+=#+: + ::+*--@-*: + :-+=%*#%@-: + MAXEB**=+%*+: + :-*###+*#*=-: + :--=+*+==#*=-: + :-*=+#*=*-#*+%@%%%#*+: + -=+-+**#+#%%%*#@@+%%#=#% + 32 -*=*+#+=%*#%*#%#+*##***-: + : #+**+*+=*+*%*%%*==++@**=: + =++++=: ::----:: +=-@*: + +++=- -++ =+: + -=@ :+- -+ + :-: :+: :- + :+ := -= + := - @ + - @ : + -: -: - + *: :- =- + :- --: =: + ::*-: :::: :-: +`; diff --git a/packages/beacon-node/src/chain/blocks/verifyBlock.ts b/packages/beacon-node/src/chain/blocks/verifyBlock.ts index 5ead67a720f7..2ce78eb5f3f1 100644 --- a/packages/beacon-node/src/chain/blocks/verifyBlock.ts +++ b/packages/beacon-node/src/chain/blocks/verifyBlock.ts @@ -15,6 +15,7 @@ import {BlockProcessOpts} from "../options.js"; import {RegenCaller} from "../regen/index.js"; import {BlockInput, BlockInputType, ImportBlockOpts} from "./types.js"; import {DENEB_BLOWFISH_BANNER} from "./utils/blowfishBanner.js"; +import {ELECTRA_GIRAFFE_BANNER} from "./utils/giraffeBanner.js"; import {CAPELLA_OWL_BANNER} from "./utils/ownBanner.js"; import {POS_PANDA_MERGE_TRANSITION_BANNER} from "./utils/pandaMergeTransitionBanner.js"; import {verifyBlocksDataAvailability} from "./verifyBlocksDataAvailability.js"; @@ -157,6 +158,11 @@ export async function verifyBlocksInEpoch( this.logger.info("Activating blobs", {epoch: this.config.DENEB_FORK_EPOCH}); break; + case ForkName.electra: + this.logger.info(ELECTRA_GIRAFFE_BANNER); + this.logger.info("Activating maxEB", {epoch: this.config.ELECTRA_FORK_EPOCH}); + break; + default: } } From 99794d3b261bb50f9a6c7d9f6ca19291b3f30911 Mon Sep 17 00:00:00 2001 From: Cayman Date: Thu, 12 Dec 2024 13:16:55 -0500 Subject: [PATCH 29/43] chore: pin nodejs version to 22.4 (#7291) Revert "chore: unpin nodejs version from 22.4 (#6982)" This reverts commit 69ae688bed4b17f93801494aa16919888935c780. --- .github/workflows/benchmark.yml | 2 +- .github/workflows/binaries.yml | 2 +- .github/workflows/docs-check.yml | 2 +- .github/workflows/docs.yml | 2 +- .github/workflows/publish-dev.yml | 2 +- .github/workflows/publish-rc.yml | 2 +- .github/workflows/publish-stable.yml | 2 +- .github/workflows/test-sim-merge.yml | 2 +- .github/workflows/test-sim.yml | 12 ++++++------ .github/workflows/test.yml | 14 +++++++------- Dockerfile | 6 +++--- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index e515bef7f92a..671434fe19ad 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -33,7 +33,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 22.4 check-latest: true cache: yarn - name: Node.js version diff --git a/.github/workflows/binaries.yml b/.github/workflows/binaries.yml index 469b0803c378..722894424b91 100644 --- a/.github/workflows/binaries.yml +++ b/.github/workflows/binaries.yml @@ -42,7 +42,7 @@ jobs: sudo apt-get install -y build-essential python3 - uses: "./.github/actions/setup-and-build" with: - node: 22 + node: 22.4 - run: | mkdir -p dist yarn global add caxa@3.0.1 diff --git a/.github/workflows/docs-check.yml b/.github/workflows/docs-check.yml index 180f1c16fdaa..bd7310995d62 100644 --- a/.github/workflows/docs-check.yml +++ b/.github/workflows/docs-check.yml @@ -15,7 +15,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 22.4 cache: yarn - name: Node.js version id: node diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index cec2bca86b4b..a4c0f18cdbe3 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 22.4 check-latest: true cache: yarn diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml index cb08c5c8fae0..4e8c76e0dfdd 100644 --- a/.github/workflows/publish-dev.yml +++ b/.github/workflows/publish-dev.yml @@ -20,7 +20,7 @@ jobs: fetch-depth: 0 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 22.4 registry-url: "https://registry.npmjs.org" check-latest: true cache: yarn diff --git a/.github/workflows/publish-rc.yml b/.github/workflows/publish-rc.yml index e16cbce814dc..936072de42c9 100644 --- a/.github/workflows/publish-rc.yml +++ b/.github/workflows/publish-rc.yml @@ -61,7 +61,7 @@ jobs: - uses: "./.github/actions/setup-and-build" with: - node: 22 + node: 22.4 - name: Generate changelog run: node scripts/generate_changelog.mjs ${{ needs.tag.outputs.prev_tag }} ${{ needs.tag.outputs.tag }} CHANGELOG.md diff --git a/.github/workflows/publish-stable.yml b/.github/workflows/publish-stable.yml index 26fee0ba6052..c2909a7e4e24 100644 --- a/.github/workflows/publish-stable.yml +++ b/.github/workflows/publish-stable.yml @@ -67,7 +67,7 @@ jobs: - uses: "./.github/actions/setup-and-build" with: - node: 22 + node: 22.4 - name: Generate changelog run: node scripts/generate_changelog.mjs ${{ needs.tag.outputs.prev_tag }} ${{ needs.tag.outputs.tag }} CHANGELOG.md diff --git a/.github/workflows/test-sim-merge.yml b/.github/workflows/test-sim-merge.yml index ad79bc2c0035..0042a9337bc3 100644 --- a/.github/workflows/test-sim-merge.yml +++ b/.github/workflows/test-sim-merge.yml @@ -30,7 +30,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 22 + node-version: 22.4 check-latest: true cache: yarn - name: Node.js version diff --git a/.github/workflows/test-sim.yml b/.github/workflows/test-sim.yml index ff28149537d3..fbe2691da637 100644 --- a/.github/workflows/test-sim.yml +++ b/.github/workflows/test-sim.yml @@ -31,7 +31,7 @@ jobs: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" with: - node: 22 + node: 22.4 sim-test-multifork: name: Multifork sim test @@ -42,7 +42,7 @@ jobs: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" with: - node: 22 + node: 22.4 - name: Load env variables uses: ./.github/actions/dotenv - name: Download required docker images before running tests @@ -71,7 +71,7 @@ jobs: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" with: - node: 22 + node: 22.4 - name: Load env variables uses: ./.github/actions/dotenv - name: Download required docker images before running tests @@ -100,7 +100,7 @@ jobs: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" with: - node: 22 + node: 22.4 - name: Load env variables uses: ./.github/actions/dotenv - name: Download required docker images before running tests @@ -129,7 +129,7 @@ jobs: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" with: - node: 22 + node: 22.4 - name: Load env variables uses: ./.github/actions/dotenv - name: Download required docker images before running tests @@ -158,7 +158,7 @@ jobs: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" with: - node: 22 + node: 22.4 - name: Load env variables uses: ./.github/actions/dotenv - name: Download required docker images before running tests diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 47e17c56f042..5514f6e896b7 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -19,7 +19,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22] + node: [22.4] steps: # - Uses YAML anchors in the future - uses: actions/checkout@v4 @@ -42,7 +42,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22] + node: [22.4] steps: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" @@ -71,7 +71,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22] + node: [22.4] steps: - uses: actions/checkout@v4 @@ -92,7 +92,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22] + node: [22.4] steps: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" @@ -131,7 +131,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22] + node: [22.4] steps: # - Uses YAML anchors in the future - uses: actions/checkout@v4 @@ -168,7 +168,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22] + node: [22.4] steps: # - Uses YAML anchors in the future - uses: actions/checkout@v4 @@ -192,7 +192,7 @@ jobs: strategy: fail-fast: false matrix: - node: [22] + node: [22.4] steps: - uses: actions/checkout@v4 - uses: "./.github/actions/setup-and-build" diff --git a/Dockerfile b/Dockerfile index 5a1f51bfcee6..0ee8083c85e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,7 @@ # --platform=$BUILDPLATFORM is used build javascript source with host arch # Otherwise TS builds on emulated archs and can be extremely slow (+1h) -FROM --platform=${BUILDPLATFORM:-amd64} node:22-slim AS build_src +FROM --platform=${BUILDPLATFORM:-amd64} node:22.4-slim AS build_src ARG COMMIT WORKDIR /usr/app RUN apt-get update && apt-get install -y g++ make python3 python3-setuptools && apt-get clean && rm -rf /var/lib/apt/lists/* @@ -21,7 +21,7 @@ RUN cd packages/cli && GIT_COMMIT=${COMMIT} yarn write-git-data # Copy built src + node_modules to build native packages for archs different than host. # Note: This step is redundant for the host arch -FROM node:22-slim AS build_deps +FROM node:22.4-slim AS build_deps WORKDIR /usr/app RUN apt-get update && apt-get install -y g++ make python3 python3-setuptools && apt-get clean && rm -rf /var/lib/apt/lists/* @@ -35,7 +35,7 @@ RUN cd node_modules/classic-level && yarn rebuild # Copy built src + node_modules to a new layer to prune unnecessary fs # Previous layer weights 7.25GB, while this final 488MB (as of Oct 2020) -FROM node:22-slim +FROM node:22.4-slim WORKDIR /usr/app COPY --from=build_deps /usr/app . From 879f99bf87b7826bad8c2530a42f5ed16cf340b7 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Fri, 13 Dec 2024 21:29:10 +0100 Subject: [PATCH 30/43] feat: expose `DOMAIN_APPLICATION_MASK` in config/spec api (#7296) * feat: expose DOMAIN_APPLICATION_MASK in config/spec api * Lint --- packages/beacon-node/src/api/impl/config/constants.ts | 2 ++ packages/beacon-node/test/e2e/api/impl/config.test.ts | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/src/api/impl/config/constants.ts b/packages/beacon-node/src/api/impl/config/constants.ts index c55cfe7008f4..6b390727cec3 100644 --- a/packages/beacon-node/src/api/impl/config/constants.ts +++ b/packages/beacon-node/src/api/impl/config/constants.ts @@ -7,6 +7,7 @@ import { DEPOSIT_CONTRACT_TREE_DEPTH, DOMAIN_AGGREGATE_AND_PROOF, DOMAIN_APPLICATION_BUILDER, + DOMAIN_APPLICATION_MASK, DOMAIN_BEACON_ATTESTER, DOMAIN_BEACON_PROPOSER, DOMAIN_BLS_TO_EXECUTION_CHANGE, @@ -67,6 +68,7 @@ export const specConstants = { DOMAIN_VOLUNTARY_EXIT, DOMAIN_SELECTION_PROOF, DOMAIN_AGGREGATE_AND_PROOF, + DOMAIN_APPLICATION_MASK, DOMAIN_APPLICATION_BUILDER, // phase0/validator.md diff --git a/packages/beacon-node/test/e2e/api/impl/config.test.ts b/packages/beacon-node/test/e2e/api/impl/config.test.ts index a878d41a36f0..474826050a3a 100644 --- a/packages/beacon-node/test/e2e/api/impl/config.test.ts +++ b/packages/beacon-node/test/e2e/api/impl/config.test.ts @@ -60,7 +60,7 @@ async function downloadRemoteConstants(commit: string): Promise { const constantNames: string[] = []; for (const spec of await Promise.all(downloadedSpecs)) { - const matches = spec.matchAll(/\|\s`*([A-Z_]+)`\s\|/g); + const matches = spec.matchAll(/\|\s`*([A-Z_]+)`\s+\|/g); for (const match of matches) { constantNames.push(match[1]); } From 30c669b1f58d029e4ff821264796f041a45a55d9 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Fri, 13 Dec 2024 12:51:29 -0800 Subject: [PATCH 31/43] feat: make `MAX_REQUEST_BLOB_SIDECARS` and `MAX_BLOBS_PER_BLOCK` configurable (#7294) * Init commit * Fix check-types * Add comment on how MAX_REQUEST_BLOB_SIDECARS is calculated * Ensure proper config object is passed * Address comment --------- Co-authored-by: Nico Flaig --- .../src/network/core/networkCore.ts | 16 +++++----- .../src/network/gossip/gossipsub.ts | 2 +- .../beacon-node/src/network/gossip/topic.ts | 6 ++-- packages/beacon-node/src/network/interface.ts | 3 +- packages/beacon-node/src/network/network.ts | 12 ++++---- .../src/network/reqresp/ReqRespBeaconNode.ts | 6 ++-- .../reqresp/handlers/blobSidecarsByRoot.ts | 5 ++-- .../src/network/reqresp/handlers/index.ts | 3 +- .../src/network/reqresp/protocols.ts | 8 ++--- .../src/network/reqresp/rateLimit.ts | 29 +++++++++---------- .../beacon-node/src/network/reqresp/types.ts | 12 ++++---- packages/beacon-node/src/util/types.ts | 7 ++++- .../config/src/chainConfig/configs/mainnet.ts | 3 ++ .../config/src/chainConfig/configs/minimal.ts | 3 ++ packages/config/src/chainConfig/types.ts | 4 +++ packages/params/src/index.ts | 2 -- packages/params/src/presets/mainnet.ts | 1 - packages/params/src/presets/minimal.ts | 1 - packages/params/src/types.ts | 2 -- .../test/e2e/ensure-config-is-synced.test.ts | 3 +- .../src/block/processExecutionPayload.ts | 6 ++-- packages/types/src/deneb/sszTypes.ts | 3 -- packages/types/src/deneb/types.ts | 1 - packages/validator/src/util/params.ts | 3 +- 24 files changed, 74 insertions(+), 67 deletions(-) diff --git a/packages/beacon-node/src/network/core/networkCore.ts b/packages/beacon-node/src/network/core/networkCore.ts index 718b7c92df7f..beda73b62a94 100644 --- a/packages/beacon-node/src/network/core/networkCore.ts +++ b/packages/beacon-node/src/network/core/networkCore.ts @@ -316,7 +316,7 @@ export class NetworkCore implements INetworkCore { } for (const fork of getActiveForks(this.config, this.clock.currentEpoch)) { - this.subscribeCoreTopicsAtFork(fork); + this.subscribeCoreTopicsAtFork(this.config, fork); } } @@ -325,7 +325,7 @@ export class NetworkCore implements INetworkCore { */ async unsubscribeGossipCoreTopics(): Promise { for (const fork of this.subscribedForks.values()) { - this.unsubscribeCoreTopicsAtFork(fork); + this.unsubscribeCoreTopicsAtFork(this.config, fork); } } @@ -456,7 +456,7 @@ export class NetworkCore implements INetworkCore { if (epoch === forkEpoch - FORK_EPOCH_LOOKAHEAD) { // Don't subscribe to new fork if the node is not subscribed to any topic if (await this.isSubscribedToGossipCoreTopics()) { - this.subscribeCoreTopicsAtFork(nextFork); + this.subscribeCoreTopicsAtFork(this.config, nextFork); this.logger.info("Subscribing gossip topics before fork", {nextFork}); } else { this.logger.info("Skipping subscribing gossip topics before fork", {nextFork}); @@ -475,7 +475,7 @@ export class NetworkCore implements INetworkCore { // After fork transition if (epoch === forkEpoch + FORK_EPOCH_LOOKAHEAD) { this.logger.info("Unsubscribing gossip topics from prev fork", {prevFork}); - this.unsubscribeCoreTopicsAtFork(prevFork); + this.unsubscribeCoreTopicsAtFork(this.config, prevFork); this.attnetsService.unsubscribeSubnetsFromPrevFork(prevFork); this.syncnetsService.unsubscribeSubnetsFromPrevFork(prevFork); } @@ -501,12 +501,12 @@ export class NetworkCore implements INetworkCore { } }; - private subscribeCoreTopicsAtFork(fork: ForkName): void { + private subscribeCoreTopicsAtFork(config: BeaconConfig, fork: ForkName): void { if (this.subscribedForks.has(fork)) return; this.subscribedForks.add(fork); const {subscribeAllSubnets, disableLightClientServer} = this.opts; - for (const topic of getCoreTopicsAtFork(fork, { + for (const topic of getCoreTopicsAtFork(config, fork, { subscribeAllSubnets, disableLightClientServer, })) { @@ -514,12 +514,12 @@ export class NetworkCore implements INetworkCore { } } - private unsubscribeCoreTopicsAtFork(fork: ForkName): void { + private unsubscribeCoreTopicsAtFork(config: BeaconConfig, fork: ForkName): void { if (!this.subscribedForks.has(fork)) return; this.subscribedForks.delete(fork); const {subscribeAllSubnets, disableLightClientServer} = this.opts; - for (const topic of getCoreTopicsAtFork(fork, { + for (const topic of getCoreTopicsAtFork(config, fork, { subscribeAllSubnets, disableLightClientServer, })) { diff --git a/packages/beacon-node/src/network/gossip/gossipsub.ts b/packages/beacon-node/src/network/gossip/gossipsub.ts index 42f8ba8b114c..9f2b47e21c6f 100644 --- a/packages/beacon-node/src/network/gossip/gossipsub.ts +++ b/packages/beacon-node/src/network/gossip/gossipsub.ts @@ -329,7 +329,7 @@ function getMetricsTopicStrToLabel(config: BeaconConfig, opts: {disableLightClie const metricsTopicStrToLabel = new Map(); for (const {name: fork} of config.forksAscendingEpochOrder) { - const topics = getCoreTopicsAtFork(fork, { + const topics = getCoreTopicsAtFork(config, fork, { subscribeAllSubnets: true, disableLightClientServer: opts.disableLightClientServer, }); diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index de52860605a9..1c34440df2b8 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -1,9 +1,8 @@ -import {ForkDigestContext} from "@lodestar/config"; +import {ChainConfig, ForkDigestContext} from "@lodestar/config"; import { ATTESTATION_SUBNET_COUNT, ForkName, ForkSeq, - MAX_BLOBS_PER_BLOCK, SYNC_COMMITTEE_SUBNET_COUNT, isForkLightClient, } from "@lodestar/params"; @@ -199,6 +198,7 @@ export function parseGossipTopic(forkDigestContext: ForkDigestContext, topicStr: * De-duplicate logic to pick fork topics between subscribeCoreTopicsAtFork and unsubscribeCoreTopicsAtFork */ export function getCoreTopicsAtFork( + config: ChainConfig, fork: ForkName, opts: {subscribeAllSubnets?: boolean; disableLightClientServer?: boolean} ): GossipTopicTypeMap[keyof GossipTopicTypeMap][] { @@ -213,7 +213,7 @@ export function getCoreTopicsAtFork( // After Deneb also track blob_sidecar_{index} if (ForkSeq[fork] >= ForkSeq.deneb) { - for (let index = 0; index < MAX_BLOBS_PER_BLOCK; index++) { + for (let index = 0; index < config.MAX_BLOBS_PER_BLOCK; index++) { topics.push({type: GossipType.blob_sidecar, index}); } } diff --git a/packages/beacon-node/src/network/interface.ts b/packages/beacon-node/src/network/interface.ts index bf117cc8a743..edcf35878420 100644 --- a/packages/beacon-node/src/network/interface.ts +++ b/packages/beacon-node/src/network/interface.ts @@ -30,6 +30,7 @@ import { import type {Datastore} from "interface-datastore"; import {Libp2p as ILibp2p} from "libp2p"; import {PeerIdStr} from "../util/peerId.js"; +import {BlobSidecarsByRootRequest} from "../util/types.js"; import {INetworkCorePublic} from "./core/types.js"; import {INetworkEventBus} from "./events.js"; import {GossipType} from "./gossip/interface.js"; @@ -66,7 +67,7 @@ export interface INetwork extends INetworkCorePublic { request: phase0.BeaconBlocksByRootRequest ): Promise[]>; sendBlobSidecarsByRange(peerId: PeerIdStr, request: deneb.BlobSidecarsByRangeRequest): Promise; - sendBlobSidecarsByRoot(peerId: PeerIdStr, request: deneb.BlobSidecarsByRootRequest): Promise; + sendBlobSidecarsByRoot(peerId: PeerIdStr, request: BlobSidecarsByRootRequest): Promise; // Gossip publishBeaconBlock(signedBlock: SignedBeaconBlock): Promise; diff --git a/packages/beacon-node/src/network/network.ts b/packages/beacon-node/src/network/network.ts index 2181e21744da..870fbf303ff4 100644 --- a/packages/beacon-node/src/network/network.ts +++ b/packages/beacon-node/src/network/network.ts @@ -4,7 +4,7 @@ import {PeerId} from "@libp2p/interface"; import {routes} from "@lodestar/api"; import {BeaconConfig} from "@lodestar/config"; import {LoggerNode} from "@lodestar/logger/node"; -import {ForkSeq, MAX_BLOBS_PER_BLOCK} from "@lodestar/params"; +import {ForkSeq} from "@lodestar/params"; import {ResponseIncoming} from "@lodestar/reqresp"; import {computeStartSlotAtEpoch, computeTimeAtSlot} from "@lodestar/state-transition"; import { @@ -28,6 +28,7 @@ import {IBeaconDb} from "../db/interface.js"; import {Metrics, RegistryMetricCreator} from "../metrics/index.js"; import {IClock} from "../util/clock.js"; import {PeerIdStr, peerIdToString} from "../util/peerId.js"; +import {BlobSidecarsByRootRequest} from "../util/types.js"; import {INetworkCore, NetworkCore, WorkerNetworkCore} from "./core/index.js"; import {INetworkEventBus, NetworkEvent, NetworkEventBus, NetworkEventData} from "./events.js"; import {getActiveForks} from "./forks.js"; @@ -502,15 +503,12 @@ export class Network implements INetwork { return collectMaxResponseTyped( this.sendReqRespRequest(peerId, ReqRespMethod.BlobSidecarsByRange, [Version.V1], request), // request's count represent the slots, so the actual max count received could be slots * blobs per slot - request.count * MAX_BLOBS_PER_BLOCK, + request.count * this.config.MAX_BLOBS_PER_BLOCK, responseSszTypeByMethod[ReqRespMethod.BlobSidecarsByRange] ); } - async sendBlobSidecarsByRoot( - peerId: PeerIdStr, - request: deneb.BlobSidecarsByRootRequest - ): Promise { + async sendBlobSidecarsByRoot(peerId: PeerIdStr, request: BlobSidecarsByRootRequest): Promise { return collectMaxResponseTyped( this.sendReqRespRequest(peerId, ReqRespMethod.BlobSidecarsByRoot, [Version.V1], request), request.length, @@ -524,7 +522,7 @@ export class Network implements INetwork { versions: number[], request: Req ): AsyncIterable { - const requestType = requestSszTypeByMethod[method]; + const requestType = requestSszTypeByMethod(this.config)[method]; const requestData = requestType ? requestType.serialize(request as never) : new Uint8Array(); // ReqResp outgoing request, emit from main thread to worker diff --git a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts index a2a2ebd657ab..96b5c6c0a776 100644 --- a/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts +++ b/packages/beacon-node/src/network/reqresp/ReqRespBeaconNode.ts @@ -156,7 +156,9 @@ export class ReqRespBeaconNode extends ReqResp { // Overwrite placeholder requestData from main thread with correct sequenceNumber if (method === ReqRespMethod.Ping) { - requestData = requestSszTypeByMethod[ReqRespMethod.Ping].serialize(this.metadataController.seqNumber); + requestData = requestSszTypeByMethod(this.config)[ReqRespMethod.Ping].serialize( + this.metadataController.seqNumber + ); } // ReqResp outgoing request, emit from main thread to worker @@ -205,7 +207,7 @@ export class ReqRespBeaconNode extends ReqResp { versions: number[], request: Req ): AsyncIterable { - const requestType = requestSszTypeByMethod[method]; + const requestType = requestSszTypeByMethod(this.config)[method]; const requestData = requestType ? requestType.serialize(request as never) : new Uint8Array(); return this.sendRequestWithoutEncoding(peerId, method, versions, requestData); } diff --git a/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRoot.ts b/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRoot.ts index f44f9482eeb6..951a39cff564 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRoot.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/blobSidecarsByRoot.ts @@ -1,13 +1,14 @@ import {BLOBSIDECAR_FIXED_SIZE} from "@lodestar/params"; import {RespStatus, ResponseError, ResponseOutgoing} from "@lodestar/reqresp"; -import {RootHex, deneb} from "@lodestar/types"; +import {RootHex} from "@lodestar/types"; import {fromHex, toRootHex} from "@lodestar/utils"; import {IBeaconChain} from "../../../chain/index.js"; import {IBeaconDb} from "../../../db/index.js"; import {BLOB_SIDECARS_IN_WRAPPER_INDEX} from "../../../db/repositories/blobSidecars.js"; +import {BlobSidecarsByRootRequest} from "../../../util/types.js"; export async function* onBlobSidecarsByRoot( - requestBody: deneb.BlobSidecarsByRootRequest, + requestBody: BlobSidecarsByRootRequest, chain: IBeaconChain, db: IBeaconDb ): AsyncIterable { diff --git a/packages/beacon-node/src/network/reqresp/handlers/index.ts b/packages/beacon-node/src/network/reqresp/handlers/index.ts index a836cbc47769..83f6620dbbd4 100644 --- a/packages/beacon-node/src/network/reqresp/handlers/index.ts +++ b/packages/beacon-node/src/network/reqresp/handlers/index.ts @@ -2,6 +2,7 @@ import {ProtocolHandler} from "@lodestar/reqresp"; import {ssz} from "@lodestar/types"; import {IBeaconChain} from "../../../chain/index.js"; import {IBeaconDb} from "../../../db/index.js"; +import {BlobSidecarsByRootRequestType} from "../../../util/types.js"; import {GetReqRespHandlerFn, ReqRespMethod} from "../types.js"; import {onBeaconBlocksByRange} from "./beaconBlocksByRange.js"; import {onBeaconBlocksByRoot} from "./beaconBlocksByRoot.js"; @@ -37,7 +38,7 @@ export function getReqRespHandlers({db, chain}: {db: IBeaconDb; chain: IBeaconCh return onBeaconBlocksByRoot(body, chain, db); }, [ReqRespMethod.BlobSidecarsByRoot]: (req) => { - const body = ssz.deneb.BlobSidecarsByRootRequest.deserialize(req.data); + const body = BlobSidecarsByRootRequestType(chain.config).deserialize(req.data); return onBlobSidecarsByRoot(body, chain, db); }, [ReqRespMethod.BlobSidecarsByRange]: (req) => { diff --git a/packages/beacon-node/src/network/reqresp/protocols.ts b/packages/beacon-node/src/network/reqresp/protocols.ts index b6b9c6c48967..b254db022101 100644 --- a/packages/beacon-node/src/network/reqresp/protocols.ts +++ b/packages/beacon-node/src/network/reqresp/protocols.ts @@ -1,4 +1,4 @@ -import {ForkDigestContext} from "@lodestar/config"; +import {BeaconConfig, ForkDigestContext} from "@lodestar/config"; import {ContextBytesFactory, ContextBytesType, Encoding} from "@lodestar/reqresp"; import {rateLimitQuotas} from "./rateLimit.js"; import {ProtocolNoHandler, ReqRespMethod, Version, requestSszTypeByMethod, responseSszTypeByMethod} from "./types.js"; @@ -100,13 +100,13 @@ type ProtocolSummary = { }; function toProtocol(protocol: ProtocolSummary) { - return (config: ForkDigestContext): ProtocolNoHandler => ({ + return (config: BeaconConfig): ProtocolNoHandler => ({ method: protocol.method, version: protocol.version, encoding: Encoding.SSZ_SNAPPY, contextBytes: toContextBytes(protocol.contextBytesType, config), - inboundRateLimits: rateLimitQuotas[protocol.method], - requestSizes: requestSszTypeByMethod[protocol.method], + inboundRateLimits: rateLimitQuotas(config)[protocol.method], + requestSizes: requestSszTypeByMethod(config)[protocol.method], responseSizes: (fork) => responseSszTypeByMethod[protocol.method](fork, protocol.version), }); } diff --git a/packages/beacon-node/src/network/reqresp/rateLimit.ts b/packages/beacon-node/src/network/reqresp/rateLimit.ts index 5830b48d2eab..771d01f6c339 100644 --- a/packages/beacon-node/src/network/reqresp/rateLimit.ts +++ b/packages/beacon-node/src/network/reqresp/rateLimit.ts @@ -1,14 +1,10 @@ -import { - MAX_BLOBS_PER_BLOCK, - MAX_REQUEST_BLOB_SIDECARS, - MAX_REQUEST_BLOCKS, - MAX_REQUEST_LIGHT_CLIENT_UPDATES, -} from "@lodestar/params"; +import {ChainConfig} from "@lodestar/config"; +import {MAX_REQUEST_BLOCKS, MAX_REQUEST_LIGHT_CLIENT_UPDATES} from "@lodestar/params"; import {InboundRateLimitQuota} from "@lodestar/reqresp"; import {ReqRespMethod, RequestBodyByMethod} from "./types.js"; import {requestSszTypeByMethod} from "./types.js"; -export const rateLimitQuotas: Record = { +export const rateLimitQuotas: (config: ChainConfig) => Record = (config) => ({ [ReqRespMethod.Status]: { // Rationale: https://github.com/sigp/lighthouse/blob/bf533c8e42cc73c35730e285c21df8add0195369/beacon_node/lighthouse_network/src/rpc/mod.rs#L118-L130 byPeer: {quota: 5, quotaTimeMs: 15_000}, @@ -29,22 +25,22 @@ export const rateLimitQuotas: Record = { [ReqRespMethod.BeaconBlocksByRange]: { // Rationale: https://github.com/sigp/lighthouse/blob/bf533c8e42cc73c35730e285c21df8add0195369/beacon_node/lighthouse_network/src/rpc/mod.rs#L118-L130 byPeer: {quota: MAX_REQUEST_BLOCKS, quotaTimeMs: 10_000}, - getRequestCount: getRequestCountFn(ReqRespMethod.BeaconBlocksByRange, (req) => req.count), + getRequestCount: getRequestCountFn(config, ReqRespMethod.BeaconBlocksByRange, (req) => req.count), }, [ReqRespMethod.BeaconBlocksByRoot]: { // Rationale: https://github.com/sigp/lighthouse/blob/bf533c8e42cc73c35730e285c21df8add0195369/beacon_node/lighthouse_network/src/rpc/mod.rs#L118-L130 byPeer: {quota: 128, quotaTimeMs: 10_000}, - getRequestCount: getRequestCountFn(ReqRespMethod.BeaconBlocksByRoot, (req) => req.length), + getRequestCount: getRequestCountFn(config, ReqRespMethod.BeaconBlocksByRoot, (req) => req.length), }, [ReqRespMethod.BlobSidecarsByRange]: { // Rationale: MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK - byPeer: {quota: MAX_REQUEST_BLOB_SIDECARS, quotaTimeMs: 10_000}, - getRequestCount: getRequestCountFn(ReqRespMethod.BlobSidecarsByRange, (req) => req.count), + byPeer: {quota: config.MAX_REQUEST_BLOB_SIDECARS, quotaTimeMs: 10_000}, + getRequestCount: getRequestCountFn(config, ReqRespMethod.BlobSidecarsByRange, (req) => req.count), }, [ReqRespMethod.BlobSidecarsByRoot]: { // Rationale: quota of BeaconBlocksByRoot * MAX_BLOBS_PER_BLOCK - byPeer: {quota: 128 * MAX_BLOBS_PER_BLOCK, quotaTimeMs: 10_000}, - getRequestCount: getRequestCountFn(ReqRespMethod.BlobSidecarsByRoot, (req) => req.length), + byPeer: {quota: config.MAX_REQUEST_BLOB_SIDECARS, quotaTimeMs: 10_000}, + getRequestCount: getRequestCountFn(config, ReqRespMethod.BlobSidecarsByRoot, (req) => req.length), }, [ReqRespMethod.LightClientBootstrap]: { // As similar in the nature of `Status` protocol so we use the same rate limits. @@ -53,7 +49,7 @@ export const rateLimitQuotas: Record = { [ReqRespMethod.LightClientUpdatesByRange]: { // Same rationale as for BeaconBlocksByRange byPeer: {quota: MAX_REQUEST_LIGHT_CLIENT_UPDATES, quotaTimeMs: 10_000}, - getRequestCount: getRequestCountFn(ReqRespMethod.LightClientUpdatesByRange, (req) => req.count), + getRequestCount: getRequestCountFn(config, ReqRespMethod.LightClientUpdatesByRange, (req) => req.count), }, [ReqRespMethod.LightClientFinalityUpdate]: { // Finality updates should not be requested more than once per epoch. @@ -65,14 +61,15 @@ export const rateLimitQuotas: Record = { // Allow 2 per slot and a very safe bound until there's more testing of real usage. byPeer: {quota: 2, quotaTimeMs: 12_000}, }, -}; +}); // Helper to produce a getRequestCount function function getRequestCountFn( + config: ChainConfig, method: T, fn: (req: RequestBodyByMethod[T]) => number ): (reqData: Uint8Array) => number { - const type = requestSszTypeByMethod[method]; + const type = requestSszTypeByMethod(config)[method]; return (reqData: Uint8Array) => { try { return (type && fn(type.deserialize(reqData))) ?? 1; diff --git a/packages/beacon-node/src/network/reqresp/types.ts b/packages/beacon-node/src/network/reqresp/types.ts index 96ae1558ec07..b7c18ebdfeb5 100644 --- a/packages/beacon-node/src/network/reqresp/types.ts +++ b/packages/beacon-node/src/network/reqresp/types.ts @@ -1,4 +1,5 @@ import {Type} from "@chainsafe/ssz"; +import {ChainConfig} from "@lodestar/config"; import {ForkLightClient, ForkName, isForkLightClient} from "@lodestar/params"; import {Protocol, ProtocolHandler, ReqRespRequest} from "@lodestar/reqresp"; import { @@ -15,6 +16,7 @@ import { ssz, sszTypesFor, } from "@lodestar/types"; +import {BlobSidecarsByRootRequest, BlobSidecarsByRootRequestType} from "../../util/types.js"; export type ProtocolNoHandler = Omit; @@ -44,7 +46,7 @@ export type RequestBodyByMethod = { [ReqRespMethod.BeaconBlocksByRange]: phase0.BeaconBlocksByRangeRequest; [ReqRespMethod.BeaconBlocksByRoot]: phase0.BeaconBlocksByRootRequest; [ReqRespMethod.BlobSidecarsByRange]: deneb.BlobSidecarsByRangeRequest; - [ReqRespMethod.BlobSidecarsByRoot]: deneb.BlobSidecarsByRootRequest; + [ReqRespMethod.BlobSidecarsByRoot]: BlobSidecarsByRootRequest; [ReqRespMethod.LightClientBootstrap]: Root; [ReqRespMethod.LightClientUpdatesByRange]: altair.LightClientUpdatesByRange; [ReqRespMethod.LightClientFinalityUpdate]: null; @@ -68,9 +70,9 @@ type ResponseBodyByMethod = { }; /** Request SSZ type for each method and ForkName */ -export const requestSszTypeByMethod: { +export const requestSszTypeByMethod: (config: ChainConfig) => { [K in ReqRespMethod]: RequestBodyByMethod[K] extends null ? null : Type; -} = { +} = (config) => ({ [ReqRespMethod.Status]: ssz.phase0.Status, [ReqRespMethod.Goodbye]: ssz.phase0.Goodbye, [ReqRespMethod.Ping]: ssz.phase0.Ping, @@ -78,12 +80,12 @@ export const requestSszTypeByMethod: { [ReqRespMethod.BeaconBlocksByRange]: ssz.phase0.BeaconBlocksByRangeRequest, [ReqRespMethod.BeaconBlocksByRoot]: ssz.phase0.BeaconBlocksByRootRequest, [ReqRespMethod.BlobSidecarsByRange]: ssz.deneb.BlobSidecarsByRangeRequest, - [ReqRespMethod.BlobSidecarsByRoot]: ssz.deneb.BlobSidecarsByRootRequest, + [ReqRespMethod.BlobSidecarsByRoot]: BlobSidecarsByRootRequestType(config), [ReqRespMethod.LightClientBootstrap]: ssz.Root, [ReqRespMethod.LightClientUpdatesByRange]: ssz.altair.LightClientUpdatesByRange, [ReqRespMethod.LightClientFinalityUpdate]: null, [ReqRespMethod.LightClientOptimisticUpdate]: null, -}; +}); export type ResponseTypeGetter = (fork: ForkName, version: number) => Type; diff --git a/packages/beacon-node/src/util/types.ts b/packages/beacon-node/src/util/types.ts index 545a706c7511..5b9c7a784277 100644 --- a/packages/beacon-node/src/util/types.ts +++ b/packages/beacon-node/src/util/types.ts @@ -1,4 +1,5 @@ -import {ContainerType, ValueOf} from "@chainsafe/ssz"; +import {ContainerType, ListCompositeType, ValueOf} from "@chainsafe/ssz"; +import {ChainConfig} from "@lodestar/config"; import {ssz} from "@lodestar/types"; // Misc SSZ types used only in the beacon-node package, no need to upstream to types @@ -12,3 +13,7 @@ export const signedBLSToExecutionChangeVersionedType = new ContainerType( {jsonCase: "eth2", typeName: "SignedBLSToExecutionChangeVersionedType"} ); export type SignedBLSToExecutionChangeVersioned = ValueOf; + +export const BlobSidecarsByRootRequestType = (config: ChainConfig) => + new ListCompositeType(ssz.deneb.BlobIdentifier, config.MAX_REQUEST_BLOB_SIDECARS); +export type BlobSidecarsByRootRequest = ValueOf>; diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index 4a01d9d062b1..ae9a2ec74d1f 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -101,6 +101,9 @@ export const chainConfig: ChainConfig = { // Deneb // `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096, + MAX_BLOBS_PER_BLOCK: 6, + // MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK + MAX_REQUEST_BLOB_SIDECARS: 768, // Electra // 2**8 * 10**9 (= 256,000,000,000) diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index f0e116a553ab..6f536bc7732c 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -98,6 +98,9 @@ export const chainConfig: ChainConfig = { // Deneb // `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096, + MAX_BLOBS_PER_BLOCK: 6, + // MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK + MAX_REQUEST_BLOB_SIDECARS: 768, // Electra // 2**7 * 10**9 (= 128,000,000,000) diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index 5c06b205c2f6..513324bdbcb8 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -72,6 +72,8 @@ export type ChainConfig = { // Networking MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: number; + MAX_BLOBS_PER_BLOCK: number; + MAX_REQUEST_BLOB_SIDECARS: number; }; export const chainConfigTypes: SpecTypes = { @@ -136,6 +138,8 @@ export const chainConfigTypes: SpecTypes = { // Networking MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: "number", + MAX_BLOBS_PER_BLOCK: "number", + MAX_REQUEST_BLOB_SIDECARS: "number", }; /** Allows values in a Spec file */ diff --git a/packages/params/src/index.ts b/packages/params/src/index.ts index 82ffa491daa3..64d3b64dbd60 100644 --- a/packages/params/src/index.ts +++ b/packages/params/src/index.ts @@ -91,7 +91,6 @@ export const { FIELD_ELEMENTS_PER_BLOB, MAX_BLOB_COMMITMENTS_PER_BLOCK, - MAX_BLOBS_PER_BLOCK, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, MAX_EFFECTIVE_BALANCE_ELECTRA, @@ -198,7 +197,6 @@ export const SYNC_COMMITTEE_SUBNET_SIZE = Math.floor(SYNC_COMMITTEE_SIZE / SYNC_ export const MAX_REQUEST_BLOCKS = 2 ** 10; // 1024 export const MAX_REQUEST_BLOCKS_DENEB = 2 ** 7; // 128 -export const MAX_REQUEST_BLOB_SIDECARS = MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK; // Lightclient pre-computed /** diff --git a/packages/params/src/presets/mainnet.ts b/packages/params/src/presets/mainnet.ts index 9a03001375f2..afbfd78eba95 100644 --- a/packages/params/src/presets/mainnet.ts +++ b/packages/params/src/presets/mainnet.ts @@ -115,7 +115,6 @@ export const mainnetPreset: BeaconPreset = { /////////// FIELD_ELEMENTS_PER_BLOB: 4096, MAX_BLOB_COMMITMENTS_PER_BLOCK: 4096, - MAX_BLOBS_PER_BLOCK: 6, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 17, // ELECTRA diff --git a/packages/params/src/presets/minimal.ts b/packages/params/src/presets/minimal.ts index 6edd7e2858f1..d9be1b1468ab 100644 --- a/packages/params/src/presets/minimal.ts +++ b/packages/params/src/presets/minimal.ts @@ -116,7 +116,6 @@ export const minimalPreset: BeaconPreset = { /////////// FIELD_ELEMENTS_PER_BLOB: 4096, MAX_BLOB_COMMITMENTS_PER_BLOCK: 16, - MAX_BLOBS_PER_BLOCK: 6, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: 9, // ELECTRA diff --git a/packages/params/src/types.ts b/packages/params/src/types.ts index 5e17adaace12..e641e0f05286 100644 --- a/packages/params/src/types.ts +++ b/packages/params/src/types.ts @@ -78,7 +78,6 @@ export type BeaconPreset = { /////////// FIELD_ELEMENTS_PER_BLOB: number; MAX_BLOB_COMMITMENTS_PER_BLOCK: number; - MAX_BLOBS_PER_BLOCK: number; KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: number; // ELECTRA @@ -179,7 +178,6 @@ export const beaconPresetTypes: BeaconPresetTypes = { /////////// FIELD_ELEMENTS_PER_BLOB: "number", MAX_BLOB_COMMITMENTS_PER_BLOCK: "number", - MAX_BLOBS_PER_BLOCK: "number", KZG_COMMITMENT_INCLUSION_PROOF_DEPTH: "number", // ELECTRA diff --git a/packages/params/test/e2e/ensure-config-is-synced.test.ts b/packages/params/test/e2e/ensure-config-is-synced.test.ts index ab34c16201c8..b1d9c05a54b1 100644 --- a/packages/params/test/e2e/ensure-config-is-synced.test.ts +++ b/packages/params/test/e2e/ensure-config-is-synced.test.ts @@ -12,9 +12,8 @@ const specConfigCommit = "v1.5.0-alpha.8"; /** * Fields that we filter from local config when doing comparison. * Ideally this should be empty as it is not spec compliant - * For `MAX_BLOBS_PER_BLOCK`, see https://github.com/ChainSafe/lodestar/issues/7172 */ -const ignoredLocalPresetFields: (keyof BeaconPreset)[] = ["MAX_BLOBS_PER_BLOCK"]; +const ignoredLocalPresetFields: (keyof BeaconPreset)[] = []; describe("Ensure config is synced", () => { vi.setConfig({testTimeout: 60 * 1000}); diff --git a/packages/state-transition/src/block/processExecutionPayload.ts b/packages/state-transition/src/block/processExecutionPayload.ts index 3900583956ba..0ea2fc7a16f7 100644 --- a/packages/state-transition/src/block/processExecutionPayload.ts +++ b/packages/state-transition/src/block/processExecutionPayload.ts @@ -1,5 +1,5 @@ import {byteArrayEquals} from "@chainsafe/ssz"; -import {ForkSeq, MAX_BLOBS_PER_BLOCK} from "@lodestar/params"; +import {ForkSeq} from "@lodestar/params"; import {BeaconBlockBody, BlindedBeaconBlockBody, deneb, isExecutionPayload} from "@lodestar/types"; import {toHex, toRootHex} from "@lodestar/utils"; import {CachedBeaconStateBellatrix, CachedBeaconStateCapella} from "../types.js"; @@ -49,8 +49,8 @@ export function processExecutionPayload( if (fork >= ForkSeq.deneb) { const blobKzgCommitmentsLen = (body as deneb.BeaconBlockBody).blobKzgCommitments?.length ?? 0; - if (blobKzgCommitmentsLen > MAX_BLOBS_PER_BLOCK) { - throw Error(`blobKzgCommitmentsLen exceeds limit=${MAX_BLOBS_PER_BLOCK}`); + if (blobKzgCommitmentsLen > state.config.MAX_BLOBS_PER_BLOCK) { + throw Error(`blobKzgCommitmentsLen exceeds limit=${state.config.MAX_BLOBS_PER_BLOCK}`); } } diff --git a/packages/types/src/deneb/sszTypes.ts b/packages/types/src/deneb/sszTypes.ts index 6dc18c8f8b02..9275ac4c4da5 100644 --- a/packages/types/src/deneb/sszTypes.ts +++ b/packages/types/src/deneb/sszTypes.ts @@ -6,7 +6,6 @@ import { HISTORICAL_ROOTS_LIMIT, KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, MAX_BLOB_COMMITMENTS_PER_BLOCK, - MAX_REQUEST_BLOB_SIDECARS, SLOTS_PER_EPOCH, } from "@lodestar/params"; import {ssz as altairSsz} from "../altair/index.js"; @@ -62,8 +61,6 @@ export const BlobIdentifier = new ContainerType( {typeName: "BlobIdentifier", jsonCase: "eth2"} ); -export const BlobSidecarsByRootRequest = new ListCompositeType(BlobIdentifier, MAX_REQUEST_BLOB_SIDECARS); - // Beacon Chain types // https://github.com/ethereum/consensus-specs/blob/dev/specs/eip4844/beacon-chain.md#containers diff --git a/packages/types/src/deneb/types.ts b/packages/types/src/deneb/types.ts index 1bbabd0e4285..c1c973d4d5be 100644 --- a/packages/types/src/deneb/types.ts +++ b/packages/types/src/deneb/types.ts @@ -18,7 +18,6 @@ export type BLSFieldElement = ValueOf; export type BlobIdentifier = ValueOf; export type BlobSidecarsByRangeRequest = ValueOf; -export type BlobSidecarsByRootRequest = ValueOf; export type ExecutionPayload = ValueOf; export type ExecutionPayloadHeader = ValueOf; diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index 53ccd759a5c1..e8fd1a1e6dc4 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -136,6 +136,7 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Sat, 14 Dec 2024 01:23:31 +0100 Subject: [PATCH 32/43] feat: use `BLOB_SIDECAR_SUBNET_COUNT` to configure blob subnets (#7297) feat: use BLOB_SIDECAR_SUBNET_COUNT to configure blob subnets --- .../src/chain/errors/blobSidecarError.ts | 2 +- .../src/chain/validation/blobSidecar.ts | 15 ++++++++++----- .../beacon-node/src/network/gossip/interface.ts | 2 +- packages/beacon-node/src/network/gossip/topic.ts | 16 ++++++++-------- packages/beacon-node/src/network/network.ts | 4 ++-- .../src/network/processor/gossipHandlers.ts | 10 +++++----- .../beacon-node/test/e2e/api/impl/config.test.ts | 2 -- .../test/unit/network/gossip/topic.test.ts | 2 +- .../config/src/chainConfig/configs/mainnet.ts | 1 + .../config/src/chainConfig/configs/minimal.ts | 1 + packages/config/src/chainConfig/types.ts | 2 ++ packages/validator/src/util/params.ts | 1 + 12 files changed, 33 insertions(+), 25 deletions(-) diff --git a/packages/beacon-node/src/chain/errors/blobSidecarError.ts b/packages/beacon-node/src/chain/errors/blobSidecarError.ts index 410574594911..71118d8b8f9d 100644 --- a/packages/beacon-node/src/chain/errors/blobSidecarError.ts +++ b/packages/beacon-node/src/chain/errors/blobSidecarError.ts @@ -26,7 +26,7 @@ export enum BlobSidecarErrorCode { } export type BlobSidecarErrorType = - | {code: BlobSidecarErrorCode.INVALID_INDEX; blobIdx: number; gossipIndex: number} + | {code: BlobSidecarErrorCode.INVALID_INDEX; blobIdx: number; subnet: number} | {code: BlobSidecarErrorCode.INVALID_KZG; blobIdx: number} | {code: BlobSidecarErrorCode.INVALID_KZG_TXS} | {code: BlobSidecarErrorCode.INCORRECT_SLOT; blockSlot: Slot; blobSlot: Slot; blobIdx: number} diff --git a/packages/beacon-node/src/chain/validation/blobSidecar.ts b/packages/beacon-node/src/chain/validation/blobSidecar.ts index 3b90972f62b1..e2f4ed267d01 100644 --- a/packages/beacon-node/src/chain/validation/blobSidecar.ts +++ b/packages/beacon-node/src/chain/validation/blobSidecar.ts @@ -1,6 +1,7 @@ +import {ChainConfig} from "@lodestar/config"; import {KZG_COMMITMENT_INCLUSION_PROOF_DEPTH, KZG_COMMITMENT_SUBTREE_INDEX0} from "@lodestar/params"; import {computeStartSlotAtEpoch, getBlockHeaderProposerSignatureSet} from "@lodestar/state-transition"; -import {Root, Slot, deneb, ssz} from "@lodestar/types"; +import {BlobIndex, Root, Slot, deneb, ssz} from "@lodestar/types"; import {toRootHex, verifyMerkleBranch} from "@lodestar/utils"; import {byteArrayEquals} from "../../util/bytes.js"; @@ -13,16 +14,16 @@ import {RegenCaller} from "../regen/index.js"; export async function validateGossipBlobSidecar( chain: IBeaconChain, blobSidecar: deneb.BlobSidecar, - gossipIndex: number + subnet: number ): Promise { const blobSlot = blobSidecar.signedBlockHeader.message.slot; - // [REJECT] The sidecar is for the correct topic -- i.e. sidecar.index matches the topic {index}. - if (blobSidecar.index !== gossipIndex) { + // [REJECT] The sidecar is for the correct subnet -- i.e. `compute_subnet_for_blob_sidecar(sidecar.index) == subnet_id`. + if (computeSubnetForBlobSidecar(blobSidecar.index, chain.config) !== subnet) { throw new BlobSidecarGossipError(GossipAction.REJECT, { code: BlobSidecarErrorCode.INVALID_INDEX, blobIdx: blobSidecar.index, - gossipIndex, + subnet, }); } @@ -225,3 +226,7 @@ function validateInclusionProof(blobSidecar: deneb.BlobSidecar): boolean { blobSidecar.signedBlockHeader.message.bodyRoot ); } + +function computeSubnetForBlobSidecar(blobIndex: BlobIndex, config: ChainConfig): number { + return blobIndex % config.BLOB_SIDECAR_SUBNET_COUNT; +} diff --git a/packages/beacon-node/src/network/gossip/interface.ts b/packages/beacon-node/src/network/gossip/interface.ts index 9939ed5af657..be7293524ce9 100644 --- a/packages/beacon-node/src/network/gossip/interface.ts +++ b/packages/beacon-node/src/network/gossip/interface.ts @@ -54,7 +54,7 @@ export interface IGossipTopic { export type GossipTopicTypeMap = { [GossipType.beacon_block]: {type: GossipType.beacon_block}; - [GossipType.blob_sidecar]: {type: GossipType.blob_sidecar; index: number}; + [GossipType.blob_sidecar]: {type: GossipType.blob_sidecar; subnet: number}; [GossipType.beacon_aggregate_and_proof]: {type: GossipType.beacon_aggregate_and_proof}; [GossipType.beacon_attestation]: {type: GossipType.beacon_attestation; subnet: number}; [GossipType.voluntary_exit]: {type: GossipType.voluntary_exit}; diff --git a/packages/beacon-node/src/network/gossip/topic.ts b/packages/beacon-node/src/network/gossip/topic.ts index 1c34440df2b8..88ef4143f8ff 100644 --- a/packages/beacon-node/src/network/gossip/topic.ts +++ b/packages/beacon-node/src/network/gossip/topic.ts @@ -73,7 +73,7 @@ function stringifyGossipTopicType(topic: GossipTopic): string { case GossipType.sync_committee: return `${topic.type}_${topic.subnet}`; case GossipType.blob_sidecar: - return `${topic.type}_${topic.index}`; + return `${topic.type}_${topic.subnet}`; } } @@ -181,10 +181,10 @@ export function parseGossipTopic(forkDigestContext: ForkDigestContext, topicStr: } if (gossipTypeStr.startsWith(GossipType.blob_sidecar)) { - const indexStr = gossipTypeStr.slice(GossipType.blob_sidecar.length + 1); // +1 for '_' concatenating the topic name and the index - const index = parseInt(indexStr, 10); - if (Number.isNaN(index)) throw Error(`index ${indexStr} is not a number`); - return {type: GossipType.blob_sidecar, index, fork, encoding}; + const subnetStr = gossipTypeStr.slice(GossipType.blob_sidecar.length + 1); // +1 for '_' concatenating the topic name and the subnet + const subnet = parseInt(subnetStr, 10); + if (Number.isNaN(subnet)) throw Error(`subnet ${subnetStr} is not a number`); + return {type: GossipType.blob_sidecar, subnet, fork, encoding}; } throw Error(`Unknown gossip type ${gossipTypeStr}`); @@ -211,10 +211,10 @@ export function getCoreTopicsAtFork( {type: GossipType.attester_slashing}, ]; - // After Deneb also track blob_sidecar_{index} + // After Deneb also track blob_sidecar_{subnet_id} if (ForkSeq[fork] >= ForkSeq.deneb) { - for (let index = 0; index < config.MAX_BLOBS_PER_BLOCK; index++) { - topics.push({type: GossipType.blob_sidecar, index}); + for (let subnet = 0; subnet < config.BLOB_SIDECAR_SUBNET_COUNT; subnet++) { + topics.push({type: GossipType.blob_sidecar, subnet}); } } diff --git a/packages/beacon-node/src/network/network.ts b/packages/beacon-node/src/network/network.ts index 870fbf303ff4..d8370fc22b5f 100644 --- a/packages/beacon-node/src/network/network.ts +++ b/packages/beacon-node/src/network/network.ts @@ -312,9 +312,9 @@ export class Network implements INetwork { async publishBlobSidecar(blobSidecar: deneb.BlobSidecar): Promise { const slot = blobSidecar.signedBlockHeader.message.slot; const fork = this.config.getForkName(slot); - const index = blobSidecar.index; + const subnet = blobSidecar.index; - return this.publishGossip({type: GossipType.blob_sidecar, fork, index}, blobSidecar, { + return this.publishGossip({type: GossipType.blob_sidecar, fork, subnet}, blobSidecar, { ignoreDuplicatePublishError: true, }); } diff --git a/packages/beacon-node/src/network/processor/gossipHandlers.ts b/packages/beacon-node/src/network/processor/gossipHandlers.ts index 029e7ae4db42..3fb7b06e700a 100644 --- a/packages/beacon-node/src/network/processor/gossipHandlers.ts +++ b/packages/beacon-node/src/network/processor/gossipHandlers.ts @@ -179,7 +179,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand async function validateBeaconBlob( blobSidecar: deneb.BlobSidecar, blobBytes: Uint8Array, - gossipIndex: number, + subnet: number, peerIdStr: string, seenTimestampSec: number ): Promise { @@ -202,7 +202,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand ); try { - await validateGossipBlobSidecar(chain, blobSidecar, gossipIndex); + await validateGossipBlobSidecar(chain, blobSidecar, subnet); const recvToValidation = Date.now() / 1000 - seenTimestampSec; const validationTime = recvToValidation - recvToValLatency; @@ -212,10 +212,10 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand logger.debug("Received gossip blob", { slot: slot, root: blockHex, - curentSlot: chain.clock.currentSlot, + currentSlot: chain.clock.currentSlot, peerId: peerIdStr, delaySec, - gossipIndex, + subnet, ...blockInputMeta, recvToValLatency, recvToValidation, @@ -363,7 +363,7 @@ function getSequentialHandlers(modules: ValidatorFnsModules, options: GossipHand const blockInput = await validateBeaconBlob( blobSidecar, serializedData, - topic.index, + topic.subnet, peerIdStr, seenTimestampSec ); diff --git a/packages/beacon-node/test/e2e/api/impl/config.test.ts b/packages/beacon-node/test/e2e/api/impl/config.test.ts index 474826050a3a..078e210cb0cc 100644 --- a/packages/beacon-node/test/e2e/api/impl/config.test.ts +++ b/packages/beacon-node/test/e2e/api/impl/config.test.ts @@ -10,8 +10,6 @@ const CONSTANT_NAMES_SKIP_LIST = new Set([ // This constant can also be derived from existing constants so it's not critical. // PARTICIPATION_FLAG_WEIGHTS = [TIMELY_SOURCE_WEIGHT, TIMELY_TARGET_WEIGHT, TIMELY_HEAD_WEIGHT] "PARTICIPATION_FLAG_WEIGHTS", - // TODO DENEB: Configure the blob subnets in a followup PR - "BLOB_SIDECAR_SUBNET_COUNT", ]); describe("api / impl / config", () => { diff --git a/packages/beacon-node/test/unit/network/gossip/topic.test.ts b/packages/beacon-node/test/unit/network/gossip/topic.test.ts index f68c2d737fd6..4b323865061e 100644 --- a/packages/beacon-node/test/unit/network/gossip/topic.test.ts +++ b/packages/beacon-node/test/unit/network/gossip/topic.test.ts @@ -17,7 +17,7 @@ describe("network / gossip / topic", () => { ], [GossipType.blob_sidecar]: [ { - topic: {type: GossipType.blob_sidecar, index: 1, fork: ForkName.deneb, encoding}, + topic: {type: GossipType.blob_sidecar, subnet: 1, fork: ForkName.deneb, encoding}, topicStr: "/eth2/46acb19a/blob_sidecar_1/ssz_snappy", }, ], diff --git a/packages/config/src/chainConfig/configs/mainnet.ts b/packages/config/src/chainConfig/configs/mainnet.ts index ae9a2ec74d1f..a4adc04a1756 100644 --- a/packages/config/src/chainConfig/configs/mainnet.ts +++ b/packages/config/src/chainConfig/configs/mainnet.ts @@ -101,6 +101,7 @@ export const chainConfig: ChainConfig = { // Deneb // `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096, + BLOB_SIDECAR_SUBNET_COUNT: 6, MAX_BLOBS_PER_BLOCK: 6, // MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK MAX_REQUEST_BLOB_SIDECARS: 768, diff --git a/packages/config/src/chainConfig/configs/minimal.ts b/packages/config/src/chainConfig/configs/minimal.ts index 6f536bc7732c..d16b03e82c28 100644 --- a/packages/config/src/chainConfig/configs/minimal.ts +++ b/packages/config/src/chainConfig/configs/minimal.ts @@ -98,6 +98,7 @@ export const chainConfig: ChainConfig = { // Deneb // `2**12` (= 4096 epochs, ~18 days) MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: 4096, + BLOB_SIDECAR_SUBNET_COUNT: 6, MAX_BLOBS_PER_BLOCK: 6, // MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK MAX_REQUEST_BLOB_SIDECARS: 768, diff --git a/packages/config/src/chainConfig/types.ts b/packages/config/src/chainConfig/types.ts index 513324bdbcb8..291dcc8601a7 100644 --- a/packages/config/src/chainConfig/types.ts +++ b/packages/config/src/chainConfig/types.ts @@ -72,6 +72,7 @@ export type ChainConfig = { // Networking MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: number; + BLOB_SIDECAR_SUBNET_COUNT: number; MAX_BLOBS_PER_BLOCK: number; MAX_REQUEST_BLOB_SIDECARS: number; }; @@ -138,6 +139,7 @@ export const chainConfigTypes: SpecTypes = { // Networking MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS: "number", + BLOB_SIDECAR_SUBNET_COUNT: "number", MAX_BLOBS_PER_BLOCK: "number", MAX_REQUEST_BLOB_SIDECARS: "number", }; diff --git a/packages/validator/src/util/params.ts b/packages/validator/src/util/params.ts index e8fd1a1e6dc4..825f60e8c7fa 100644 --- a/packages/validator/src/util/params.ts +++ b/packages/validator/src/util/params.ts @@ -136,6 +136,7 @@ function getSpecCriticalParams(localConfig: ChainConfig): Record Date: Sun, 15 Dec 2024 12:34:56 +0100 Subject: [PATCH 33/43] chore: log sync aggregate participants when producing beacon block body (#7300) * chore: log sync aggregate participants when producing beacon block body * Use isForkLightClient instead of ForkSeq * Fix produce block unit tests --- .../chain/produceBlock/produceBlockBody.ts | 23 ++++++++++++++----- .../test/mocks/mockedBeaconChain.ts | 11 ++++++++- .../api/impl/validator/produceBlockV2.test.ts | 6 ++++- 3 files changed, 32 insertions(+), 8 deletions(-) diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index d26703050070..e4c86b78907c 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -1,11 +1,10 @@ import {ChainForkConfig} from "@lodestar/config"; -import {ForkExecution, ForkSeq, isForkExecution} from "@lodestar/params"; +import {ForkExecution, ForkSeq, isForkExecution, isForkLightClient} from "@lodestar/params"; import { CachedBeaconStateAllForks, CachedBeaconStateBellatrix, CachedBeaconStateCapella, CachedBeaconStateExecutions, - computeEpochAtSlot, computeTimeAtSlot, getCurrentEpoch, getExpectedWithdrawals, @@ -143,8 +142,15 @@ export async function produceBlockBody( ? Object.assign({}, commonBlockBody) : await produceCommonBlockBody.call(this, blockType, currentState, blockAttr); - const {attestations, deposits, voluntaryExits, attesterSlashings, proposerSlashings, blsToExecutionChanges} = - blockBody; + const { + attestations, + deposits, + voluntaryExits, + attesterSlashings, + proposerSlashings, + syncAggregate, + blsToExecutionChanges, + } = blockBody; Object.assign(logMeta, { attestations: attestations.length, @@ -154,6 +160,12 @@ export async function produceBlockBody( proposerSlashings: proposerSlashings.length, }); + if (isForkLightClient(fork)) { + Object.assign(logMeta, { + syncAggregateParticipants: syncAggregate.syncCommitteeBits.getTrueBitIndexes().length, + }); + } + const endExecutionPayload = stepsMetrics?.startTimer(); if (isForkExecution(fork)) { const safeBlockHash = this.forkChoice.getJustifiedBlock().executionPayloadBlockHash ?? ZERO_HASH_HEX; @@ -608,7 +620,6 @@ export async function produceCommonBlockBody( ? this.metrics?.executionBlockProductionTimeSteps : this.metrics?.builderBlockProductionTimeSteps; - const blockEpoch = computeEpochAtSlot(slot); const fork = currentState.config.getForkName(slot); // TODO: @@ -653,7 +664,7 @@ export async function produceCommonBlockBody( } const endSyncAggregate = stepsMetrics?.startTimer(); - if (blockEpoch >= this.config.ALTAIR_FORK_EPOCH) { + if (ForkSeq[fork] >= ForkSeq.altair) { const syncAggregate = this.syncContributionAndProofPool.getAggregate(parentSlot, parentBlockRoot); this.metrics?.production.producedSyncAggregateParticipants.observe( syncAggregate.syncCommitteeBits.getTrueBitIndexes().length diff --git a/packages/beacon-node/test/mocks/mockedBeaconChain.ts b/packages/beacon-node/test/mocks/mockedBeaconChain.ts index b274284e3ab1..36d6fb27c53a 100644 --- a/packages/beacon-node/test/mocks/mockedBeaconChain.ts +++ b/packages/beacon-node/test/mocks/mockedBeaconChain.ts @@ -8,7 +8,7 @@ import {BeaconProposerCache} from "../../src/chain/beaconProposerCache.js"; import {BeaconChain} from "../../src/chain/chain.js"; import {ChainEventEmitter} from "../../src/chain/emitter.js"; import {LightClientServer} from "../../src/chain/lightClient/index.js"; -import {AggregatedAttestationPool, OpPool} from "../../src/chain/opPools/index.js"; +import {AggregatedAttestationPool, OpPool, SyncContributionAndProofPool} from "../../src/chain/opPools/index.js"; import {QueuedStateRegenerator} from "../../src/chain/regen/index.js"; import {ShufflingCache} from "../../src/chain/shufflingCache.js"; import {Eth1ForBlockProduction} from "../../src/eth1/index.js"; @@ -27,6 +27,7 @@ export type MockedBeaconChain = Mocked & { eth1: Mocked; opPool: Mocked; aggregatedAttestationPool: Mocked; + syncContributionAndProofPool: Mocked; beaconProposerCache: Mocked; shufflingCache: Mocked; regen: Mocked; @@ -94,10 +95,17 @@ vi.mock("../../src/chain/opPools/index.js", async (importActual) => { }; }); + const SyncContributionAndProofPool = vi.fn().mockImplementation(() => { + return { + getAggregate: vi.fn(), + }; + }); + return { ...mod, OpPool, AggregatedAttestationPool, + SyncContributionAndProofPool, }; }); @@ -124,6 +132,7 @@ vi.mock("../../src/chain/chain.js", async (importActual) => { eth1: new Eth1ForBlockProduction(), opPool: new OpPool(), aggregatedAttestationPool: new AggregatedAttestationPool(config), + syncContributionAndProofPool: new SyncContributionAndProofPool(), // @ts-expect-error beaconProposerCache: new BeaconProposerCache(), shufflingCache: new ShufflingCache(), diff --git a/packages/beacon-node/test/unit/api/impl/validator/produceBlockV2.test.ts b/packages/beacon-node/test/unit/api/impl/validator/produceBlockV2.test.ts index 52f600b7174c..418dd2e56d8d 100644 --- a/packages/beacon-node/test/unit/api/impl/validator/produceBlockV2.test.ts +++ b/packages/beacon-node/test/unit/api/impl/validator/produceBlockV2.test.ts @@ -1,7 +1,7 @@ import {fromHexString, toHexString} from "@chainsafe/ssz"; import {ProtoBlock} from "@lodestar/fork-choice"; import {ForkName} from "@lodestar/params"; -import {CachedBeaconStateBellatrix, computeTimeAtSlot} from "@lodestar/state-transition"; +import {CachedBeaconStateBellatrix, G2_POINT_AT_INFINITY, computeTimeAtSlot} from "@lodestar/state-transition"; import {ssz} from "@lodestar/types"; import {afterEach, beforeEach, describe, expect, it, vi} from "vitest"; import {getValidatorApi} from "../../../../../src/api/impl/validator/index.js"; @@ -99,6 +99,10 @@ describe("api/validator - produceBlockV2", () => { eth1Data: ssz.phase0.Eth1Data.defaultValue(), deposits: [], }); + modules.chain["syncContributionAndProofPool"].getAggregate.mockReturnValue({ + syncCommitteeBits: ssz.altair.SyncCommitteeBits.defaultValue(), + syncCommitteeSignature: G2_POINT_AT_INFINITY, + }); modules.forkChoice.getJustifiedBlock.mockReturnValue({} as ProtoBlock); modules.forkChoice.getFinalizedBlock.mockReturnValue({} as ProtoBlock); From 3f3c7fcb3476d66550515958a4b80e81b07ea87b Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Mon, 16 Dec 2024 17:25:19 +0000 Subject: [PATCH 34/43] chore: print graffiti when producing beacon block body (#7303) --- packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index e4c86b78907c..0b7797828c98 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -143,6 +143,7 @@ export async function produceBlockBody( : await produceCommonBlockBody.call(this, blockType, currentState, blockAttr); const { + graffiti, attestations, deposits, voluntaryExits, @@ -153,6 +154,7 @@ export async function produceBlockBody( } = blockBody; Object.assign(logMeta, { + graffiti, attestations: attestations.length, deposits: deposits.length, voluntaryExits: voluntaryExits.length, From c290469e9069aec2ff1654018d8967b8b82f6106 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Tue, 17 Dec 2024 01:24:44 +0000 Subject: [PATCH 35/43] fix: warn if engine / builder failed to produce block within cutoff time (#7305) --- .../src/api/impl/validator/index.ts | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/packages/beacon-node/src/api/impl/validator/index.ts b/packages/beacon-node/src/api/impl/validator/index.ts index b15644a8087e..fe26c559661c 100644 --- a/packages/beacon-node/src/api/impl/validator/index.ts +++ b/packages/beacon-node/src/api/impl/validator/index.ts @@ -695,32 +695,46 @@ export function getValidatorApi( throw Error("Builder and engine both failed to produce the block within timeout"); } - if (engine.status === "rejected" && isEngineEnabled) { - logger.warn( - "Engine failed to produce the block", - { - ...loggerContext, - durationMs: engine.durationMs, - }, - engine.reason - ); - } - - if (builder.status === "rejected" && isBuilderEnabled) { - if (builder.reason instanceof NoBidReceived) { - logger.info("Builder did not provide a bid", { - ...loggerContext, - durationMs: builder.durationMs, - }); - } else { + if (isEngineEnabled) { + if (engine.status === "rejected") { logger.warn( - "Builder failed to produce the block", + "Engine failed to produce the block", { ...loggerContext, - durationMs: builder.durationMs, + durationMs: engine.durationMs, }, - builder.reason + engine.reason ); + } else if (engine.status === "pending") { + logger.warn("Engine failed to produce the block within cutoff time", { + ...loggerContext, + cutoffMs, + }); + } + } + + if (isBuilderEnabled) { + if (builder.status === "rejected") { + if (builder.reason instanceof NoBidReceived) { + logger.info("Builder did not provide a bid", { + ...loggerContext, + durationMs: builder.durationMs, + }); + } else { + logger.warn( + "Builder failed to produce the block", + { + ...loggerContext, + durationMs: builder.durationMs, + }, + builder.reason + ); + } + } else if (builder.status === "pending") { + logger.warn("Builder failed to produce the block within cutoff time", { + ...loggerContext, + cutoffMs, + }); } } From 7cd296b0466f75136cbf544aaec5ce000c89a8ed Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:21:22 -0800 Subject: [PATCH 36/43] feat: add kzg commitment length check when validating gossip blocks (#7302) --- .../src/chain/errors/blockError.ts | 5 +- .../beacon-node/src/chain/validation/block.ts | 16 ++++++- .../test/unit/chain/validation/block.test.ts | 47 +++++++++++++++++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/packages/beacon-node/src/chain/errors/blockError.ts b/packages/beacon-node/src/chain/errors/blockError.ts index bc5dd6c33956..1dc5b08ccc20 100644 --- a/packages/beacon-node/src/chain/errors/blockError.ts +++ b/packages/beacon-node/src/chain/errors/blockError.ts @@ -64,6 +64,8 @@ export enum BlockErrorCode { TOO_MANY_SKIPPED_SLOTS = "TOO_MANY_SKIPPED_SLOTS", /** The blobs are unavailable */ DATA_UNAVAILABLE = "BLOCK_ERROR_DATA_UNAVAILABLE", + /** Block contains too many kzg commitments */ + TOO_MANY_KZG_COMMITMENTS = "BLOCK_ERROR_TOO_MANY_KZG_COMMITMENTS", } type ExecutionErrorStatus = Exclude< @@ -105,7 +107,8 @@ export type BlockErrorType = | {code: BlockErrorCode.SAME_PARENT_HASH; blockHash: RootHex} | {code: BlockErrorCode.TRANSACTIONS_TOO_BIG; size: number; max: number} | {code: BlockErrorCode.EXECUTION_ENGINE_ERROR; execStatus: ExecutionErrorStatus; errorMessage: string} - | {code: BlockErrorCode.DATA_UNAVAILABLE}; + | {code: BlockErrorCode.DATA_UNAVAILABLE} + | {code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS; blobKzgCommitmentsLen: number; commitmentLimit: number}; export class BlockGossipError extends GossipActionError {} diff --git a/packages/beacon-node/src/chain/validation/block.ts b/packages/beacon-node/src/chain/validation/block.ts index 1b5251f77809..b2623aa4f79d 100644 --- a/packages/beacon-node/src/chain/validation/block.ts +++ b/packages/beacon-node/src/chain/validation/block.ts @@ -1,5 +1,5 @@ import {ChainForkConfig} from "@lodestar/config"; -import {ForkName} from "@lodestar/params"; +import {ForkName, isForkBlobs} from "@lodestar/params"; import { computeStartSlotAtEpoch, computeTimeAtSlot, @@ -8,7 +8,7 @@ import { isExecutionEnabled, isExecutionStateType, } from "@lodestar/state-transition"; -import {SignedBeaconBlock} from "@lodestar/types"; +import {SignedBeaconBlock, deneb} from "@lodestar/types"; import {sleep, toRootHex} from "@lodestar/utils"; import {MAXIMUM_GOSSIP_CLOCK_DISPARITY} from "../../constants/index.js"; import {BlockErrorCode, BlockGossipError, GossipAction} from "../errors/index.js"; @@ -110,6 +110,18 @@ export async function validateGossipBlock( }); } + // [REJECT] The length of KZG commitments is less than or equal to the limitation defined in Consensus Layer -- i.e. validate that len(body.signed_beacon_block.message.blob_kzg_commitments) <= MAX_BLOBS_PER_BLOCK + if (isForkBlobs(fork)) { + const blobKzgCommitmentsLen = (block as deneb.BeaconBlock).body.blobKzgCommitments.length; + if (blobKzgCommitmentsLen > chain.config.MAX_BLOBS_PER_BLOCK) { + throw new BlockGossipError(GossipAction.REJECT, { + code: BlockErrorCode.TOO_MANY_KZG_COMMITMENTS, + blobKzgCommitmentsLen, + commitmentLimit: chain.config.MAX_BLOBS_PER_BLOCK, + }); + } + } + // use getPreState to reload state if needed. It also checks for whether the current finalized checkpoint is an ancestor of the block. // As a result, we throw an IGNORE (whereas the spec says we should REJECT for this scenario). // this is something we should change this in the future to make the code airtight to the spec. diff --git a/packages/beacon-node/test/unit/chain/validation/block.test.ts b/packages/beacon-node/test/unit/chain/validation/block.test.ts index 4b236181b038..5e3faf794453 100644 --- a/packages/beacon-node/test/unit/chain/validation/block.test.ts +++ b/packages/beacon-node/test/unit/chain/validation/block.test.ts @@ -1,6 +1,6 @@ import {config} from "@lodestar/config/default"; import {ProtoBlock} from "@lodestar/fork-choice"; -import {ForkName} from "@lodestar/params"; +import {ForkBlobs, ForkName} from "@lodestar/params"; import {SignedBeaconBlock, ssz} from "@lodestar/types"; import {Mock, Mocked, beforeEach, describe, it, vi} from "vitest"; import {BlockErrorCode} from "../../../../src/chain/errors/index.js"; @@ -20,12 +20,15 @@ describe("gossip block validation", () => { let job: SignedBeaconBlock; const proposerIndex = 0; const clockSlot = 32; - const block = ssz.phase0.BeaconBlock.defaultValue(); + const block = ssz.deneb.BeaconBlock.defaultValue(); block.slot = clockSlot; const signature = EMPTY_SIGNATURE; const maxSkipSlots = 10; beforeEach(() => { + // Fill up with kzg commitments + block.body.blobKzgCommitments = Array.from({length: config.MAX_BLOBS_PER_BLOCK}, () => new Uint8Array([0])); + chain = getMockedBeaconChain(); vi.spyOn(chain.clock, "currentSlotWithGossipDisparity", "get").mockReturnValue(clockSlot); forkChoice = chain.forkChoice; @@ -184,9 +187,47 @@ describe("gossip block validation", () => { regen.getPreState.mockResolvedValue(state); // BLS signature verifier returns valid verifySignature.mockResolvedValue(true); - // Force proposer shuffling cache to return wrong value + // Force proposer shuffling cache to return correct value vi.spyOn(state.epochCtx, "getBeaconProposer").mockReturnValue(proposerIndex); await validateGossipBlock(config, chain, job, ForkName.phase0); }); + + it("deneb - TOO_MANY_KZG_COMMITMENTS", async () => { + // Return not known for proposed block + forkChoice.getBlockHex.mockReturnValueOnce(null); + // Returned parent block is latter than proposed block + forkChoice.getBlockHex.mockReturnValueOnce({slot: clockSlot - 1} as ProtoBlock); + // Regen returns some state + const state = generateCachedState(); + regen.getPreState.mockResolvedValue(state); + // BLS signature verifier returns valid + verifySignature.mockResolvedValue(true); + // Force proposer shuffling cache to return correct value + vi.spyOn(state.epochCtx, "getBeaconProposer").mockReturnValue(proposerIndex + 1); + // Add one extra kzg commitment in the block so it goes over the limit + (job as SignedBeaconBlock).message.body.blobKzgCommitments.push(new Uint8Array([0])); + + await expectRejectedWithLodestarError( + validateGossipBlock(config, chain, job, ForkName.deneb), + BlockErrorCode.TOO_MANY_KZG_COMMITMENTS + ); + }); + + it("deneb - valid", async () => { + // Return not known for proposed block + forkChoice.getBlockHex.mockReturnValueOnce(null); + // Returned parent block is latter than proposed block + forkChoice.getBlockHex.mockReturnValueOnce({slot: clockSlot - 1} as ProtoBlock); + // Regen returns some state + const state = generateCachedState(); + regen.getPreState.mockResolvedValue(state); + // BLS signature verifier returns valid + verifySignature.mockResolvedValue(true); + // Force proposer shuffling cache to return correct value + vi.spyOn(state.epochCtx, "getBeaconProposer").mockReturnValue(proposerIndex); + // Keep number of kzg commitments as is so it stays within the limit + + await validateGossipBlock(config, chain, job, ForkName.deneb); + }); }); From bfe54d25def3e72705c6fa027549878d7e69f996 Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 18 Dec 2024 16:45:51 -0800 Subject: [PATCH 37/43] feat: add blob sidecar index check (#7313) Validate blobSidecar index --- .../beacon-node/src/chain/errors/blobSidecarError.ts | 2 ++ packages/beacon-node/src/chain/validation/blobSidecar.ts | 9 +++++++++ 2 files changed, 11 insertions(+) diff --git a/packages/beacon-node/src/chain/errors/blobSidecarError.ts b/packages/beacon-node/src/chain/errors/blobSidecarError.ts index 71118d8b8f9d..216ad9206db4 100644 --- a/packages/beacon-node/src/chain/errors/blobSidecarError.ts +++ b/packages/beacon-node/src/chain/errors/blobSidecarError.ts @@ -2,6 +2,7 @@ import {RootHex, Slot, ValidatorIndex} from "@lodestar/types"; import {GossipActionError} from "./gossipValidation.js"; export enum BlobSidecarErrorCode { + INDEX_TOO_LARGE = "BLOB_SIDECAR_ERROR_INDEX_TOO_LARGE", INVALID_INDEX = "BLOB_SIDECAR_ERROR_INVALID_INDEX", /** !bls.KeyValidate(block.body.blob_kzg_commitments[i]) */ INVALID_KZG = "BLOB_SIDECAR_ERROR_INVALID_KZG", @@ -26,6 +27,7 @@ export enum BlobSidecarErrorCode { } export type BlobSidecarErrorType = + | {code: BlobSidecarErrorCode.INDEX_TOO_LARGE; blobIdx: number; maxBlobsPerBlock: number} | {code: BlobSidecarErrorCode.INVALID_INDEX; blobIdx: number; subnet: number} | {code: BlobSidecarErrorCode.INVALID_KZG; blobIdx: number} | {code: BlobSidecarErrorCode.INVALID_KZG_TXS} diff --git a/packages/beacon-node/src/chain/validation/blobSidecar.ts b/packages/beacon-node/src/chain/validation/blobSidecar.ts index e2f4ed267d01..4aeff0f23ff1 100644 --- a/packages/beacon-node/src/chain/validation/blobSidecar.ts +++ b/packages/beacon-node/src/chain/validation/blobSidecar.ts @@ -18,6 +18,15 @@ export async function validateGossipBlobSidecar( ): Promise { const blobSlot = blobSidecar.signedBlockHeader.message.slot; + // [REJECT] The sidecar's index is consistent with `MAX_BLOBS_PER_BLOCK` -- i.e. `blob_sidecar.index < MAX_BLOBS_PER_BLOCK`. + if (blobSidecar.index < chain.config.MAX_BLOBS_PER_BLOCK) { + throw new BlobSidecarGossipError(GossipAction.REJECT, { + code: BlobSidecarErrorCode.INDEX_TOO_LARGE, + blobIdx: blobSidecar.index, + maxBlobsPerBlock: chain.config.MAX_BLOBS_PER_BLOCK, + }); + } + // [REJECT] The sidecar is for the correct subnet -- i.e. `compute_subnet_for_blob_sidecar(sidecar.index) == subnet_id`. if (computeSubnetForBlobSidecar(blobSidecar.index, chain.config) !== subnet) { throw new BlobSidecarGossipError(GossipAction.REJECT, { From 1f38c8b1751c810e267ceab4d56d06735ac3445c Mon Sep 17 00:00:00 2001 From: NC <17676176+ensi321@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:31:22 -0800 Subject: [PATCH 38/43] fix: fix blob sidecar index check (#7315) Fix index check --- packages/beacon-node/src/chain/validation/blobSidecar.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/validation/blobSidecar.ts b/packages/beacon-node/src/chain/validation/blobSidecar.ts index 4aeff0f23ff1..b79db77e3931 100644 --- a/packages/beacon-node/src/chain/validation/blobSidecar.ts +++ b/packages/beacon-node/src/chain/validation/blobSidecar.ts @@ -19,7 +19,7 @@ export async function validateGossipBlobSidecar( const blobSlot = blobSidecar.signedBlockHeader.message.slot; // [REJECT] The sidecar's index is consistent with `MAX_BLOBS_PER_BLOCK` -- i.e. `blob_sidecar.index < MAX_BLOBS_PER_BLOCK`. - if (blobSidecar.index < chain.config.MAX_BLOBS_PER_BLOCK) { + if (blobSidecar.index >= chain.config.MAX_BLOBS_PER_BLOCK) { throw new BlobSidecarGossipError(GossipAction.REJECT, { code: BlobSidecarErrorCode.INDEX_TOO_LARGE, blobIdx: blobSidecar.index, From 8c7eaf8c57b56aa9294dcb8607f334b5500de237 Mon Sep 17 00:00:00 2001 From: Nico Flaig Date: Thu, 19 Dec 2024 09:35:23 +0000 Subject: [PATCH 39/43] chore: fix format of printed graffiti from hex to utf-8 (#7306) * chore: fix format of printed graffiti from hex to utf-8 * Use Buffer.from no copy with offset --- .../beacon-node/src/chain/produceBlock/produceBlockBody.ts | 3 ++- packages/beacon-node/src/util/graffiti.ts | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts index 0b7797828c98..d7b36e06abc4 100644 --- a/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts +++ b/packages/beacon-node/src/chain/produceBlock/produceBlockBody.ts @@ -37,6 +37,7 @@ import {ZERO_HASH, ZERO_HASH_HEX} from "../../constants/index.js"; import {IEth1ForBlockProduction} from "../../eth1/index.js"; import {numToQuantity} from "../../eth1/provider/utils.js"; import {IExecutionBuilder, IExecutionEngine, PayloadAttributes, PayloadId} from "../../execution/index.js"; +import {fromGraffitiBuffer} from "../../util/graffiti.js"; import type {BeaconChain} from "../chain.js"; import {CommonBlockBody} from "../interface.js"; import {validateBlobsAndKzgCommitments} from "./validateBlobsAndKzgCommitments.js"; @@ -154,7 +155,7 @@ export async function produceBlockBody( } = blockBody; Object.assign(logMeta, { - graffiti, + graffiti: fromGraffitiBuffer(graffiti), attestations: attestations.length, deposits: deposits.length, voluntaryExits: voluntaryExits.length, diff --git a/packages/beacon-node/src/util/graffiti.ts b/packages/beacon-node/src/util/graffiti.ts index 9a4bc3d9689b..fb5c30aca3d3 100644 --- a/packages/beacon-node/src/util/graffiti.ts +++ b/packages/beacon-node/src/util/graffiti.ts @@ -8,6 +8,13 @@ export function toGraffitiBuffer(graffiti: string): Buffer { return Buffer.concat([Buffer.from(graffiti, "utf8"), Buffer.alloc(GRAFFITI_SIZE, 0)], GRAFFITI_SIZE); } +/** + * Converts a graffiti from 32 bytes buffer back to a UTF-8 string + */ +export function fromGraffitiBuffer(graffiti: Uint8Array): string { + return Buffer.from(graffiti.buffer, graffiti.byteOffset, graffiti.byteLength).toString("utf8"); +} + export function getDefaultGraffiti( consensusClientVersion: ClientVersion, executionClientVersion: ClientVersion | null | undefined, From ffdfb2e51704456ffb49214b5aba9bd7a2360051 Mon Sep 17 00:00:00 2001 From: Phil Ngo <58080811+philknows@users.noreply.github.com> Date: Thu, 19 Dec 2024 14:44:42 -0500 Subject: [PATCH 40/43] docs: batch commit typos and update contributor readme (#7312) * batch commit typos and update contributor readme * update donation text Co-authored-by: Nico Flaig * correct spelling Co-authored-by: Nico Flaig --------- Co-authored-by: Nico Flaig --- .wordlist.txt | 1 - CONTRIBUTING.md | 2 ++ README.md | 4 ++-- RELEASE.md | 2 +- docs/pages/contribution/testing/index.md | 4 ++-- docs/pages/contribution/tools/heap-dumps.md | 4 ++-- docs/pages/faqs.md | 2 +- docs/pages/introduction.md | 2 +- docs/pages/run/logging-and-metrics/client-monitoring.md | 2 +- packages/light-client/README.md | 6 +++--- packages/params/README.md | 2 +- packages/prover/README.md | 2 +- packages/utils/src/yaml/int.ts | 2 +- scripts/await-release.sh | 2 +- 14 files changed, 19 insertions(+), 18 deletions(-) diff --git a/.wordlist.txt b/.wordlist.txt index 5070aafcf9a5..d74291a2fb0c 100644 --- a/.wordlist.txt +++ b/.wordlist.txt @@ -198,7 +198,6 @@ namespaces nodemodule orchestrator osx -overriden params performant pid diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 24d087810980..ca5a28442fd8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -126,6 +126,8 @@ Unsure where to begin contributing to Lodestar? Here are some ideas! 5. Make an open pull request when you're ready for it to be reviewed. We review PRs on a regular basis. See Pull request etiquette for more information. 6. You may be asked to sign a Contributor License Agreement (CLA). We make it relatively painless with CLA-bot. +Please note that trivial, non-code contributions such as spelling, grammar, typos, corrections, comments and link fixes are not acceptable pull requests. Although we appreciate the effort to fix these valid concerns, it is not practical for us to run our CI systems to accommodate minor external contributions which generate minimal value for the purpose of contribution/airdrop farming. It would be appreciated for you to open up an issue instead for our team to aggregate these types of contributions into a batch commit. + ## Github Style Guide **Branch Naming** diff --git a/README.md b/README.md index e03de683a9de..53c53cee8621 100644 --- a/README.md +++ b/README.md @@ -70,9 +70,9 @@ Read our [contributors document](/CONTRIBUTING.md), [submit an issue](https://gi ## Meetings -Weekly contributor meetings are public and announced on Discord. Feel free to check out our meeting notes and documents on [HackMD](https://hackmd.io/@wemeetagain/rJTEOdqPS/%2FXBzvaQgMTyyMJuToWAEDjw). Post-September 2021, meeting notes can be found on the [Lodestar Wiki Page](https://github.com/ChainSafe/lodestar/wiki). +Weekly contributor meetings are posted under [Discussions](https://github.com/ChainSafe/lodestar/discussions) and topics are welcomed by any participant in the relevant meeting thread. Feel free to check out our meeting notes and documents on [HackMD](https://hackmd.io/@wemeetagain/rJTEOdqPS/%2FXBzvaQgMTyyMJuToWAEDjw). Post-September 2021, meeting notes can be found on the [Lodestar Wiki Page](https://github.com/ChainSafe/lodestar/wiki). ## Donations We are a local group of Toronto open-source developers. As such, all of our open-source work is funded by grants. We all take the time out of our hectic lives to contribute to the Ethereum ecosystem. -If you want to donate, you can send us ETH at the following address: `lodestar.chainsafe.eth` +If you want to donate, you can find the ETH address under "Sponsor this project" on this repository. diff --git a/RELEASE.md b/RELEASE.md index bb40b611ebe5..6d831ddb3bd4 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -69,7 +69,7 @@ Tagging a release candidate will trigger CI to publish to NPM, dockerhub, and Gi Once a release candidate is created, the Lodestar team begins a testing period. -If there is a bug discovered during the testing period which significantly impacts performance, security, or stability, and it is determined that it is no longer prudent to promote the `rc.x` candidate to `stable`, then it will await a bug fix by the team. The fix will be committed to `unstable` first, then cherrypicked into the `rc/v1.1.0` branch. Then we publish and promote the new commit to `rc.x+1`. The 3 day testing period will reset. +If there is a bug discovered during the testing period which significantly impacts performance, security, or stability, and it is determined that it is no longer prudent to promote the `rc.x` candidate to `stable`, then it will await a bug fix by the team. The fix will be committed to `unstable` first, then cherrypicked into the `rc/v1.1.0` branch. Then we publish and promote the new commit to `rc.x+1`. The 3-day testing period will reset. For example: After 3-5 days of testing, is performance equal to or better than latest stable? diff --git a/docs/pages/contribution/testing/index.md b/docs/pages/contribution/testing/index.md index ecef1ef22c56..a9795670fa82 100644 --- a/docs/pages/contribution/testing/index.md +++ b/docs/pages/contribution/testing/index.md @@ -2,7 +2,7 @@ Testing is critical to the Lodestar project and there are many types of tests that are run to build a product that is both effective AND efficient. This page will help to break down the different types of tests you will find in the Lodestar repo. -There are few flags you can set through env variables to override behavior of testing and it's output. +There are a few flags you can set through env variables to override behavior of testing and its output. | ENV variable | Effect | Impact | | ------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------- | @@ -24,7 +24,7 @@ Node.js is an unforgiving virtual machine when it comes to high performance, mul ### End-To-End Tests -E2E test are where Lodestar is run in its full form, often from the CLI as a user would to check that the system as a whole works as expected. These tests are meant to exercise the entire system in isolation and there is no network interaction, nor interaction with any other code outside of Lodestar. See the [End-To-End Testing](./end-to-end-tests.md) page for more information. +E2E tests are where Lodestar is run in its full form, often from the CLI as a user would to check that the system as a whole works as expected. These tests are meant to exercise the entire system in isolation and there is no network interaction, nor interaction with any other code outside of Lodestar. See the [End-To-End Testing](./end-to-end-tests.md) page for more information. ### Integration Tests diff --git a/docs/pages/contribution/tools/heap-dumps.md b/docs/pages/contribution/tools/heap-dumps.md index 97f2be51dfac..612dfebc3f83 100644 --- a/docs/pages/contribution/tools/heap-dumps.md +++ b/docs/pages/contribution/tools/heap-dumps.md @@ -40,7 +40,7 @@ Click on the `Open dedicated DevTools for Node` link to open the node specific w ![Memory Tab](/images/heap-dumps/memory-tab.png) -Load the profile by either right clicking on the left pane or by clicking the `Load` button at the bottom. +Load the profile by either right-clicking on the left pane or by clicking the `Load` button at the bottom. ![Load Profile](/images/heap-dumps/load-profile.png) @@ -70,7 +70,7 @@ Having a good understanding of the codebase will help to narrow down where to lo _**note: collecting a native heap dump is only supported on linux, analysis can be done from linux or Mac**_ -There are several tools that can be used to do native heap dump analysis. The most common are [`massif`](https://valgrind.org/docs/manual/ms-manual.html) from the [`Valgrind`](https://valgrind.org/) suite, google's [`gperftools`](https://github.com/gperftools/gperftools) and `heaptrack` from [KDE](https://community.kde.org/Main_Page). Of the three, `heaptrack` is the most user friendly tool, and it is specifically designed for the task. It is much faster than `Valgrind`, easier to integrate than `gperftools` and also includes a gui for result analysis. Often times there are also memory allocations that are not related to memory leaks, and tools like `Valgrind` and `gperftools` become less useful. This is why `heaptrack` is the recommended tool for heap dump analysis on Lodestar. +There are several tools that can be used to do native heap dump analysis. The most common are [`massif`](https://valgrind.org/docs/manual/ms-manual.html) from the [`Valgrind`](https://valgrind.org/) suite, google's [`gperftools`](https://github.com/gperftools/gperftools) and `heaptrack` from [KDE](https://community.kde.org/Main_Page). Of the three, `heaptrack` is the most user-friendly tool, and it is specifically designed for the task. It is much faster than `Valgrind`, easier to integrate than `gperftools` and also includes a gui for result analysis. Often times there are also memory allocations that are not related to memory leaks, and tools like `Valgrind` and `gperftools` become less useful. This is why `heaptrack` is the recommended tool for heap dump analysis on Lodestar. There are a few things that will make the results with `heaptrack` far better. The most important is using debug builds of all libraries included in a binary, including the application itself. This will make the results usable. Not to say that they will be useless without debug symbols but it will be kinda tough to optimize functions without knowing the function names nor the file and line numbers. diff --git a/docs/pages/faqs.md b/docs/pages/faqs.md index e7ac490bf9b9..ebe2a2158a2d 100644 --- a/docs/pages/faqs.md +++ b/docs/pages/faqs.md @@ -1,6 +1,6 @@ # Frequently Asked Questions -This section of the documentation will cover common questions and encounters often asked by users and developers. +This section of the documentation will cover common questions and common encounters by users and developers. ## Tooling diff --git a/docs/pages/introduction.md b/docs/pages/introduction.md index f74d0b70768d..5633131dd600 100644 --- a/docs/pages/introduction.md +++ b/docs/pages/introduction.md @@ -1,6 +1,6 @@ # Introduction -Ethereum is one of the most profoundly important inventions in recent history. It is a decentralized, open-source blockchain featuring smart contract functionality. It is the second-largest cryptocurrency by market capitalization, after Bitcoin, and is the second largest blockchain by market capitalization. Ethereum was proposed in 2013 by programmer Vitalik Buterin. Development was crowdfunded in 2014, and the network went live on 30 July 2015, with 72 million coins premined. ChainSafe was founded not too long afterwards in 2017 and has been actively working in the Ethereum ecosystem ever since. We are proud to develop Lodestar, the only TypeScript based consensus client, and to present this documentation as a resource for the Ethereum community. +Ethereum is one of the most profoundly important inventions in recent history. It is a decentralized, open-source blockchain featuring smart contract functionality. It is the second-largest cryptocurrency by market capitalization, after Bitcoin, and is the second-largest blockchain by market capitalization. Ethereum was proposed in 2013 by programmer Vitalik Buterin. Development was crowdfunded in 2014, and the network went live on 30 July 2015, with 72 million coins premined. ChainSafe was founded not too long afterwards in 2017 and has been actively working in the Ethereum ecosystem ever since. We are proud to develop Lodestar, the only TypeScript based consensus client, and to present this documentation as a resource for the Ethereum community. ## Proof of Stake diff --git a/docs/pages/run/logging-and-metrics/client-monitoring.md b/docs/pages/run/logging-and-metrics/client-monitoring.md index 3d9ede1d8464..31a20600ecf3 100644 --- a/docs/pages/run/logging-and-metrics/client-monitoring.md +++ b/docs/pages/run/logging-and-metrics/client-monitoring.md @@ -27,7 +27,7 @@ When sending data to a remote service you should be conscious about security: - Only use a service that you trust as this will send information which may identify you and associate your validators, IP address and other personal information. -- Always use a HTTPS connection (i.e. a URL starting with `https://`) to prevent the traffic +- Always use an HTTPS connection (i.e. a URL starting with `https://`) to prevent the traffic from being intercepted in transit and leaking information. ::: diff --git a/packages/light-client/README.md b/packages/light-client/README.md index 3cab0c12f13b..f9af65f23f98 100644 --- a/packages/light-client/README.md +++ b/packages/light-client/README.md @@ -18,7 +18,7 @@ The evolution of light clients is emblematic of the broader trajectory of Ethere ## Requirements for Running a Light-Client -Access to an beacon node that supports the light client specification is necessary. The client must support the following routes from the [consensus API spec](https://github.com/ethereum/beacon-APIs/tree/v2.5.0/apis/beacon/light_client): +Access to a beacon node that supports the light client specification is necessary. The client must support the following routes from the [consensus API spec](https://github.com/ethereum/beacon-APIs/tree/v2.5.0/apis/beacon/light_client): - [`GET /eth/v1/beacon/light_client/updates`](https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.5.0#/Beacon/getLightClientUpdatesByRange) - [`GET /eth/v1/beacon/light_client/optimistic_update`](https://ethereum.github.io/beacon-APIs/?urls.primaryName=v2.5.0#/Beacon/getLightClientOptimisticUpdate) @@ -32,7 +32,7 @@ You can find more information about the light-client protocol in the [specificat ## Getting started - Follow the [installation guide](https://chainsafe.github.io/lodestar/run/getting-started/installation) to install Lodestar. -- Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/advanced-topics/setting-up-a-testnet). +- Quickly try out the whole stack by [starting a local testnet](https://chainsafe.github.io/lodestar/contribution/advanced-topics/setting-up-a-testnet). ## Light-Client CLI Example @@ -92,7 +92,7 @@ lightclient.emitter.on(LightclientEvent.lightClientOptimisticHeader, async (opti ## Browser Integration -If you want to use Lightclient in browser and facing some issues in building it with bundlers like webpack, vite. We suggest to use our distribution build. The support for single distribution build is started from `1.20.0` version. +If you want to use Lightclient in browser and are facing some issues in building it with bundlers like webpack, vite. We suggest using our distribution build. The support for single distribution build is started from `1.20.0` version. Directly link the dist build with the `