Skip to content

Commit 1d42b8a

Browse files
Maddiaa0Rumata888
authored andcommitted
fix: attestations are requested based on their proposal not just slot (#8442)
1 parent 3302c48 commit 1d42b8a

File tree

6 files changed

+114
-26
lines changed

6 files changed

+114
-26
lines changed

yarn-project/p2p/src/attestation_pool/attestation_pool.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ export interface AttestationPool {
3636
* Retrieve all of the attestations observed pertaining to a given slot
3737
*
3838
* @param slot - The slot to query
39+
* @param proposalId - The proposal to query
3940
* @return BlockAttestations
4041
*/
41-
getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]>;
42+
getAttestationsForSlot(slot: bigint, proposalId: string): Promise<BlockAttestation[]>;
4243
}

yarn-project/p2p/src/attestation_pool/memory_attestation_pool.test.ts

+51-7
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { Fr } from '@aztec/foundation/fields';
2+
13
import { type PrivateKeyAccount } from 'viem';
24

35
import { InMemoryAttestationPool } from './memory_attestation_pool.js';
@@ -16,19 +18,22 @@ describe('MemoryAttestationPool', () => {
1618

1719
it('should add attestation to pool', async () => {
1820
const slotNumber = 420;
19-
const attestations = await Promise.all(signers.map(signer => mockAttestation(signer, slotNumber)));
21+
const archive = Fr.random();
22+
const attestations = await Promise.all(signers.map(signer => mockAttestation(signer, slotNumber, archive)));
23+
24+
const proposalId = attestations[0].p2pMessageIdentifier.toString();
2025

2126
await ap.addAttestations(attestations);
2227

23-
const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber));
28+
const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
2429

2530
expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
2631
expect(retreivedAttestations).toEqual(attestations);
2732

2833
// Delete by slot
2934
await ap.deleteAttestationsForSlot(BigInt(slotNumber));
3035

31-
const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber));
36+
const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
3237
expect(retreivedAttestationsAfterDelete.length).toBe(0);
3338
});
3439

@@ -40,8 +45,9 @@ describe('MemoryAttestationPool', () => {
4045

4146
for (const attestation of attestations) {
4247
const slot = attestation.payload.header.globalVariables.slotNumber;
48+
const proposalId = attestation.p2pMessageIdentifier.toString();
4349

44-
const retreivedAttestations = await ap.getAttestationsForSlot(slot.toBigInt());
50+
const retreivedAttestations = await ap.getAttestationsForSlot(slot.toBigInt(), proposalId);
4551
expect(retreivedAttestations.length).toBe(1);
4652
expect(retreivedAttestations[0]).toEqual(attestation);
4753
expect(retreivedAttestations[0].payload.header.globalVariables.slotNumber).toEqual(slot);
@@ -50,17 +56,55 @@ describe('MemoryAttestationPool', () => {
5056

5157
it('Should delete attestations', async () => {
5258
const slotNumber = 420;
53-
const attestations = await Promise.all(signers.map(signer => mockAttestation(signer, slotNumber)));
59+
const archive = Fr.random();
60+
const attestations = await Promise.all(signers.map(signer => mockAttestation(signer, slotNumber, archive)));
61+
const proposalId = attestations[0].p2pMessageIdentifier.toString();
5462

5563
await ap.addAttestations(attestations);
5664

57-
const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber));
65+
const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
5866
expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
5967
expect(retreivedAttestations).toEqual(attestations);
6068

6169
await ap.deleteAttestations(attestations);
6270

63-
const gottenAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber));
71+
const gottenAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
6472
expect(gottenAfterDelete.length).toBe(0);
6573
});
74+
75+
it('Should blanket delete attestations per slot', async () => {
76+
const slotNumber = 420;
77+
const archive = Fr.random();
78+
const attestations = await Promise.all(signers.map(signer => mockAttestation(signer, slotNumber, archive)));
79+
const proposalId = attestations[0].p2pMessageIdentifier.toString();
80+
81+
await ap.addAttestations(attestations);
82+
83+
const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
84+
expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
85+
expect(retreivedAttestations).toEqual(attestations);
86+
87+
await ap.deleteAttestationsForSlot(BigInt(slotNumber));
88+
89+
const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
90+
expect(retreivedAttestationsAfterDelete.length).toBe(0);
91+
});
92+
93+
it('Should blanket delete attestations per slot and proposal', async () => {
94+
const slotNumber = 420;
95+
const archive = Fr.random();
96+
const attestations = await Promise.all(signers.map(signer => mockAttestation(signer, slotNumber, archive)));
97+
const proposalId = attestations[0].p2pMessageIdentifier.toString();
98+
99+
await ap.addAttestations(attestations);
100+
101+
const retreivedAttestations = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
102+
expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
103+
expect(retreivedAttestations).toEqual(attestations);
104+
105+
await ap.deleteAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
106+
107+
const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlot(BigInt(slotNumber), proposalId);
108+
expect(retreivedAttestationsAfterDelete.length).toBe(0);
109+
});
66110
});

