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
10 changes: 8 additions & 2 deletions yarn-project/archiver/src/archiver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,11 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
},
private readonly blobClient: BlobClientInterface,
instrumentation: ArchiverInstrumentation,
protected override readonly l1Constants: L1RollupConstants & { l1StartBlockHash: Buffer32; genesisArchiveRoot: Fr },
protected override readonly l1Constants: L1RollupConstants & {
l1StartBlockHash: Buffer32;
genesisArchiveRoot: Fr;
rollupManaLimit?: number;
},
synchronizer: ArchiverL1Synchronizer,
events: ArchiverEmitter,
l2TipsCache?: L2TipsCache,
Expand All @@ -133,7 +137,9 @@ export class Archiver extends ArchiverDataSourceBase implements L2BlockSink, Tra
this.synchronizer = synchronizer;
this.events = events;
this.l2TipsCache = l2TipsCache ?? new L2TipsCache(this.dataStore.blockStore);
this.updater = new ArchiverDataStoreUpdater(this.dataStore, this.l2TipsCache);
this.updater = new ArchiverDataStoreUpdater(this.dataStore, this.l2TipsCache, {
rollupManaLimit: l1Constants.rollupManaLimit,
});

// Running promise starts with a small interval inbetween runs, so all iterations needed for the initial sync
// are done as fast as possible. This then gets updated once the initial sync completes.
Expand Down
3 changes: 3 additions & 0 deletions yarn-project/archiver/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,15 @@ export async function createArchiver(
genesisArchiveRoot,
slashingProposerAddress,
targetCommitteeSize,
rollupManaLimit,
] = await Promise.all([
rollup.getL1StartBlock(),
rollup.getL1GenesisTime(),
rollup.getProofSubmissionEpochs(),
rollup.getGenesisArchiveTreeRoot(),
rollup.getSlashingProposerAddress(),
rollup.getTargetCommitteeSize(),
rollup.getManaLimit(),
] as const);

const l1StartBlockHash = await publicClient
Expand All @@ -110,6 +112,7 @@ export async function createArchiver(
proofSubmissionEpochs: Number(proofSubmissionEpochs),
targetCommitteeSize,
genesisArchiveRoot: Fr.fromString(genesisArchiveRoot.toString()),
rollupManaLimit: Number(rollupManaLimit),
};

