Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ describe('e2e_p2p_broadcasted_invalid_block_proposal_slash', () => {
ethereumSlotDuration: ETHEREUM_SLOT_DURATION,
aztecSlotDuration: AZTEC_SLOT_DURATION,
aztecTargetCommitteeSize: COMMITTEE_SIZE,
enableProposerPipelining: true,
inboxLag: 2,
aztecProofSubmissionEpochs: 1024, // effectively do not reorg
slashInactivityConsecutiveEpochThreshold: 32, // effectively do not slash for inactivity
minTxsPerBlock: 0, // always be building
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ describe('e2e_p2p_data_withholding_slash', () => {
slashAmountLarge: slashingUnit * 3n,
slashSelfAllowed: true,
minTxsPerBlock: 0,
enableProposerPipelining: true,
inboxLag: 2,
},
});

Expand Down Expand Up @@ -165,6 +167,11 @@ describe('e2e_p2p_data_withholding_slash', () => {

// Re-create the nodes.
// ASSUMING they sync in the middle of the epoch, they will "see" the reorg, and try to slash.
// Reset minTxsPerBlock to 0 so re-created validators build empty checkpoints. Under proposer
// pipelining, the vote-offenses signature is bound to the target slot and the multicall is only
// delayed to the target slot start when a checkpoint is being proposed; without a proposal,
// votes would mine in the current wall-clock slot, causing the EIP-712 signature verification to fail.
t.ctx.aztecNodeConfig.minTxsPerBlock = 0;
t.logger.warn('Re-creating nodes');
nodes = await createNodes(
t.ctx.aztecNodeConfig,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ describe('e2e_p2p_duplicate_attestation_slash', () => {
slashDuplicateProposalPenalty: slashingUnit,
slashDuplicateAttestationPenalty: slashingUnit,
slashingOffsetInRounds: 1,
enableProposerPipelining: true,
inboxLag: 2,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AztecNodeService } from '@aztec/aztec-node';
import type { TestAztecNodeService } from '@aztec/aztec-node/test';
import { EthAddress } from '@aztec/aztec.js/addresses';
import { EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
import { retryUntil } from '@aztec/foundation/retry';
import { bufferToHex } from '@aztec/foundation/string';
import { OffenseType } from '@aztec/slasher';
import { TopicType } from '@aztec/stdlib/p2p';
Expand Down Expand Up @@ -79,6 +80,8 @@ describe('e2e_p2p_duplicate_proposal_slash', () => {
blockDurationMs: BLOCK_DURATION * 1000,
slashDuplicateProposalPenalty: slashingUnit,
slashingOffsetInRounds: 1,
enableProposerPipelining: true,
inboxLag: 2,
},
});

Expand Down Expand Up @@ -220,28 +223,47 @@ describe('e2e_p2p_duplicate_proposal_slash', () => {
t.logger.warn('Starting all sequencers');
await Promise.all(nodes.map(n => n.getSequencer()!.start()));

// Now warp to the target epoch — sequencers are already running
t.logger.warn(`Advancing to target epoch ${targetEpoch}`);
await t.ctx.cheatCodes.rollup.advanceToEpoch(targetEpoch);
// Now warp to one slot before the target epoch — sequencers are already running.
// Under proposer pipelining, the malicious proposers begin building for the first
// slot of the target epoch one slot earlier; warping to the start of the epoch
// would force both AVM-heavy duplicate proposals to serialize past the slot
// boundary, after which honest receivers reject them as late.
t.logger.warn(`Advancing to one slot before target epoch ${targetEpoch}`);
await t.ctx.cheatCodes.rollup.advanceToEpoch(targetEpoch, { offset: -AZTEC_SLOT_DURATION });

// Wait for offense to be detected
// The honest nodes should detect the duplicate proposal from the malicious validator
// Wait for offense to be detected. Under proposer pipelining, checkpoint proposals are broadcast
// at the slot boundary while the receivers' wall clocks may have already advanced past the build
// slot — when that happens, honest nodes reject the gossip with "invalid slot number" before
// duplicate detection runs, so DUPLICATE_PROPOSAL is only observed by whichever node managed to
// process both proposals while still in the build slot (often the other malicious node, since
// they receive each other's broadcasts immediately). We therefore collect offenses from every
// node in the network and assert that at least one of them recorded the duplicate proposal.
t.logger.warn('Waiting for duplicate proposal offense to be detected...');
const offenses = await awaitOffenseDetected({
await awaitOffenseDetected({
epochDuration: t.ctx.aztecNodeConfig.aztecEpochDuration,
logger: t.logger,
nodeAdmin: honestNode1, // Use honest node to check for offenses
nodeAdmin: honestNode1,
slashingRoundSize,
waitUntilOffenseCount: 1,
timeoutSeconds: AZTEC_SLOT_DURATION * 16,
});

t.logger.warn(`Collected offenses`, { offenses });
// Poll every node for DUPLICATE_PROPOSAL offenses, retrying briefly so any node that detected
// the duplicate after the initial offense was collected has time to flush it through the
// slasher's offenses-collector.
const proposalOffenses = await retryUntil(
async () => {
const allOffenses = (await Promise.all(nodes.map(n => n.getSlashOffenses('all')))).flat();
const filtered = allOffenses.filter(o => o.offenseType === OffenseType.DUPLICATE_PROPOSAL);
if (filtered.length > 0) {
return filtered;
}
},
'duplicate proposal offense',
AZTEC_SLOT_DURATION * 4,
);

// Filter to only DUPLICATE_PROPOSAL offenses. The two malicious nodes sharing the same key
// will also each self-attest to their own (different) checkpoint proposals, which causes honest
// nodes to detect a DUPLICATE_ATTESTATION as well. We only care about proposals here.
const proposalOffenses = offenses.filter(o => o.offenseType === OffenseType.DUPLICATE_PROPOSAL);
t.logger.warn(`Collected duplicate proposal offenses`, { proposalOffenses });
expect(proposalOffenses.length).toBeGreaterThan(0);
for (const offense of proposalOffenses) {
expect(offense.validator.toString()).toEqual(maliciousValidatorAddress.toString());
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/src/e2e_p2p/gossip_network.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ describe('e2e_p2p_network', () => {
slashingRoundSizeInEpochs: 2,
slashingQuorum: 5,
listenAddress: '127.0.0.1',
enableProposerPipelining: true,
inboxLag: 2,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ describe('e2e_p2p_network', () => {
// Without this, no blocks are built until txs arrive, and a failed checkpoint during tx
// submission causes block pruning that invalidates tx references.
minTxsPerBlock: 0,
enableProposerPipelining: true,
inboxLag: 2,
},
});

Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/src/e2e_p2p/inactivity_slash_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ export class P2PInactivityTest {
basePort: BOOT_NODE_UDP_PORT,
startProverNode: true,
initialConfig: {
enableProposerPipelining: true,
inboxLag: 2,
anvilSlotsInAnEpoch: 4,
proverNodeConfig: { proverNodeEpochProvingDelayMs: AZTEC_SLOT_DURATION * 1000 },
aztecTargetCommitteeSize: COMMITTEE_SIZE,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('e2e_p2p_inactivity_slash_with_consecutive_epochs', () => {
return offenses.length > 0 ? offenses : undefined;
},
'slash offenses',
slashInactivityConsecutiveEpochThreshold * aztecEpochDuration * aztecSlotDuration * 2,
slashInactivityConsecutiveEpochThreshold * aztecEpochDuration * aztecSlotDuration * 4,
);
expect(unique(offenses.map(o => o.validator.toString()))).toEqual([offlineValidator.toString()]);
expect(unique(offenses.map(o => o.offenseType))).toEqual([OffenseType.INACTIVITY]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ describe('e2e_p2p_multiple_validators_sentinel', () => {
slashingRoundSizeInEpochs: 2,
sentinelEnabled: true,
slashInactivityPenalty: 0n, // Set to 0 to disable
enableProposerPipelining: true,
inboxLag: 2,
},
});

Expand Down Expand Up @@ -103,7 +105,20 @@ describe('e2e_p2p_multiple_validators_sentinel', () => {
});

it('collects attestations for all validators on a node', async () => {
// Ensure all nodes see each other, especially the sentinel, before starting slot counting
await t.waitForP2PMeshConnectivity([...nodes, sentinel]);

// Wait until validator nodes have advanced past their first proposed slot so that the
// pipelining warm-up period (where some attestations may be missed) is behind us.
await t.monitor.run();
const warmupSlot = Number(t.monitor.l2SlotNumber) + 1;
t.logger.info(`Waiting for warmup slot ${warmupSlot} before establishing initial slot`);
await retryUntil(
async () => (await t.monitor.run()).l2SlotNumber >= warmupSlot,
'warmup slot',
AZTEC_SLOT_DURATION * 3,
);

const { checkpointNumber: initialBlock, l2SlotNumber: initialSlot } = t.monitor;

const timeout = AZTEC_SLOT_DURATION * SLOT_COUNT * 4;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ describe('e2e_p2p_preferred_network', () => {
p2pDisableStatusHandshake: false,
// Just for testing be aggressive here, don't allow any auth handshake failures
p2pMaxFailedAuthAttemptsAllowed: 0,
minTxsPerBlock: 0,
enableProposerPipelining: true,
inboxLag: 2,
},
});

Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/src/e2e_p2p/rediscovery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ describe('e2e_p2p_rediscovery', () => {
...SHORTENED_BLOCK_TIME_CONFIG_NO_PRUNES,
aztecSlotDuration: 24,
listenAddress: '127.0.0.1',
enableProposerPipelining: true,
inboxLag: 2,
},
});
await t.setup();
Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/src/e2e_p2p/reex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ describe('e2e_p2p_reex', () => {
txTimeoutMs: 30_000,
listenAddress: '127.0.0.1',
aztecProofSubmissionEpochs: 1024, // effectively do not reorg
enableProposerPipelining: true,
inboxLag: 2,
},
});

Expand Down
5 changes: 4 additions & 1 deletion yarn-project/end-to-end/src/e2e_p2p/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,10 @@ export async function awaitCommitteeKicked({
expect(attesterInfo.status).toEqual(1); // Validating
}

const timeout = slashingRoundSize * 2 * aztecSlotDuration + 30;
// Allow up to four round-lengths so that under proposer pipelining, where individual rounds
// sometimes fail to gather quorum because parts of the committee miss votes due to chain-state
// races, we still see a later round execute the slash.
const timeout = slashingRoundSize * 4 * aztecSlotDuration + 30;
logger.info(`Waiting for slash to be executed (timeout ${timeout}s)`);
await awaitProposalExecution(slashingProposer, timeout, logger);

Expand Down
2 changes: 2 additions & 0 deletions yarn-project/end-to-end/src/e2e_p2p/slash_veto_demo.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ describe('veto slash', () => {
aztecProofSubmissionEpochs: 1024, // effectively do not reorg
listenAddress: '127.0.0.1',
minTxsPerBlock: 0,
enableProposerPipelining: true,
inboxLag: 2,
aztecEpochDuration: EPOCH_DURATION,
sentinelEnabled: true,
slashSelfAllowed: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ describe('e2e_p2p_governance_proposer', () => {
governanceProposerRoundSize: 10,
activationThreshold: 10n ** 22n,
ejectionThreshold: 5n ** 22n,
enableProposerPipelining: true,
inboxLag: 2,
minTxsPerBlock: 0,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ describe('e2e_p2p_valid_epoch_pruned_slash', () => {
slashAmountMedium: slashingUnit * 2n,
slashAmountLarge: slashingUnit * 3n,
aztecTargetCommitteeSize: COMMITTEE_SIZE,
enableProposerPipelining: true,
inboxLag: 2,
},
});

Expand Down
16 changes: 16 additions & 0 deletions yarn-project/end-to-end/src/e2e_p2p/validators_sentinel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ describe('e2e_p2p_validators_sentinel', () => {
slashingRoundSizeInEpochs: 2,
sentinelEnabled: true,
slashInactivityPenalty: 0n, // Set to 0 to disable
enableProposerPipelining: true,
inboxLag: 2,
},
});

Expand Down Expand Up @@ -85,6 +87,20 @@ describe('e2e_p2p_validators_sentinel', () => {
describe('with an offline validator', () => {
let stats: ValidatorsStats;
beforeAll(async () => {
// Ensure all running validator nodes see each other before starting block counting.
await t.waitForP2PMeshConnectivity(nodes);

// Wait until validator nodes have advanced past their first proposed slot so that the
// pipelining warm-up period (where some attestations may be missed) is behind us.
await t.monitor.run();
const warmupSlot = Number(t.monitor.l2SlotNumber) + 1;
t.logger.info(`Waiting for warmup slot ${warmupSlot} before establishing initial block`);
await retryUntil(
async () => (await t.monitor.run()).l2SlotNumber >= warmupSlot,
'warmup slot',
AZTEC_SLOT_DURATION * 3,
);

const currentBlock = t.monitor.checkpointNumber;
const blockCount = BLOCK_COUNT;
const timeout = AZTEC_SLOT_DURATION * blockCount * 8;
Expand Down
Loading