diff --git a/yarn-project/end-to-end/src/e2e_epochs/epochs_long_proving_time.test.ts b/yarn-project/end-to-end/src/e2e_epochs/epochs_long_proving_time.test.ts index ddf522d2c102..63d442847e40 100644 --- a/yarn-project/end-to-end/src/e2e_epochs/epochs_long_proving_time.test.ts +++ b/yarn-project/end-to-end/src/e2e_epochs/epochs_long_proving_time.test.ts @@ -24,7 +24,12 @@ describe('e2e_epochs/epochs_long_proving_time', () => { const { aztecSlotDuration } = EpochsTestContext.getSlotDurations({ aztecEpochDuration }); const epochDurationInSeconds = aztecSlotDuration * aztecEpochDuration; const proverTestDelayMs = (epochDurationInSeconds * 1000 * 3) / 4; - test = await EpochsTestContext.setup({ aztecEpochDuration, aztecProofSubmissionEpochs: 8, proverTestDelayMs }); + test = await EpochsTestContext.setup({ + aztecEpochDuration, + aztecProofSubmissionEpochs: 1000, // Effectively don't re-org + proverTestDelayMs, + proverNodeMaxPendingJobs: 1, // We test for only a single job at once + }); ({ logger, monitor, L1_BLOCK_TIME_IN_S } = test); logger.warn(`Initialized with prover delay set to ${proverTestDelayMs}ms (epoch is ${epochDurationInSeconds}s)`); }); @@ -34,7 +39,7 @@ describe('e2e_epochs/epochs_long_proving_time', () => { await test.teardown(); }); - it.skip('generates proof over multiple epochs', async () => { + it('generates proof over multiple epochs', async () => { const targetProvenEpochs = process.env.TARGET_PROVEN_EPOCHS ? parseInt(process.env.TARGET_PROVEN_EPOCHS) : 1; const targetProvenBlockNumber = targetProvenEpochs * test.epochDuration; logger.info(`Waiting for ${targetProvenEpochs} epochs to be proven at ${targetProvenBlockNumber} L2 blocks`); diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index ef1f14bd63b3..e35c23bcd20f 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -216,6 +216,7 @@ export type EnvVar = | 'SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT' | 'SEQ_ATTESTATION_PROPAGATION_TIME' | 'SEQ_BLOCK_DURATION_MS' + | 'SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT' | 'SEQ_BUILD_CHECKPOINT_IF_EMPTY' | 'SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_COMMITTEE_MEMBER' | 'SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_NON_COMMITTEE_MEMBER' diff --git a/yarn-project/p2p/src/config.ts b/yarn-project/p2p/src/config.ts index ad47120b786b..f7c54eb1e191 100644 --- a/yarn-project/p2p/src/config.ts +++ b/yarn-project/p2p/src/config.ts @@ -38,7 +38,7 @@ export interface P2PConfig ChainConfig, TxCollectionConfig, TxFileStoreConfig, - Pick { + Pick { /** A flag dictating whether the P2P subsystem should be enabled. */ p2pEnabled: boolean; diff --git a/yarn-project/p2p/src/services/gossipsub/README.md b/yarn-project/p2p/src/services/gossipsub/README.md index e7d132544b62..5736bc70d5ef 100644 --- a/yarn-project/p2p/src/services/gossipsub/README.md +++ b/yarn-project/p2p/src/services/gossipsub/README.md @@ -40,7 +40,7 @@ We configure all parameters (P1-P4) with values calculated dynamically from netw | P3b: meshFailurePenalty | -34 per topic | Sticky penalty after pruning | | P4: invalidMessageDeliveries | -20 per message | Attack detection | -**Important:** P1 and P2 are only enabled on topics with P3 enabled (block_proposal, checkpoint_proposal, checkpoint_attestation). The tx topic has all scoring disabled except P4, to prevent free positive score accumulation that would offset penalties from other topics. +**Important:** P1 and P2 are only enabled on topics with P3 enabled. By default, P3 is enabled for checkpoint_proposal and checkpoint_attestation (2 topics). Block proposal scoring is controlled by `expectedBlockProposalsPerSlot` (current default: `0`, including when env var is unset, so disabled) - see [Block Proposals](#block-proposals-block_proposal) for details. The tx topic has all scoring disabled except P4, to prevent free positive score accumulation that would offset penalties from other topics. ## Exponential Decay @@ -217,7 +217,21 @@ Transactions are submitted unpredictably by users, so we cannot set meaningful d ### Block Proposals (block_proposal) -In Multi-Block-Per-Slot (MBPS) mode, N-1 block proposals are gossiped per slot (the last block is bundled with the checkpoint). In single-block mode, this is 0. +Block proposal scoring is controlled by the `expectedBlockProposalsPerSlot` config (`SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT` env var): + +| Config Value | Behavior | +|-------------|----------| +| `0` (current default) | Block proposal P3 scoring is **disabled** | +| Positive number | Uses the provided value as expected proposals per slot | +| `undefined` | Falls back to `blocksPerSlot - 1` (MBPS mode: N-1, single block: 0) | + +**Current behavior note:** In the current implementation, if `SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT` is not set, config mapping applies `0` by default (scoring disabled). The `undefined` fallback above is currently reachable only if the value is explicitly provided as `undefined` in code. + +**Future intent:** Once throughput is stable, we may change env parsing/defaults so an unset env var resolves to `undefined` again (re-enabling automatic fallback to `blocksPerSlot - 1`). + +**Why disabled by default?** In MBPS mode, gossipsub expects N-1 block proposals per slot. When transaction throughput is low (as expected at launch), fewer blocks are actually built, causing peers to be incorrectly penalized for under-delivering block proposals. The default of 0 disables this scoring. Set to a positive value when throughput increases and block production is consistent. + +In MBPS mode (when enabled), N-1 block proposals are gossiped per slot (the last block is bundled with the checkpoint). In single-block mode, this is 0. ### Checkpoint Proposals (checkpoint_proposal) @@ -241,6 +255,7 @@ The scoring parameters depend on: | `targetCommitteeSize` | L1RollupConstants | 48 | | `heartbeatInterval` | P2PConfig.gossipsubInterval | 700ms | | `blockDurationMs` | P2PConfig.blockDurationMs | undefined (single block) | +| `expectedBlockProposalsPerSlot` | P2PConfig.expectedBlockProposalsPerSlot | 0 (disabled; current unset-env behavior) | ## Invalid Message Handling (P4) @@ -320,9 +335,9 @@ Conversely, if topic scores are low, a peer slightly above the disconnect thresh Topic scores provide **burst response** to attacks, while app score provides **stable baseline**: -- P1 (time in mesh): Max +8 per topic (+24 across 3 topics) -- P2 (first deliveries): Max +25 per topic (+75 across 3 topics, but decays fast) -- P3 (under-delivery): Max -34 per topic (-102 across 3 topics in MBPS; -68 in single-block mode) +- P1 (time in mesh): Max +8 per topic (+16 default, +24 with block proposal scoring enabled) +- P2 (first deliveries): Max +25 per topic (+50 default, +75 with block proposal scoring, but decays fast) +- P3 (under-delivery): Max -34 per topic (-68 default with 2 topics, -102 with block proposal scoring enabled) - P4 (invalid messages): -20 per invalid message, can spike to -2000+ during attacks Example attack scenario: @@ -373,21 +388,21 @@ When a peer is pruned from the mesh: 3. **P3b captures the penalty**: The P3 deficit at prune time becomes P3b, which decays slowly After pruning, the peer's score consists mainly of P3b: -- **Total P3b across 3 topics: -102** (max) +- **Total P3b: -68** (default, 2 topics) or **-102** (with block proposal scoring enabled, 3 topics) - **Recovery time**: P3b decays to ~1% over one decay window (2-5 slots = 2-6 minutes) - **Grafting eligibility**: Peer can be grafted when score ≥ 0, but asymptotic decay means recovery is slow ### Why Non-Contributors Aren't Disconnected -With P3b capped at -102 total after pruning (MBPS mode). In single-block mode, the cap is -68: +With P3b capped at -68 (default, 2 topics) or -102 (with block proposal scoring, 3 topics) after pruning: | Threshold | Value | P3b Score | Triggered? | |-----------|-------|-----------|------------| -| gossipThreshold | -500 | -102 (MBPS) / -68 (single) | No | -| publishThreshold | -1000 | -102 (MBPS) / -68 (single) | No | -| graylistThreshold | -2000 | -102 (MBPS) / -68 (single) | No | +| gossipThreshold | -500 | -68 (default) / -102 (block scoring on) | No | +| publishThreshold | -1000 | -68 (default) / -102 (block scoring on) | No | +| graylistThreshold | -2000 | -68 (default) / -102 (block scoring on) | No | -**A score of -102 (MBPS) or -68 (single-block) is well above -500**, so non-contributing peers: +**A score of -68 or -102 is well above -500**, so non-contributing peers: - Are pruned from mesh (good - stops them slowing propagation) - Still receive gossip (can recover by reconnecting/restarting) - Are NOT disconnected unless they also have application-level penalties @@ -547,7 +562,7 @@ What happens when a peer experiences a network outage and stops delivering messa While the peer is disconnected: 1. **P3 penalty accumulates**: The message delivery counter decays toward 0, causing increasing P3 penalty -2. **Max P3 penalty reached**: Once counter drops below threshold, penalty hits -34 per topic (-102 total in MBPS; -68 single-block) +2. **Max P3 penalty reached**: Once counter drops below threshold, penalty hits -34 per topic (-68 default, -102 with block proposal scoring) 3. **Mesh pruning**: Topic score goes negative → peer is pruned from mesh 4. **P3b captures penalty**: The P3 deficit at prune time becomes P3b (sticky penalty) @@ -569,13 +584,13 @@ Note: If the peer just joined the mesh, P3 penalties only start after During a network outage, the peer: - **Does NOT send invalid messages** → No P4 penalty - **Does NOT violate protocols** → No application-level penalty -- **Only accumulates topic-level penalties** → Max -102 (P3b, MBPS) or -68 (single-block) +- **Only accumulates topic-level penalties** → Max -68 (default) or -102 (with block proposal scoring) This is the crucial difference from malicious behavior: | Scenario | App Score | Topic Score | Total | Threshold Hit | |----------|-----------|-------------|-------|---------------| -| Network outage | 0 | -102 (MBPS) / -68 (single) | -102 / -68 | None | +| Network outage | 0 | -68 (default) / -102 (block scoring on) | -68 / -102 | None | | Validation failure | -50 | -20 | -520 | gossipThreshold | | Malicious peer | -100 | -2000+ | -2100+ | graylistThreshold | diff --git a/yarn-project/p2p/src/services/gossipsub/topic_score_params.test.ts b/yarn-project/p2p/src/services/gossipsub/topic_score_params.test.ts index fed55b4535ff..d644397a7ba2 100644 --- a/yarn-project/p2p/src/services/gossipsub/topic_score_params.test.ts +++ b/yarn-project/p2p/src/services/gossipsub/topic_score_params.test.ts @@ -12,6 +12,7 @@ import { createAllTopicScoreParams, createTopicScoreParamsForTopic, getDecayWindowSlots, + getEffectiveBlockProposalsPerSlot, getExpectedMessagesPerSlot, } from './topic_score_params.js'; @@ -148,18 +149,47 @@ describe('Topic Score Params', () => { }); }); + describe('getEffectiveBlockProposalsPerSlot', () => { + it('returns undefined when override is 0 (disabled)', () => { + expect(getEffectiveBlockProposalsPerSlot(5, 0)).toBeUndefined(); + }); + + it('returns override value when positive', () => { + expect(getEffectiveBlockProposalsPerSlot(5, 3)).toBe(3); + expect(getEffectiveBlockProposalsPerSlot(1, 7)).toBe(7); + }); + + it('falls back to blocksPerSlot - 1 when override is undefined', () => { + expect(getEffectiveBlockProposalsPerSlot(5, undefined)).toBe(4); + expect(getEffectiveBlockProposalsPerSlot(3, undefined)).toBe(2); + }); + + it('returns undefined when override is undefined and single block mode', () => { + expect(getEffectiveBlockProposalsPerSlot(1, undefined)).toBeUndefined(); + }); + }); + describe('getExpectedMessagesPerSlot', () => { it('returns undefined for tx topic (unpredictable)', () => { expect(getExpectedMessagesPerSlot(TopicType.tx, 48, 5)).toBeUndefined(); }); - it('returns N-1 for block_proposal in MBPS mode', () => { + it('returns N-1 for block_proposal when override is undefined (fallback)', () => { expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 5)).toBe(4); expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 3)).toBe(2); }); - it('returns 0 for block_proposal in single block mode', () => { - expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 1)).toBe(0); + it('returns undefined for block_proposal in single block mode without override', () => { + expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 1)).toBeUndefined(); + }); + + it('returns undefined for block_proposal when override is 0 (disabled)', () => { + expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 5, 0)).toBeUndefined(); + }); + + it('returns override value for block_proposal when positive', () => { + expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 1, 3)).toBe(3); + expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 5, 7)).toBe(7); }); it('returns 1 for checkpoint_proposal', () => { @@ -207,10 +237,35 @@ describe('Topic Score Params', () => { expect(params.meshFailurePenaltyWeight).toBe(0); }); - it('enables P3/P3b for block_proposal in MBPS mode', () => { + it('disables P3/P3b for block_proposal in MBPS mode when expectedBlockProposalsPerSlot is 0', () => { + const factory = new TopicScoreParamsFactory({ + ...standardParams, + blockDurationMs: 10000, + expectedBlockProposalsPerSlot: 0, + }); + const params = factory.createForTopic(TopicType.block_proposal); + + expect(params.meshMessageDeliveriesWeight).toBe(0); + expect(params.meshFailurePenaltyWeight).toBe(0); + }); + + it('enables P3/P3b for block_proposal when expectedBlockProposalsPerSlot is positive', () => { + const factory = new TopicScoreParamsFactory({ + ...standardParams, + blockDurationMs: 10000, + expectedBlockProposalsPerSlot: 3, + }); + const params = factory.createForTopic(TopicType.block_proposal); + + expect(params.meshMessageDeliveriesWeight).toBeLessThan(0); + expect(params.meshFailurePenaltyWeight).toBeLessThan(0); + }); + + it('falls back to blocksPerSlot - 1 for block_proposal when expectedBlockProposalsPerSlot is undefined', () => { const factory = new TopicScoreParamsFactory({ ...standardParams, blockDurationMs: 10000 }); const params = factory.createForTopic(TopicType.block_proposal); + // MBPS mode with no override: falls back to blocksPerSlot - 1 > 0, so P3 is enabled expect(params.meshMessageDeliveriesWeight).toBeLessThan(0); expect(params.meshFailurePenaltyWeight).toBeLessThan(0); }); @@ -447,33 +502,42 @@ describe('Topic Score Params', () => { expect(Math.abs(maxP3)).toBeGreaterThan(maxP1 + maxP2); }); - it('total P3b across all topics is approximately -102', () => { - const factory = new TopicScoreParamsFactory(standardParams); + it('total P3b is -102 when block proposal scoring is enabled (3 topics)', () => { + const factory = new TopicScoreParamsFactory({ + ...standardParams, + blockDurationMs: 4000, + expectedBlockProposalsPerSlot: 3, + }); - // Topics with P3 enabled: checkpoint_proposal, checkpoint_attestation, block_proposal (in MBPS) - const mbpsParams = { ...standardParams, blockDurationMs: 4000 }; - const mbpsFactory = new TopicScoreParamsFactory(mbpsParams); + expect(factory.numP3EnabledTopics).toBe(3); + expect(factory.totalMaxP3bPenalty).toBeCloseTo(-102, 0); const checkpointParams = factory.createForTopic(TopicType.checkpoint_proposal); const attestationParams = factory.createForTopic(TopicType.checkpoint_attestation); - const blockParams = mbpsFactory.createForTopic(TopicType.block_proposal); + const blockParams = factory.createForTopic(TopicType.block_proposal); - // Calculate max P3 for each topic const p3Checkpoint = checkpointParams.meshMessageDeliveriesThreshold ** 2 * checkpointParams.meshMessageDeliveriesWeight; const p3Attestation = attestationParams.meshMessageDeliveriesThreshold ** 2 * attestationParams.meshMessageDeliveriesWeight; const p3Block = blockParams.meshMessageDeliveriesThreshold ** 2 * blockParams.meshMessageDeliveriesWeight; - // Each should be approximately -34 expect(p3Checkpoint).toBeCloseTo(-34, 0); expect(p3Attestation).toBeCloseTo(-34, 0); expect(p3Block).toBeCloseTo(-34, 0); - - // Total should be approximately -102 expect(p3Checkpoint + p3Attestation + p3Block).toBeCloseTo(-102, 0); }); + it('total P3b is -68 when block proposal scoring is disabled (2 topics)', () => { + const factory = new TopicScoreParamsFactory({ + ...standardParams, + expectedBlockProposalsPerSlot: 0, + }); + + expect(factory.numP3EnabledTopics).toBe(2); + expect(factory.totalMaxP3bPenalty).toBeCloseTo(-68, 0); + }); + it('non-contributing peer has negative topic score and gets pruned', () => { const factory = new TopicScoreParamsFactory(standardParams); const params = factory.createForTopic(TopicType.checkpoint_proposal); diff --git a/yarn-project/p2p/src/services/gossipsub/topic_score_params.ts b/yarn-project/p2p/src/services/gossipsub/topic_score_params.ts index aca68a536843..b7fce0e87b46 100644 --- a/yarn-project/p2p/src/services/gossipsub/topic_score_params.ts +++ b/yarn-project/p2p/src/services/gossipsub/topic_score_params.ts @@ -15,6 +15,8 @@ export type TopicScoringNetworkParams = { targetCommitteeSize: number; /** Duration per block in milliseconds when building multiple blocks per slot. If undefined, single block mode. */ blockDurationMs?: number; + /** Expected number of block proposals per slot for scoring override. 0 disables scoring, undefined falls back to blocksPerSlot - 1. */ + expectedBlockProposalsPerSlot?: number; }; /** @@ -89,18 +91,41 @@ export function computeThreshold(convergence: number, conservativeFactor: number return convergence * conservativeFactor; } +/** + * Determines the effective expected block proposals per slot for scoring. + * Returns undefined if scoring should be disabled, or a positive number if enabled. + * + * @param blocksPerSlot - Number of blocks per slot from timetable + * @param expectedBlockProposalsPerSlot - Config override. 0 disables scoring, undefined falls back to blocksPerSlot - 1. + * @returns Positive number of expected block proposals, or undefined if scoring is disabled + */ +export function getEffectiveBlockProposalsPerSlot( + blocksPerSlot: number, + expectedBlockProposalsPerSlot?: number, +): number | undefined { + if (expectedBlockProposalsPerSlot !== undefined) { + return expectedBlockProposalsPerSlot > 0 ? expectedBlockProposalsPerSlot : undefined; + } + // Fallback: In MBPS mode, N-1 block proposals per slot (last one bundled with checkpoint) + // In single block mode (blocksPerSlot=1), this is 0 → disabled + const fallback = Math.max(0, blocksPerSlot - 1); + return fallback > 0 ? fallback : undefined; +} + /** * Gets the expected messages per slot for a given topic type. * * @param topicType - The topic type * @param targetCommitteeSize - Target committee size * @param blocksPerSlot - Number of blocks per slot + * @param expectedBlockProposalsPerSlot - Override for block proposals. 0 disables scoring, undefined falls back to blocksPerSlot - 1. * @returns Expected messages per slot, or undefined if unpredictable */ export function getExpectedMessagesPerSlot( topicType: TopicType, targetCommitteeSize: number, blocksPerSlot: number, + expectedBlockProposalsPerSlot?: number, ): number | undefined { switch (topicType) { case TopicType.tx: @@ -108,9 +133,7 @@ export function getExpectedMessagesPerSlot( return undefined; case TopicType.block_proposal: - // In MBPS mode, N-1 block proposals per slot (last one bundled with checkpoint) - // In single block mode (blocksPerSlot=1), this is 0 - return Math.max(0, blocksPerSlot - 1); + return getEffectiveBlockProposalsPerSlot(blocksPerSlot, expectedBlockProposalsPerSlot); case TopicType.checkpoint_proposal: // Exactly 1 checkpoint proposal per slot @@ -190,10 +213,12 @@ const P2_DECAY_WINDOW_SLOTS = 2; // |P3| > 8 + 25 = 33 // // We set P3 max = -34 per topic (slightly more than P1+P2) to ensure pruning. -// With 3 topics having P3 enabled, total P3b after pruning = -102. +// The number of P3-enabled topics depends on config: by default 2 (checkpoint_proposal + +// checkpoint_attestation), or 3 if block proposal scoring is enabled via +// expectedBlockProposalsPerSlot. Total P3b after pruning = -68 (2 topics) or -102 (3 topics). // -// With appSpecificWeight=10, ~20 HighTolerance errors (-40 app score) plus max P3b (-102) -// would cross gossipThreshold (-500). This keeps non-contributors from being disconnected +// With appSpecificWeight=10, ~20 HighTolerance errors (-40 app score) plus max P3b (-68 or -102) +// would not cross gossipThreshold (-500). This keeps non-contributors from being disconnected // unless they also accrue app-level penalties. // // The weight formula ensures max penalty equals MAX_P3_PENALTY_PER_TOPIC: @@ -203,12 +228,6 @@ const P2_DECAY_WINDOW_SLOTS = 2; /** Maximum P3 penalty per topic (must exceed P1 + P2 to cause pruning) */ export const MAX_P3_PENALTY_PER_TOPIC = -(MAX_P1_SCORE + MAX_P2_SCORE + 1); // -34 -/** Number of topics with P3 enabled in MBPS mode (block_proposal + checkpoint_proposal + checkpoint_attestation) */ -export const NUM_P3_ENABLED_TOPICS = 3; - -/** Total maximum P3b penalty across all topics after pruning in MBPS mode */ -export const TOTAL_MAX_P3B_PENALTY = MAX_P3_PENALTY_PER_TOPIC * NUM_P3_ENABLED_TOPICS; // -102 - /** * Factory class for creating gossipsub topic scoring parameters. * Computes shared values once and reuses them across all topics. @@ -384,6 +403,18 @@ export class TopicScoreParamsFactory { }); } + /** Number of topics with P3 enabled, computed from config. Always 2 (checkpoint_proposal + checkpoint_attestation) plus optionally block_proposal. */ + get numP3EnabledTopics(): number { + const blockProposalP3Enabled = + getEffectiveBlockProposalsPerSlot(this.blocksPerSlot, this.params.expectedBlockProposalsPerSlot) !== undefined; + return blockProposalP3Enabled ? 3 : 2; + } + + /** Total maximum P3b penalty across all topics after pruning, computed from config */ + get totalMaxP3bPenalty(): number { + return MAX_P3_PENALTY_PER_TOPIC * this.numP3EnabledTopics; + } + /** * Creates topic score parameters for a specific topic type. * @@ -391,7 +422,12 @@ export class TopicScoreParamsFactory { * @returns TopicScoreParams for the topic */ createForTopic(topicType: TopicType): ReturnType { - const expectedPerSlot = getExpectedMessagesPerSlot(topicType, this.params.targetCommitteeSize, this.blocksPerSlot); + const expectedPerSlot = getExpectedMessagesPerSlot( + topicType, + this.params.targetCommitteeSize, + this.blocksPerSlot, + this.params.expectedBlockProposalsPerSlot, + ); // For unpredictable topics (tx) or topics with 0 expected messages, disable P3/P3b if (expectedPerSlot === undefined || expectedPerSlot === 0) { diff --git a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts index b27818562789..617e5bb7db11 100644 --- a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts +++ b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts @@ -341,6 +341,7 @@ export class LibP2PService extends heartbeatIntervalMs: config.gossipsubInterval, targetCommitteeSize: l1Constants.targetCommitteeSize, blockDurationMs: config.blockDurationMs, + expectedBlockProposalsPerSlot: config.expectedBlockProposalsPerSlot, }); const node = await createLibp2p({ diff --git a/yarn-project/stdlib/src/config/sequencer-config.ts b/yarn-project/stdlib/src/config/sequencer-config.ts index bc9ef0acb65b..7619cdce7e68 100644 --- a/yarn-project/stdlib/src/config/sequencer-config.ts +++ b/yarn-project/stdlib/src/config/sequencer-config.ts @@ -8,7 +8,9 @@ import type { SequencerConfig } from '../interfaces/configs.js'; * (like blockDurationMs needed by both p2p and sequencer-client) are defined here * to avoid duplication. */ -export const sharedSequencerConfigMappings: ConfigMappingsType> = { +export const sharedSequencerConfigMappings: ConfigMappingsType< + Pick +> = { blockDurationMs: { env: 'SEQ_BLOCK_DURATION_MS', description: @@ -16,4 +18,12 @@ export const sharedSequencerConfigMappings: ConfigMappingsType (val ? parseInt(val, 10) : undefined), }, + expectedBlockProposalsPerSlot: { + env: 'SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT', + description: + 'Expected number of block proposals per slot for P2P peer scoring. ' + + '0 (default) disables block proposal scoring. Set to a positive value to enable.', + parseEnv: (val: string) => (val ? parseInt(val, 10) : 0), + defaultValue: 0, + }, }; diff --git a/yarn-project/stdlib/src/interfaces/configs.ts b/yarn-project/stdlib/src/interfaces/configs.ts index 5149006d2f65..3cd2912c078f 100644 --- a/yarn-project/stdlib/src/interfaces/configs.ts +++ b/yarn-project/stdlib/src/interfaces/configs.ts @@ -65,6 +65,8 @@ export interface SequencerConfig { shuffleAttestationOrdering?: boolean; /** Duration per block in milliseconds when building multiple blocks per slot (default: undefined = single block per slot) */ blockDurationMs?: number; + /** Expected number of block proposals per slot for P2P peer scoring. 0 disables scoring, undefined falls back to blocksPerSlot - 1. */ + expectedBlockProposalsPerSlot?: number; /** Have sequencer build and publish an empty checkpoint if there are no txs */ buildCheckpointIfEmpty?: boolean; /** Skip pushing proposed blocks to archiver (default: false) */ @@ -105,6 +107,7 @@ export const SequencerConfigSchema = zodFor()( fishermanMode: z.boolean().optional(), shuffleAttestationOrdering: z.boolean().optional(), blockDurationMs: z.number().positive().optional(), + expectedBlockProposalsPerSlot: z.number().nonnegative().optional(), buildCheckpointIfEmpty: z.boolean().optional(), skipPushProposedBlocksToArchiver: z.boolean().optional(), minBlocksForCheckpoint: z.number().positive().optional(), @@ -115,6 +118,7 @@ export const SequencerConfigSchema = zodFor()( type SequencerConfigOptionalKeys = | 'governanceProposerPayload' | 'blockDurationMs' + | 'expectedBlockProposalsPerSlot' | 'coinbase' | 'feeRecipient' | 'acvmWorkingDirectory'