yarn-project/p2p/src/attestation_pool/memory_attestation_pool.ts

+50-12
Original file line numberDiff line numberDiff line change
@@ -4,30 +4,34 @@ import { createDebugLogger } from '@aztec/foundation/log';
44
import { type AttestationPool } from './attestation_pool.js';
55

66
export class InMemoryAttestationPool implements AttestationPool {
7-
private attestations: Map</*slot=*/ bigint, Map</*address=*/ string, BlockAttestation>>;
7+
private attestations: Map</*slot=*/ bigint, Map</*proposalId*/ string, Map</*address=*/ string, BlockAttestation>>>;
88

99
constructor(private log = createDebugLogger('aztec:attestation_pool')) {
1010
this.attestations = new Map();
1111
}
1212

13-
public getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]> {
13+
public getAttestationsForSlot(slot: bigint, proposalId: string): Promise<BlockAttestation[]> {
1414
const slotAttestationMap = this.attestations.get(slot);
1515
if (slotAttestationMap) {
16-
return Promise.resolve(Array.from(slotAttestationMap.values()));
17-
} else {
18-
return Promise.resolve([]);
16+
const proposalAttestationMap = slotAttestationMap.get(proposalId);
17+
if (proposalAttestationMap) {
18+
return Promise.resolve(Array.from(proposalAttestationMap.values()));
19+
}
1920
}
21+
return Promise.resolve([]);
2022
}
2123

2224
public addAttestations(attestations: BlockAttestation[]): Promise<void> {
2325
for (const attestation of attestations) {
2426
// Perf: order and group by slot before insertion
2527
const slotNumber = attestation.payload.header.globalVariables.slotNumber;
2628

29+
const proposalId = attestation.p2pMessageIdentifier.toString();
2730
const address = attestation.getSender();
2831

2932
const slotAttestationMap = getSlotOrDefault(this.attestations, slotNumber.toBigInt());
30-
slotAttestationMap.set(address.toString(), attestation);
33+
const proposalAttestationMap = getProposalOrDefault(slotAttestationMap, proposalId);
34+
proposalAttestationMap.set(address.toString(), attestation);
3135

3236
this.log.verbose(`Added attestation for slot ${slotNumber} from ${address}`);
3337
}
@@ -41,14 +45,27 @@ export class InMemoryAttestationPool implements AttestationPool {
4145
return Promise.resolve();
4246
}
4347

48+
public deleteAttestationsForSlotAndProposal(slot: bigint, proposalId: string): Promise<void> {
49+
const slotAttestationMap = this.attestations.get(slot);
50+
if (slotAttestationMap) {
51+
slotAttestationMap.delete(proposalId);
52+
this.log.verbose(`Removed attestation for slot ${slot}`);
53+
}
54+
return Promise.resolve();
55+
}
56+
4457
public deleteAttestations(attestations: BlockAttestation[]): Promise<void> {
4558
for (const attestation of attestations) {
4659
const slotNumber = attestation.payload.header.globalVariables.slotNumber;
4760
const slotAttestationMap = this.attestations.get(slotNumber.toBigInt());
4861
if (slotAttestationMap) {
49-
const address = attestation.getSender();
50-
slotAttestationMap.delete(address.toString());
51-
this.log.verbose(`Deleted attestation for slot ${slotNumber} from ${address}`);
62+
const proposalId = attestation.p2pMessageIdentifier.toString();
63+
const proposalAttestationMap = getProposalOrDefault(slotAttestationMap, proposalId);
64+
if (proposalAttestationMap) {
65+
const address = attestation.getSender();
66+
proposalAttestationMap.delete(address.toString());
67+
this.log.debug(`Deleted attestation for slot ${slotNumber} from ${address}`);
68+
}
5269
}
5370
}
5471
return Promise.resolve();
@@ -59,13 +76,34 @@ export class InMemoryAttestationPool implements AttestationPool {
5976
* Get Slot or Default
6077
*
6178
* Fetch the slot mapping, if it does not exist, then create a mapping and return it
79+
* @param map - The map to fetch from
80+
* @param slot - The slot to fetch
81+
* @returns The slot mapping
6282
*/
6383
function getSlotOrDefault(
64-
map: Map<bigint, Map<string, BlockAttestation>>,
84+
map: Map<bigint, Map<string, Map<string, BlockAttestation>>>,
6585
slot: bigint,
66-
): Map<string, BlockAttestation> {
86+
): Map<string, Map<string, BlockAttestation>> {
6787
if (!map.has(slot)) {
68-
map.set(slot, new Map<string, BlockAttestation>());
88+
map.set(slot, new Map<string, Map<string, BlockAttestation>>());
6989
}
7090
return map.get(slot)!;
7191
}
92+
93+
/**
94+
* Get Proposal or Default
95+
*
96+
* Fetch the proposal mapping, if it does not exist, then create a mapping and return it
97+
* @param map - The map to fetch from
98+
* @param proposalId - The proposal id to fetch
99+
* @returns The proposal mapping
100+
*/
101+
function getProposalOrDefault(
102+
map: Map<string, Map<string, BlockAttestation>>,
103+
proposalId: string,
104+
): Map<string, BlockAttestation> {
105+
if (!map.has(proposalId)) {
106+
map.set(proposalId, new Map<string, BlockAttestation>());
107+
}
108+
return map.get(proposalId)!;
109+
}

yarn-project/p2p/src/attestation_pool/mocks.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ export const generateAccount = () => {
2222
* @param slot The slot number the attestation is for
2323
* @returns A Block Attestation
2424
*/
25-
export const mockAttestation = async (signer: PrivateKeyAccount, slot: number = 0): Promise<BlockAttestation> => {
25+
export const mockAttestation = async (
26+
signer: PrivateKeyAccount,
27+
slot: number = 0,
28+
archive: Fr = Fr.random(),
29+
): Promise<BlockAttestation> => {
2630
// Use arbitrary numbers for all other than slot
2731
const header = makeHeader(1, 2, slot);
28-
const archive = Fr.random();
2932
const txs = [0, 1, 2, 3, 4, 5].map(() => TxHash.random());
3033

3134
const payload = new ConsensusPayload(header, archive, txs);

yarn-project/p2p/src/client/p2p_client.ts

+4-3
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ export interface P2P {
5858
* Queries the Attestation pool for attestations for the given slot
5959
*
6060
* @param slot - the slot to query
61+
* @param proposalId - the proposal id to query
6162
* @returns BlockAttestations
6263
*/
63-
getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]>;
64+
getAttestationsForSlot(slot: bigint, proposalId: string): Promise<BlockAttestation[]>;
6465

6566
/**
6667
* Registers a callback from the validator client that determines how to behave when
@@ -281,8 +282,8 @@ export class P2PClient implements P2P {
281282
return this.p2pService.propagate(proposal);
282283
}
283284

284-
public getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]> {
285-
return Promise.resolve(this.attestationPool.getAttestationsForSlot(slot));
285+
public getAttestationsForSlot(slot: bigint, proposalId: string): Promise<BlockAttestation[]> {
286+
return Promise.resolve(this.attestationPool.getAttestationsForSlot(slot, proposalId));
286287
}
287288

288289
// REVIEW: https://github.com/AztecProtocol/aztec-packages/issues/7963

yarn-project/validator-client/src/validator.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,13 @@ export class ValidatorClient implements Validator {
141141
const slot = proposal.payload.header.globalVariables.slotNumber.toBigInt();
142142
this.log.info(`Waiting for ${numberOfRequiredAttestations} attestations for slot: ${slot}`);
143143

144+
const proposalId = proposal.p2pMessageIdentifier.toString();
144145
const myAttestation = await this.validationService.attestToProposal(proposal);
145146

146147
const startTime = Date.now();
147148

148149
while (true) {
149-
const attestations = [myAttestation, ...(await this.p2pClient.getAttestationsForSlot(slot))];
150+
const attestations = [myAttestation, ...(await this.p2pClient.getAttestationsForSlot(slot, proposalId))];
150151

151152
if (attestations.length >= numberOfRequiredAttestations) {
152153
this.log.info(`Collected all ${numberOfRequiredAttestations} attestations for slot, ${slot}`);

0 commit comments

Comments
 (0)