From 6a3c7dd08fe44b6bab7cb421c5688f3626c57a92 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Wed, 12 Mar 2025 17:19:15 -0300 Subject: [PATCH 1/6] feat: Store attestations in archiver Downloads attestor signatures from L1 and stores them along with blocks. These are pushed into the attestation pool when new blocks are seen, to guarantee that all attestations are visible in the pool (in case some were missed via p2p). Introduces a new `PublishedL2Block` type (that replaces the `L1Published` wrapper type), and adds a `getPublishedBlocks` method to both archiver and node. Eventually we could merge this method with `getBlocks`, but I did not want to change every single client of `getBlocks` at this stage. --- .../end-to-end/src/e2e_p2p/reqresp.test.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts b/yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts index 986a8a5033be..a12404d9f752 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts @@ -1,6 +1,9 @@ +import type { Archiver } from '@aztec/archiver'; import type { AztecNodeService } from '@aztec/aztec-node'; import { sleep } from '@aztec/aztec.js'; import { RollupAbi } from '@aztec/l1-artifacts'; +import type { SequencerClient } from '@aztec/sequencer-client'; +import { BlockAttestation, ConsensusPayload } from '@aztec/stdlib/p2p'; import { jest } from '@jest/globals'; import fs from 'fs'; @@ -123,6 +126,25 @@ describe('e2e_p2p_reqresp_tx', () => { ), ); t.logger.info('All transactions mined'); + + // Gather signers from attestations downloaded from L1 + const blockNumber = await contexts[0].txs[0].getReceipt().then(r => r.blockNumber!); + const dataStore = ((nodes[0] as AztecNodeService).getBlockSource() as Archiver).dataStore; + const [block] = await dataStore.getBlocks(blockNumber, blockNumber); + const attestations = block.signatures.map( + sig => new BlockAttestation(ConsensusPayload.fromBlock(block.block), sig), + ); + const signers = await Promise.all(attestations.map(att => att.getSender().then(s => s.toString()))); + t.logger.info(`Attestation signers`, { signers }); + + // Check that the signers found are part of the proposer nodes to ensure the archiver fetched them right + const validatorAddresses = nodes.map(node => + ((node as AztecNodeService).getSequencer() as SequencerClient).validatorAddress?.toString(), + ); + t.logger.info(`Validator addresses`, { addresses: validatorAddresses }); + for (const signer of signers) { + expect(validatorAddresses).toContain(signer); + } }); /** From 48e5254faa39866d661426ea553b1a5f9fc46644 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Thu, 13 Mar 2025 14:01:51 -0300 Subject: [PATCH 2/6] Address comments from review --- .../end-to-end/src/e2e_p2p/reqresp.test.ts | 22 ------------------- 1 file changed, 22 deletions(-) diff --git a/yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts b/yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts index a12404d9f752..986a8a5033be 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/reqresp.test.ts @@ -1,9 +1,6 @@ -import type { Archiver } from '@aztec/archiver'; import type { AztecNodeService } from '@aztec/aztec-node'; import { sleep } from '@aztec/aztec.js'; import { RollupAbi } from '@aztec/l1-artifacts'; -import type { SequencerClient } from '@aztec/sequencer-client'; -import { BlockAttestation, ConsensusPayload } from '@aztec/stdlib/p2p'; import { jest } from '@jest/globals'; import fs from 'fs'; @@ -126,25 +123,6 @@ describe('e2e_p2p_reqresp_tx', () => { ), ); t.logger.info('All transactions mined'); - - // Gather signers from attestations downloaded from L1 - const blockNumber = await contexts[0].txs[0].getReceipt().then(r => r.blockNumber!); - const dataStore = ((nodes[0] as AztecNodeService).getBlockSource() as Archiver).dataStore; - const [block] = await dataStore.getBlocks(blockNumber, blockNumber); - const attestations = block.signatures.map( - sig => new BlockAttestation(ConsensusPayload.fromBlock(block.block), sig), - ); - const signers = await Promise.all(attestations.map(att => att.getSender().then(s => s.toString()))); - t.logger.info(`Attestation signers`, { signers }); - - // Check that the signers found are part of the proposer nodes to ensure the archiver fetched them right - const validatorAddresses = nodes.map(node => - ((node as AztecNodeService).getSequencer() as SequencerClient).validatorAddress?.toString(), - ); - t.logger.info(`Validator addresses`, { addresses: validatorAddresses }); - for (const signer of signers) { - expect(validatorAddresses).toContain(signer); - } }); /** From d365d880f524a17bbabd68c03fc00aec1f1f341b Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 14 Mar 2025 11:31:26 +0000 Subject: [PATCH 3/6] fix: allow for empty signatures --- yarn-project/p2p/src/client/p2p_client.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/yarn-project/p2p/src/client/p2p_client.ts b/yarn-project/p2p/src/client/p2p_client.ts index bfd1c5d41e88..0027d15dbee6 100644 --- a/yarn-project/p2p/src/client/p2p_client.ts +++ b/yarn-project/p2p/src/client/p2p_client.ts @@ -627,7 +627,7 @@ export class P2PClient private async addAttestationsToPool(blocks: PublishedL2Block[]): Promise { const attestations = blocks.flatMap(block => { const payload = ConsensusPayload.fromBlock(block.block); - return block.signatures.map(signature => new BlockAttestation(payload, signature)); + return block.signatures.filter(sig => !sig.isEmpty).map(signature => new BlockAttestation(payload, signature)); }); await this.attestationPool?.addAttestations(attestations); const slots = blocks.map(b => b.block.header.getSlot()).sort((a, b) => Number(a - b)); From 93f208b52fd7e2d55845ff1ac6b0792a66ab06fa Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 14 Mar 2025 11:33:51 +0000 Subject: [PATCH 4/6] fix: add test --- yarn-project/p2p/src/client/p2p_client.test.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index b4fbf816d64e..ebb22316c6ac 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -1,4 +1,5 @@ import { MockL2BlockSource } from '@aztec/archiver/test'; +import { Signature } from '@aztec/foundation/eth-signature'; import { Fr } from '@aztec/foundation/fields'; import { retryUntil } from '@aztec/foundation/retry'; import { sleep } from '@aztec/foundation/sleep'; @@ -259,5 +260,13 @@ describe('In-Memory P2P Client', () => { block.signatures.map(signature => expect.objectContaining({ signature })), ); }); + + it('handles empty signatures in block stream events', async () => { + await client.start(); + const block = await randomPublishedL2Block(1); + block.signatures[0] = Signature.empty(); + await client.handleBlockStreamEvent({ type: 'blocks-added', blocks: [block] }); + expect(attestationPool.addAttestations).toHaveBeenCalledWith([]); + }); }); }); From 4faf2ab578930e7abd60f0327ec0ea53e2c8c7a6 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 14 Mar 2025 11:58:07 +0000 Subject: [PATCH 5/6] chore: test handles empty attestations --- yarn-project/p2p/src/client/p2p_client.test.ts | 16 ++++++++++------ .../stdlib/src/block/published_l2_block.ts | 7 ++++++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index ebb22316c6ac..2ca2474e587b 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -9,10 +9,10 @@ import { L2Block, randomPublishedL2Block } from '@aztec/stdlib/block'; import { P2PClientType } from '@aztec/stdlib/p2p'; import { mockTx } from '@aztec/stdlib/testing'; -import { expect } from '@jest/globals'; +import { expect, jest } from '@jest/globals'; import { type MockProxy, mock } from 'jest-mock-extended'; -import type { P2PService } from '../index.js'; +import { InMemoryAttestationPool, type P2PService } from '../index.js'; import type { AttestationPool } from '../mem_pools/attestation_pool/attestation_pool.js'; import type { MemPools } from '../mem_pools/interface.js'; import type { TxPool } from '../mem_pools/tx_pool/index.js'; @@ -20,7 +20,7 @@ import { P2PClient } from './p2p_client.js'; describe('In-Memory P2P Client', () => { let txPool: MockProxy; - let attestationPool: MockProxy; + let attestationPool: AttestationPool; let mempools: MemPools; let blockSource: MockL2BlockSource; let p2pService: MockProxy; @@ -36,7 +36,7 @@ describe('In-Memory P2P Client', () => { p2pService = mock(); - attestationPool = mock(); + attestationPool = new InMemoryAttestationPool(); blockSource = new MockL2BlockSource(); await blockSource.createBlocks(100); @@ -255,8 +255,9 @@ describe('In-Memory P2P Client', () => { it('adds attestations to the pool', async () => { await client.start(); const block = await randomPublishedL2Block(1); + const addAttestationsSpy = jest.spyOn(attestationPool, 'addAttestations'); await client.handleBlockStreamEvent({ type: 'blocks-added', blocks: [block] }); - expect(attestationPool.addAttestations).toHaveBeenCalledWith( + expect(addAttestationsSpy).toHaveBeenCalledWith( block.signatures.map(signature => expect.objectContaining({ signature })), ); }); @@ -265,8 +266,11 @@ describe('In-Memory P2P Client', () => { await client.start(); const block = await randomPublishedL2Block(1); block.signatures[0] = Signature.empty(); + const addAttestationsSpy = jest.spyOn(attestationPool, 'addAttestations'); await client.handleBlockStreamEvent({ type: 'blocks-added', blocks: [block] }); - expect(attestationPool.addAttestations).toHaveBeenCalledWith([]); + expect(addAttestationsSpy).toHaveBeenCalledWith( + block.signatures.filter(sig => !sig.isEmpty).map(signature => expect.objectContaining({ signature })), + ); }); }); }); diff --git a/yarn-project/stdlib/src/block/published_l2_block.ts b/yarn-project/stdlib/src/block/published_l2_block.ts index ca4df4792559..c88e58a1a7d1 100644 --- a/yarn-project/stdlib/src/block/published_l2_block.ts +++ b/yarn-project/stdlib/src/block/published_l2_block.ts @@ -1,5 +1,6 @@ import { Buffer32 } from '@aztec/foundation/buffer'; import { times } from '@aztec/foundation/collection'; +import { Secp256k1Signer } from '@aztec/foundation/crypto'; import { Signature } from '@aztec/foundation/eth-signature'; import { schemas } from '@aztec/foundation/schemas'; import { L2Block } from '@aztec/stdlib/block'; @@ -35,6 +36,10 @@ export async function randomPublishedL2Block(l2BlockNumber: number): Promise Secp256k1Signer.random()); + const signatures = await Promise.all( + times(3, async i => signers[i].signMessage(Buffer32.fromField(await block.hash()))), + ); return { block, l1, signatures }; } From 72b64d308f48cc1bcb3ea8adc4519b57a8d5b336 Mon Sep 17 00:00:00 2001 From: Maddiaa0 <47148561+Maddiaa0@users.noreply.github.com> Date: Fri, 14 Mar 2025 12:30:18 +0000 Subject: [PATCH 6/6] fix other test --- yarn-project/p2p/src/client/p2p_client.test.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/yarn-project/p2p/src/client/p2p_client.test.ts b/yarn-project/p2p/src/client/p2p_client.test.ts index 2ca2474e587b..a205e677c3a9 100644 --- a/yarn-project/p2p/src/client/p2p_client.test.ts +++ b/yarn-project/p2p/src/client/p2p_client.test.ts @@ -237,15 +237,17 @@ describe('In-Memory P2P Client', () => { const advanceToProvenBlockNumber = 20; const keepAttestationsInPoolFor = 12; + const deleteAttestationsOlderThanSpy = jest.spyOn(attestationPool, 'deleteAttestationsOlderThan'); + blockSource.setProvenBlockNumber(0); (client as any).keepAttestationsInPoolFor = keepAttestationsInPoolFor; await client.start(); - expect(attestationPool.deleteAttestationsOlderThan).not.toHaveBeenCalled(); + expect(deleteAttestationsOlderThanSpy).not.toHaveBeenCalled(); await advanceToProvenBlock(advanceToProvenBlockNumber); - expect(attestationPool.deleteAttestationsOlderThan).toHaveBeenCalledTimes(1); - expect(attestationPool.deleteAttestationsOlderThan).toHaveBeenCalledWith( + expect(deleteAttestationsOlderThanSpy).toHaveBeenCalledTimes(1); + expect(deleteAttestationsOlderThanSpy).toHaveBeenCalledWith( BigInt(advanceToProvenBlockNumber - keepAttestationsInPoolFor), ); });