const archiverConfig = merge(
Expand Down
25 changes: 5 additions & 20 deletions yarn-project/archiver/src/modules/data_store_updater.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,15 @@ import { ContractClassPublishedEvent } from '@aztec/protocol-contracts/class-reg
import { ContractInstancePublishedEvent } from '@aztec/protocol-contracts/instance-registry';
import { AztecAddress } from '@aztec/stdlib/aztec-address';
import { L2Block } from '@aztec/stdlib/block';
import { Checkpoint } from '@aztec/stdlib/checkpoint';
import { ContractClassLog, PrivateLog } from '@aztec/stdlib/logs';
import { CheckpointHeader } from '@aztec/stdlib/rollup';
import '@aztec/stdlib/testing/jest';

import { readFileSync } from 'fs';
import { dirname, resolve } from 'path';
import { fileURLToPath } from 'url';

import { KVArchiverDataStore } from '../store/kv_archiver_store.js';
import { makePublishedCheckpoint } from '../test/mock_structs.js';
import { makeCheckpoint, makePublishedCheckpoint } from '../test/mock_structs.js';
import { ArchiverDataStoreUpdater } from './data_store_updater.js';

/** Loads the sample ContractClassPublished event payload from protocol-contracts fixtures. */
Expand Down Expand Up @@ -110,12 +108,7 @@ describe('ArchiverDataStoreUpdater', () => {
// Make sure it has a different archive root (which it will by default from random)
expect(conflictingBlock.archive.root.equals(localBlock.archive.root)).toBe(false);

const checkpointWithConflict = new Checkpoint(
conflictingBlock.archive,
CheckpointHeader.random({ slotNumber: SlotNumber(100) }),
[conflictingBlock],
CheckpointNumber(1),
);
const checkpointWithConflict = makeCheckpoint([conflictingBlock]);
const publishedCheckpoint = makePublishedCheckpoint(checkpointWithConflict, 10);

// This should detect the conflict and prune the local block
Expand All @@ -135,8 +128,7 @@ describe('ArchiverDataStoreUpdater', () => {
block.body.txEffects[0].contractClassLogs = [contractClassLog];
block.body.txEffects[0].privateLogs = [PrivateLog.fromBuffer(getSampleContractInstancePublishedEventPayload())];

const checkpoint = new Checkpoint(block.archive, CheckpointHeader.random(), [block], CheckpointNumber(1));
const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
const publishedCheckpoint = makePublishedCheckpoint(makeCheckpoint([block]), 10);

await updater.addCheckpoints([publishedCheckpoint]);

Expand Down Expand Up @@ -166,8 +158,7 @@ describe('ArchiverDataStoreUpdater', () => {
await updater.addProposedBlocks([block]);

// Create checkpoint with the SAME block (same archive root)
const checkpoint = new Checkpoint(block.archive, CheckpointHeader.random(), [block], CheckpointNumber(1));
const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
const publishedCheckpoint = makePublishedCheckpoint(makeCheckpoint([block]), 10);

await updater.addCheckpoints([publishedCheckpoint]);

Expand Down Expand Up @@ -196,13 +187,7 @@ describe('ArchiverDataStoreUpdater', () => {
});
expect(checkpointBlock.archive.root.equals(localBlock.archive.root)).toBe(false);

const checkpoint = new Checkpoint(
checkpointBlock.archive,
CheckpointHeader.random({ slotNumber: SlotNumber(100) }),
[checkpointBlock],
CheckpointNumber(1),
);
await updater.addCheckpoints([makePublishedCheckpoint(checkpoint, 10)]);
await updater.addCheckpoints([makePublishedCheckpoint(makeCheckpoint([checkpointBlock]), 10)]);

// Verify checkpoint block is stored
const storedBlock = await store.getBlock(BlockNumber(1));
Expand Down
7 changes: 6 additions & 1 deletion yarn-project/archiver/src/modules/data_store_updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
ContractInstanceUpdatedEvent,
} from '@aztec/protocol-contracts/instance-registry';
import type { L2Block, ValidateCheckpointResult } from '@aztec/stdlib/block';
import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
import { type PublishedCheckpoint, validateCheckpoint } from '@aztec/stdlib/checkpoint';
import {
type ExecutablePrivateFunctionWithMembershipProof,
type UtilityFunctionWithMembershipProof,
Expand Down Expand Up @@ -48,6 +48,7 @@ export class ArchiverDataStoreUpdater {
constructor(
private store: KVArchiverDataStore,
private l2TipsCache?: L2TipsCache,
private opts: { rollupManaLimit?: number } = {},
) {}

/**
Expand Down Expand Up @@ -97,6 +98,10 @@ export class ArchiverDataStoreUpdater {
checkpoints: PublishedCheckpoint[],
pendingChainValidationStatus?: ValidateCheckpointResult,
): Promise<ReconcileCheckpointsResult> {
for (const checkpoint of checkpoints) {
validateCheckpoint(checkpoint.checkpoint, { rollupManaLimit: this.opts?.rollupManaLimit });
}

const result = await this.store.transactionAsync(async () => {
// Before adding checkpoints, check for conflicts with local blocks if any
const { prunedBlocks, lastAlreadyInsertedBlockNumber } = await this.pruneMismatchingLocalBlocks(checkpoints);
Expand Down
10 changes: 8 additions & 2 deletions yarn-project/archiver/src/modules/l1_synchronizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,19 @@ export class ArchiverL1Synchronizer implements Traceable {
private readonly epochCache: EpochCache,
private readonly dateProvider: DateProvider,
private readonly instrumentation: ArchiverInstrumentation,
private readonly l1Constants: L1RollupConstants & { l1StartBlockHash: Buffer32; genesisArchiveRoot: Fr },
private readonly l1Constants: L1RollupConstants & {
l1StartBlockHash: Buffer32;
genesisArchiveRoot: Fr;
rollupManaLimit?: number;
},
private readonly events: ArchiverEmitter,
tracer: Tracer,
l2TipsCache?: L2TipsCache,
private readonly log: Logger = createLogger('archiver:l1-sync'),
) {
this.updater = new ArchiverDataStoreUpdater(this.store, l2TipsCache);
this.updater = new ArchiverDataStoreUpdater(this.store, l2TipsCache, {
rollupManaLimit: l1Constants.rollupManaLimit,
});
this.tracer = tracer;
}

Expand Down
26 changes: 20 additions & 6 deletions yarn-project/archiver/src/test/mock_structs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,25 @@ export function makeL1PublishedData(l1BlockNumber: number): L1PublishedData {
return new L1PublishedData(BigInt(l1BlockNumber), BigInt(l1BlockNumber * 1000), makeBlockHash(l1BlockNumber));
}

/** Creates a Checkpoint from a list of blocks with a header that matches the blocks' structure. */
export function makeCheckpoint(blocks: L2Block[], checkpointNumber = CheckpointNumber(1)): Checkpoint {
const firstBlock = blocks[0];
const { slotNumber, timestamp, coinbase, feeRecipient, gasFees } = firstBlock.header.globalVariables;
return new Checkpoint(
blocks.at(-1)!.archive,
CheckpointHeader.random({
lastArchiveRoot: firstBlock.header.lastArchive.root,
slotNumber,
timestamp,
coinbase,
feeRecipient,
gasFees,
}),
blocks,
checkpointNumber,
);
}

/** Wraps a Checkpoint with L1 published data and random attestations. */
export function makePublishedCheckpoint(
checkpoint: Checkpoint,
Expand Down Expand Up @@ -301,11 +320,6 @@ export async function makeCheckpointWithLogs(
return txEffect;
});

const checkpoint = new Checkpoint(
AppendOnlyTreeSnapshot.random(),
CheckpointHeader.random(),
[block],
CheckpointNumber.fromBlockNumber(BlockNumber(blockNumber)),
);
const checkpoint = makeCheckpoint([block], CheckpointNumber.fromBlockNumber(BlockNumber(blockNumber)));
return makePublishedCheckpoint(checkpoint, blockNumber);
}
1 change: 1 addition & 0 deletions yarn-project/foundation/src/config/env_var.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export type EnvVar =
| 'SENTINEL_HISTORY_LENGTH_IN_EPOCHS'
| 'SENTINEL_HISTORIC_PROVEN_PERFORMANCE_LENGTH_IN_EPOCHS'
| 'SEQ_MAX_TX_PER_BLOCK'
| 'SEQ_MAX_TX_PER_CHECKPOINT'
| 'SEQ_MIN_TX_PER_BLOCK'
| 'SEQ_PUBLISH_TXS_WITH_PROPOSALS'
| 'SEQ_MAX_DA_BLOCK_GAS'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,70 @@ describe('CheckpointProposalValidator', () => {
getTxs: () => [],
epochCacheMock: () => mock<EpochCacheInterface>(),
});

describe('maxTxsPerBlock validation', () => {
const currentSlot = SlotNumber(100);
const nextSlot = SlotNumber(101);
let epochCache: ReturnType<typeof mock<EpochCacheInterface>>;

function setupEpochCache(proposerAddress: EthAddress) {
epochCache = mock<EpochCacheInterface>();
epochCache.getCurrentAndNextSlot.mockReturnValue({ currentSlot, nextSlot });
epochCache.getProposerAttesterAddressInSlot.mockResolvedValue(proposerAddress);
}

it('rejects checkpoint proposal when last block txHashes exceed maxTxsPerBlock', async () => {
const signer = Secp256k1Signer.random();
setupEpochCache(signer.address);
const validator = new CheckpointProposalValidator(epochCache, { txsPermitted: true, maxTxsPerBlock: 2 });

const header = makeCheckpointHeader(0, { slotNumber: currentSlot });
const proposal = await makeCheckpointProposalAdapter({
blockHeader: header,
lastBlockHeader: header,
signer,
txHashes: Array.from({ length: 3 }, () => TxHash.random()),
});

const result = await validator.validate(proposal);
expect(result).toEqual({ result: 'reject', severity: expect.anything() });
});

it('accepts checkpoint proposal when last block txHashes are within maxTxsPerBlock', async () => {
const signer = Secp256k1Signer.random();
setupEpochCache(signer.address);
const validator = new CheckpointProposalValidator(epochCache, { txsPermitted: true, maxTxsPerBlock: 5 });

const header = makeCheckpointHeader(0, { slotNumber: currentSlot });
const proposal = await makeCheckpointProposalAdapter({
blockHeader: header,
lastBlockHeader: header,
signer,
txHashes: Array.from({ length: 3 }, () => TxHash.random()),
});

const result = await validator.validate(proposal);
expect(result).toEqual({ result: 'accept' });
});

it('skips maxTxsPerBlock check when not configured', async () => {
const signer = Secp256k1Signer.random();
setupEpochCache(signer.address);
const validator = new CheckpointProposalValidator(epochCache, {
txsPermitted: true,
maxTxsPerBlock: undefined,
});

const header = makeCheckpointHeader(0, { slotNumber: currentSlot });
const proposal = await makeCheckpointProposalAdapter({
blockHeader: header,
lastBlockHeader: header,
signer,
txHashes: Array.from({ length: 100 }, () => TxHash.random()),
});

const result = await validator.validate(proposal);
expect(result).toEqual({ result: 'accept' });
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function sharedProposalValidatorTests<TProposal extends BlockProposal | C

beforeEach(() => {
epochCache = epochCacheMock();
validator = validatorFactory(epochCache, { txsPermitted: true });
validator = validatorFactory(epochCache, { txsPermitted: true, maxTxsPerBlock: undefined });
epochCache.getCurrentAndNextSlot.mockReturnValue({
currentSlot: currentSlot,
nextSlot: nextSlot,
Expand Down Expand Up @@ -231,7 +231,10 @@ export function sharedProposalValidatorTests<TProposal extends BlockProposal | C
describe('transaction permission validation', () => {
it('returns mid tolerance error if txs not permitted and proposal contains txHashes', async () => {
const currentProposer = getSigner();
const validatorWithTxsDisabled = validatorFactory(epochCache, { txsPermitted: false });
const validatorWithTxsDisabled = validatorFactory(epochCache, {
txsPermitted: false,
maxTxsPerBlock: undefined,
});
const header = makeHeader(1, 100, 100);
const mockProposal = await makeProposal({
blockHeader: header,
Expand All @@ -247,7 +250,10 @@ export function sharedProposalValidatorTests<TProposal extends BlockProposal | C

it('returns undefined if txs not permitted but proposal has no txHashes', async () => {
const currentProposer = getSigner();
const validatorWithTxsDisabled = validatorFactory(epochCache, { txsPermitted: false });
const validatorWithTxsDisabled = validatorFactory(epochCache, {
txsPermitted: false,
maxTxsPerBlock: undefined,
});
const header = makeHeader(1, 100, 100);
const mockProposal = await makeProposal({
blockHeader: header,
Expand Down
10 changes: 4 additions & 6 deletions yarn-project/p2p/src/services/libp2p/libp2p_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,14 +222,12 @@ export class LibP2PService extends WithTracer implements P2PService {
this.protocolVersion,
);

this.blockProposalValidator = new BlockProposalValidator(epochCache, {
const proposalValidatorOpts = {
txsPermitted: !config.disableTransactions,
maxTxsPerBlock: config.maxTxsPerBlock,
});
this.checkpointProposalValidator = new CheckpointProposalValidator(epochCache, {
txsPermitted: !config.disableTransactions,
maxTxsPerBlock: config.maxTxsPerBlock,
});
};
this.blockProposalValidator = new BlockProposalValidator(epochCache, proposalValidatorOpts);
this.checkpointProposalValidator = new CheckpointProposalValidator(epochCache, proposalValidatorOpts);
this.checkpointAttestationValidator = config.fishermanMode
? new FishermanAttestationValidator(epochCache, mempools.attestationPool, telemetry)
: new CheckpointAttestationValidator(epochCache);
Expand Down
Loading
Loading