Skip to content

Commit a99880f

Browse files
authored
feat: merge-train/spartan (#20620)
BEGIN_COMMIT_OVERRIDE fix: Fix the epoch long proving test (#20617) feat: disabling peer scoring for block proposals topic (#20577) END_COMMIT_OVERRIDE
2 parents dcc4e4f + 99fae29 commit a99880f

File tree

9 files changed

+181
-45
lines changed

9 files changed

+181
-45
lines changed

yarn-project/end-to-end/src/e2e_epochs/epochs_long_proving_time.test.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ describe('e2e_epochs/epochs_long_proving_time', () => {
2424
const { aztecSlotDuration } = EpochsTestContext.getSlotDurations({ aztecEpochDuration });
2525
const epochDurationInSeconds = aztecSlotDuration * aztecEpochDuration;
2626
const proverTestDelayMs = (epochDurationInSeconds * 1000 * 3) / 4;
27-
test = await EpochsTestContext.setup({ aztecEpochDuration, aztecProofSubmissionEpochs: 8, proverTestDelayMs });
27+
test = await EpochsTestContext.setup({
28+
aztecEpochDuration,
29+
aztecProofSubmissionEpochs: 1000, // Effectively don't re-org
30+
proverTestDelayMs,
31+
proverNodeMaxPendingJobs: 1, // We test for only a single job at once
32+
});
2833
({ logger, monitor, L1_BLOCK_TIME_IN_S } = test);
2934
logger.warn(`Initialized with prover delay set to ${proverTestDelayMs}ms (epoch is ${epochDurationInSeconds}s)`);
3035
});
@@ -34,7 +39,7 @@ describe('e2e_epochs/epochs_long_proving_time', () => {
3439
await test.teardown();
3540
});
3641

37-
it.skip('generates proof over multiple epochs', async () => {
42+
it('generates proof over multiple epochs', async () => {
3843
const targetProvenEpochs = process.env.TARGET_PROVEN_EPOCHS ? parseInt(process.env.TARGET_PROVEN_EPOCHS) : 1;
3944
const targetProvenBlockNumber = targetProvenEpochs * test.epochDuration;
4045
logger.info(`Waiting for ${targetProvenEpochs} epochs to be proven at ${targetProvenBlockNumber} L2 blocks`);

yarn-project/foundation/src/config/env_var.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ export type EnvVar =
216216
| 'SEQ_L1_PUBLISHING_TIME_ALLOWANCE_IN_SLOT'
217217
| 'SEQ_ATTESTATION_PROPAGATION_TIME'
218218
| 'SEQ_BLOCK_DURATION_MS'
219+
| 'SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT'
219220
| 'SEQ_BUILD_CHECKPOINT_IF_EMPTY'
220221
| 'SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_COMMITTEE_MEMBER'
221222
| 'SEQ_SECONDS_BEFORE_INVALIDATING_BLOCK_AS_NON_COMMITTEE_MEMBER'

yarn-project/p2p/src/config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export interface P2PConfig
3838
ChainConfig,
3939
TxCollectionConfig,
4040
TxFileStoreConfig,
41-
Pick<SequencerConfig, 'blockDurationMs'> {
41+
Pick<SequencerConfig, 'blockDurationMs' | 'expectedBlockProposalsPerSlot'> {
4242
/** A flag dictating whether the P2P subsystem should be enabled. */
4343
p2pEnabled: boolean;
4444

yarn-project/p2p/src/services/gossipsub/README.md

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ We configure all parameters (P1-P4) with values calculated dynamically from netw
4040
| P3b: meshFailurePenalty | -34 per topic | Sticky penalty after pruning |
4141
| P4: invalidMessageDeliveries | -20 per message | Attack detection |
4242

43-
**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.
43+
**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.
4444

4545
## Exponential Decay
4646

@@ -217,7 +217,21 @@ Transactions are submitted unpredictably by users, so we cannot set meaningful d
217217

218218
### Block Proposals (block_proposal)
219219

220-
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.
220+
Block proposal scoring is controlled by the `expectedBlockProposalsPerSlot` config (`SEQ_EXPECTED_BLOCK_PROPOSALS_PER_SLOT` env var):
221+
222+
| Config Value | Behavior |
223+
|-------------|----------|
224+
| `0` (current default) | Block proposal P3 scoring is **disabled** |
225+
| Positive number | Uses the provided value as expected proposals per slot |
226+
| `undefined` | Falls back to `blocksPerSlot - 1` (MBPS mode: N-1, single block: 0) |
227+
228+
**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.
229+
230+
**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`).
231+
232+
**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.
233+
234+
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.
221235

222236
### Checkpoint Proposals (checkpoint_proposal)
223237

@@ -241,6 +255,7 @@ The scoring parameters depend on:
241255
| `targetCommitteeSize` | L1RollupConstants | 48 |
242256
| `heartbeatInterval` | P2PConfig.gossipsubInterval | 700ms |
243257
| `blockDurationMs` | P2PConfig.blockDurationMs | undefined (single block) |
258+
| `expectedBlockProposalsPerSlot` | P2PConfig.expectedBlockProposalsPerSlot | 0 (disabled; current unset-env behavior) |
244259

245260
## Invalid Message Handling (P4)
246261

@@ -320,9 +335,9 @@ Conversely, if topic scores are low, a peer slightly above the disconnect thresh
320335

321336
Topic scores provide **burst response** to attacks, while app score provides **stable baseline**:
322337

323-
- P1 (time in mesh): Max +8 per topic (+24 across 3 topics)
324-
- P2 (first deliveries): Max +25 per topic (+75 across 3 topics, but decays fast)
325-
- P3 (under-delivery): Max -34 per topic (-102 across 3 topics in MBPS; -68 in single-block mode)
338+
- P1 (time in mesh): Max +8 per topic (+16 default, +24 with block proposal scoring enabled)
339+
- P2 (first deliveries): Max +25 per topic (+50 default, +75 with block proposal scoring, but decays fast)
340+
- P3 (under-delivery): Max -34 per topic (-68 default with 2 topics, -102 with block proposal scoring enabled)
326341
- P4 (invalid messages): -20 per invalid message, can spike to -2000+ during attacks
327342

328343
Example attack scenario:
@@ -373,21 +388,21 @@ When a peer is pruned from the mesh:
373388
3. **P3b captures the penalty**: The P3 deficit at prune time becomes P3b, which decays slowly
374389

375390
After pruning, the peer's score consists mainly of P3b:
376-
- **Total P3b across 3 topics: -102** (max)
391+
- **Total P3b: -68** (default, 2 topics) or **-102** (with block proposal scoring enabled, 3 topics)
377392
- **Recovery time**: P3b decays to ~1% over one decay window (2-5 slots = 2-6 minutes)
378393
- **Grafting eligibility**: Peer can be grafted when score ≥ 0, but asymptotic decay means recovery is slow
379394

380395
### Why Non-Contributors Aren't Disconnected
381396

382-
With P3b capped at -102 total after pruning (MBPS mode). In single-block mode, the cap is -68:
397+
With P3b capped at -68 (default, 2 topics) or -102 (with block proposal scoring, 3 topics) after pruning:
383398

384399
| Threshold | Value | P3b Score | Triggered? |
385400
|-----------|-------|-----------|------------|
386-
| gossipThreshold | -500 | -102 (MBPS) / -68 (single) | No |
387-
| publishThreshold | -1000 | -102 (MBPS) / -68 (single) | No |
388-
| graylistThreshold | -2000 | -102 (MBPS) / -68 (single) | No |
401+
| gossipThreshold | -500 | -68 (default) / -102 (block scoring on) | No |
402+
| publishThreshold | -1000 | -68 (default) / -102 (block scoring on) | No |
403+
| graylistThreshold | -2000 | -68 (default) / -102 (block scoring on) | No |
389404

390-
**A score of -102 (MBPS) or -68 (single-block) is well above -500**, so non-contributing peers:
405+
**A score of -68 or -102 is well above -500**, so non-contributing peers:
391406
- Are pruned from mesh (good - stops them slowing propagation)
392407
- Still receive gossip (can recover by reconnecting/restarting)
393408
- 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
547562
While the peer is disconnected:
548563

549564
1. **P3 penalty accumulates**: The message delivery counter decays toward 0, causing increasing P3 penalty
550-
2. **Max P3 penalty reached**: Once counter drops below threshold, penalty hits -34 per topic (-102 total in MBPS; -68 single-block)
565+
2. **Max P3 penalty reached**: Once counter drops below threshold, penalty hits -34 per topic (-68 default, -102 with block proposal scoring)
551566
3. **Mesh pruning**: Topic score goes negative → peer is pruned from mesh
552567
4. **P3b captures penalty**: The P3 deficit at prune time becomes P3b (sticky penalty)
553568

@@ -569,13 +584,13 @@ Note: If the peer just joined the mesh, P3 penalties only start after
569584
During a network outage, the peer:
570585
- **Does NOT send invalid messages** → No P4 penalty
571586
- **Does NOT violate protocols** → No application-level penalty
572-
- **Only accumulates topic-level penalties** → Max -102 (P3b, MBPS) or -68 (single-block)
587+
- **Only accumulates topic-level penalties** → Max -68 (default) or -102 (with block proposal scoring)
573588

574589
This is the crucial difference from malicious behavior:
575590

576591
| Scenario | App Score | Topic Score | Total | Threshold Hit |
577592
|----------|-----------|-------------|-------|---------------|
578-
| Network outage | 0 | -102 (MBPS) / -68 (single) | -102 / -68 | None |
593+
| Network outage | 0 | -68 (default) / -102 (block scoring on) | -68 / -102 | None |
579594
| Validation failure | -50 | -20 | -520 | gossipThreshold |
580595
| Malicious peer | -100 | -2000+ | -2100+ | graylistThreshold |
581596

yarn-project/p2p/src/services/gossipsub/topic_score_params.test.ts

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
createAllTopicScoreParams,
1313
createTopicScoreParamsForTopic,
1414
getDecayWindowSlots,
15+
getEffectiveBlockProposalsPerSlot,
1516
getExpectedMessagesPerSlot,
1617
} from './topic_score_params.js';
1718

@@ -148,18 +149,47 @@ describe('Topic Score Params', () => {
148149
});
149150
});
150151

152+
describe('getEffectiveBlockProposalsPerSlot', () => {
153+
it('returns undefined when override is 0 (disabled)', () => {
154+
expect(getEffectiveBlockProposalsPerSlot(5, 0)).toBeUndefined();
155+
});
156+
157+
it('returns override value when positive', () => {
158+
expect(getEffectiveBlockProposalsPerSlot(5, 3)).toBe(3);
159+
expect(getEffectiveBlockProposalsPerSlot(1, 7)).toBe(7);
160+
});
161+
162+
it('falls back to blocksPerSlot - 1 when override is undefined', () => {
163+
expect(getEffectiveBlockProposalsPerSlot(5, undefined)).toBe(4);
164+
expect(getEffectiveBlockProposalsPerSlot(3, undefined)).toBe(2);
165+
});
166+
167+
it('returns undefined when override is undefined and single block mode', () => {
168+
expect(getEffectiveBlockProposalsPerSlot(1, undefined)).toBeUndefined();
169+
});
170+
});
171+
151172
describe('getExpectedMessagesPerSlot', () => {
152173
it('returns undefined for tx topic (unpredictable)', () => {
153174
expect(getExpectedMessagesPerSlot(TopicType.tx, 48, 5)).toBeUndefined();
154175
});
155176

156-
it('returns N-1 for block_proposal in MBPS mode', () => {
177+
it('returns N-1 for block_proposal when override is undefined (fallback)', () => {
157178
expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 5)).toBe(4);
158179
expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 3)).toBe(2);
159180
});
160181

161-
it('returns 0 for block_proposal in single block mode', () => {
162-
expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 1)).toBe(0);
182+
it('returns undefined for block_proposal in single block mode without override', () => {
183+
expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 1)).toBeUndefined();
184+
});
185+
186+
it('returns undefined for block_proposal when override is 0 (disabled)', () => {
187+
expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 5, 0)).toBeUndefined();
188+
});
189+
190+
it('returns override value for block_proposal when positive', () => {
191+
expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 1, 3)).toBe(3);
192+
expect(getExpectedMessagesPerSlot(TopicType.block_proposal, 48, 5, 7)).toBe(7);
163193
});
164194

165195
it('returns 1 for checkpoint_proposal', () => {
@@ -207,10 +237,35 @@ describe('Topic Score Params', () => {
207237
expect(params.meshFailurePenaltyWeight).toBe(0);
208238
});
209239

210-
it('enables P3/P3b for block_proposal in MBPS mode', () => {
240+
it('disables P3/P3b for block_proposal in MBPS mode when expectedBlockProposalsPerSlot is 0', () => {
241+
const factory = new TopicScoreParamsFactory({
242+
...standardParams,
243+
blockDurationMs: 10000,
244+
expectedBlockProposalsPerSlot: 0,
245+
});
246+
const params = factory.createForTopic(TopicType.block_proposal);
247+
248+
expect(params.meshMessageDeliveriesWeight).toBe(0);
249+
expect(params.meshFailurePenaltyWeight).toBe(0);
250+
});
251+
252+
it('enables P3/P3b for block_proposal when expectedBlockProposalsPerSlot is positive', () => {
253+
const factory = new TopicScoreParamsFactory({
254+
...standardParams,
255+
blockDurationMs: 10000,
256+
expectedBlockProposalsPerSlot: 3,
257+
});
258+
const params = factory.createForTopic(TopicType.block_proposal);
259+
260+
expect(params.meshMessageDeliveriesWeight).toBeLessThan(0);
261+
expect(params.meshFailurePenaltyWeight).toBeLessThan(0);
262+
});
263+
264+
it('falls back to blocksPerSlot - 1 for block_proposal when expectedBlockProposalsPerSlot is undefined', () => {
211265
const factory = new TopicScoreParamsFactory({ ...standardParams, blockDurationMs: 10000 });
212266
const params = factory.createForTopic(TopicType.block_proposal);
213267

268+
// MBPS mode with no override: falls back to blocksPerSlot - 1 > 0, so P3 is enabled
214269
expect(params.meshMessageDeliveriesWeight).toBeLessThan(0);
215270
expect(params.meshFailurePenaltyWeight).toBeLessThan(0);
216271
});
@@ -447,33 +502,42 @@ describe('Topic Score Params', () => {
447502
expect(Math.abs(maxP3)).toBeGreaterThan(maxP1 + maxP2);
448503
});
449504

450-
it('total P3b across all topics is approximately -102', () => {
451-
const factory = new TopicScoreParamsFactory(standardParams);
505+
it('total P3b is -102 when block proposal scoring is enabled (3 topics)', () => {
506+
const factory = new TopicScoreParamsFactory({
507+
...standardParams,
508+
blockDurationMs: 4000,
509+
expectedBlockProposalsPerSlot: 3,
510+
});
452511

453-
// Topics with P3 enabled: checkpoint_proposal, checkpoint_attestation, block_proposal (in MBPS)
454-
const mbpsParams = { ...standardParams, blockDurationMs: 4000 };
455-
const mbpsFactory = new TopicScoreParamsFactory(mbpsParams);
512+
expect(factory.numP3EnabledTopics).toBe(3);
513+
expect(factory.totalMaxP3bPenalty).toBeCloseTo(-102, 0);
456514

457515
const checkpointParams = factory.createForTopic(TopicType.checkpoint_proposal);
458516
const attestationParams = factory.createForTopic(TopicType.checkpoint_attestation);
459-
const blockParams = mbpsFactory.createForTopic(TopicType.block_proposal);
517+
const blockParams = factory.createForTopic(TopicType.block_proposal);
460518

461-
// Calculate max P3 for each topic
462519
const p3Checkpoint =
463520
checkpointParams.meshMessageDeliveriesThreshold ** 2 * checkpointParams.meshMessageDeliveriesWeight;
464521
const p3Attestation =
465522
attestationParams.meshMessageDeliveriesThreshold ** 2 * attestationParams.meshMessageDeliveriesWeight;
466523
const p3Block = blockParams.meshMessageDeliveriesThreshold ** 2 * blockParams.meshMessageDeliveriesWeight;
467524

468-
// Each should be approximately -34
469525
expect(p3Checkpoint).toBeCloseTo(-34, 0);
470526
expect(p3Attestation).toBeCloseTo(-34, 0);
471527
expect(p3Block).toBeCloseTo(-34, 0);
472-
473-
// Total should be approximately -102
474528
expect(p3Checkpoint + p3Attestation + p3Block).toBeCloseTo(-102, 0);
475529
});
476530

531+
it('total P3b is -68 when block proposal scoring is disabled (2 topics)', () => {
532+
const factory = new TopicScoreParamsFactory({
533+
...standardParams,
534+
expectedBlockProposalsPerSlot: 0,
535+
});
536+
537+
expect(factory.numP3EnabledTopics).toBe(2);
538+
expect(factory.totalMaxP3bPenalty).toBeCloseTo(-68, 0);
539+
});
540+
477541
it('non-contributing peer has negative topic score and gets pruned', () => {
478542
const factory = new TopicScoreParamsFactory(standardParams);
479543
const params = factory.createForTopic(TopicType.checkpoint_proposal);

0 commit comments

Comments
 (0)