@aztec/p2p 0.82.2 → 0.82.3-nightly.20250403
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/client/p2p_client.d.ts +5 -1
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +17 -6
- package/dest/config.d.ts +1 -1
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +1 -0
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +11 -2
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.js +40 -19
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +2 -1
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +14 -2
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +2 -1
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +8 -2
- package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/double_spend_validator.js +21 -27
- package/dest/msg_validators/tx_validator/metadata_validator.d.ts +2 -1
- package/dest/msg_validators/tx_validator/metadata_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/metadata_validator.js +14 -1
- package/dest/services/libp2p/libp2p_service.d.ts +3 -1
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +7 -4
- package/dest/test-helpers/reqresp-nodes.js +3 -3
- package/dest/testbench/p2p_client_testbench_worker.js +7 -2
- package/dest/testbench/worker_client_manager.js +1 -1
- package/package.json +10 -10
- package/src/client/p2p_client.ts +23 -7
- package/src/config.ts +1 -0
- package/src/mem_pools/attestation_pool/attestation_pool.ts +12 -2
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +40 -20
- package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +16 -1
- package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +12 -1
- package/src/msg_validators/tx_validator/double_spend_validator.ts +12 -17
- package/src/msg_validators/tx_validator/metadata_validator.ts +17 -1
- package/src/services/libp2p/libp2p_service.ts +13 -4
- package/src/test-helpers/reqresp-nodes.ts +3 -3
- package/src/testbench/p2p_client_testbench_worker.ts +2 -1
- package/src/testbench/worker_client_manager.ts +1 -1
|
@@ -64,6 +64,7 @@ import { ReqResp } from '../reqresp/reqresp.js';
|
|
|
64
64
|
* @param block - The block received from the peer.
|
|
65
65
|
* @returns The attestation for the block, if any.
|
|
66
66
|
*/ blockReceivedCallback;
|
|
67
|
+
gossipSubEventHandler;
|
|
67
68
|
constructor(clientType, config, node, peerDiscoveryService, mempools, l2BlockSource, epochCache, proofVerifier, worldStateSynchronizer, telemetry, logger = createLogger('p2p:libp2p_service')){
|
|
68
69
|
super(telemetry, 'LibP2PService'), this.clientType = clientType, this.config = config, this.node = node, this.peerDiscoveryService = peerDiscoveryService, this.mempools = mempools, this.l2BlockSource = l2BlockSource, this.proofVerifier = proofVerifier, this.worldStateSynchronizer = worldStateSynchronizer, this.logger = logger, this.jobQueue = new SerialQueue(), this.trustedPeersIds = [];
|
|
69
70
|
const peerScoring = new PeerScoring(config);
|
|
@@ -76,6 +77,7 @@ import { ReqResp } from '../reqresp/reqresp.js';
|
|
|
76
77
|
this.node.services.pubsub.score.params.appSpecificWeight = 10;
|
|
77
78
|
this.attestationValidator = new AttestationValidator(epochCache);
|
|
78
79
|
this.blockProposalValidator = new BlockProposalValidator(epochCache);
|
|
80
|
+
this.gossipSubEventHandler = this.handleGossipSubEvent.bind(this);
|
|
79
81
|
this.blockReceivedCallback = async (block)=>{
|
|
80
82
|
this.logger.debug(`Handler not yet registered: Block received callback not set. Received block for slot ${block.slotNumber.toNumber()} from peer.`, {
|
|
81
83
|
p2pMessageIdentifier: await block.p2pMessageIdentifier()
|
|
@@ -230,7 +232,7 @@ import { ReqResp } from '../reqresp/reqresp.js';
|
|
|
230
232
|
[ReqRespSubProtocol.BLOCK]: blockHandler.bind(this)
|
|
231
233
|
};
|
|
232
234
|
// add GossipSub listener
|
|
233
|
-
this.node.services.pubsub.addEventListener(GossipSubEvent.MESSAGE, this.
|
|
235
|
+
this.node.services.pubsub.addEventListener(GossipSubEvent.MESSAGE, this.gossipSubEventHandler);
|
|
234
236
|
// Start running promise for peer discovery
|
|
235
237
|
this.discoveryRunningPromise = new RunningPromise(()=>this.peerManager.heartbeat(), this.logger, this.config.peerCheckIntervalMS);
|
|
236
238
|
this.discoveryRunningPromise.start();
|
|
@@ -253,7 +255,7 @@ import { ReqResp } from '../reqresp/reqresp.js';
|
|
|
253
255
|
* @returns An empty promise.
|
|
254
256
|
*/ async stop() {
|
|
255
257
|
// Remove gossip sub listener
|
|
256
|
-
this.node.services.pubsub.removeEventListener(GossipSubEvent.MESSAGE, this.
|
|
258
|
+
this.node.services.pubsub.removeEventListener(GossipSubEvent.MESSAGE, this.gossipSubEventHandler);
|
|
257
259
|
// Stop peer manager
|
|
258
260
|
this.logger.debug('Stopping peer manager...');
|
|
259
261
|
await this.peerManager.stop();
|
|
@@ -383,7 +385,8 @@ import { ReqResp } from '../reqresp/reqresp.js';
|
|
|
383
385
|
tx
|
|
384
386
|
]);
|
|
385
387
|
}
|
|
386
|
-
/**
|
|
388
|
+
/**
|
|
389
|
+
* Process Attestation From Peer
|
|
387
390
|
* When a proposal is received from a peer, we add it to the attestation pool, so it can be accessed by other services.
|
|
388
391
|
*
|
|
389
392
|
* @param attestation - The attestation to process.
|
|
@@ -536,7 +539,7 @@ import { ReqResp } from '../reqresp/reqresp.js';
|
|
|
536
539
|
severity: PeerErrorSeverity.HighToleranceError
|
|
537
540
|
},
|
|
538
541
|
metadataValidator: {
|
|
539
|
-
validator: new MetadataTxValidator(new Fr(this.config.l1ChainId), new Fr(blockNumber)),
|
|
542
|
+
validator: new MetadataTxValidator(new Fr(this.config.l1ChainId), new Fr(this.config.rollupVersion), new Fr(blockNumber)),
|
|
540
543
|
severity: PeerErrorSeverity.HighToleranceError
|
|
541
544
|
},
|
|
542
545
|
proofValidator: {
|
|
@@ -30,10 +30,10 @@ import { convertToMultiaddr, createLibP2PPeerIdFromPrivateKey } from '../util.js
|
|
|
30
30
|
start,
|
|
31
31
|
addresses: {
|
|
32
32
|
listen: [
|
|
33
|
-
`/ip4/
|
|
33
|
+
`/ip4/127.0.0.1/tcp/${port}`
|
|
34
34
|
],
|
|
35
35
|
announce: [
|
|
36
|
-
`/ip4/
|
|
36
|
+
`/ip4/127.0.0.1/tcp/${port}`
|
|
37
37
|
]
|
|
38
38
|
},
|
|
39
39
|
connectionEncryption: [
|
|
@@ -173,7 +173,7 @@ export function createBootstrapNodeConfig(privateKey, port, chainConfig) {
|
|
|
173
173
|
dataDirectory: undefined,
|
|
174
174
|
dataStoreMapSizeKB: 0,
|
|
175
175
|
bootstrapNodes: [],
|
|
176
|
-
listenAddress: '
|
|
176
|
+
listenAddress: '127.0.0.1'
|
|
177
177
|
};
|
|
178
178
|
}
|
|
179
179
|
export function createBootstrapNodeFromPrivateKey(privateKey, port, telemetry = getTelemetryClient(), chainConfig = emptyChainConfig) {
|
|
@@ -35,12 +35,17 @@ function mockAttestationPool() {
|
|
|
35
35
|
deleteAttestationsOlderThan: ()=>Promise.resolve(),
|
|
36
36
|
deleteAttestationsForSlot: ()=>Promise.resolve(),
|
|
37
37
|
deleteAttestationsForSlotAndProposal: ()=>Promise.resolve(),
|
|
38
|
-
getAttestationsForSlot: ()=>Promise.resolve([])
|
|
38
|
+
getAttestationsForSlot: ()=>Promise.resolve([]),
|
|
39
|
+
getAttestationsForSlotAndProposal: ()=>Promise.resolve([])
|
|
39
40
|
};
|
|
40
41
|
}
|
|
41
42
|
function mockEpochCache() {
|
|
42
43
|
return {
|
|
43
|
-
getCommittee: ()=>Promise.resolve(
|
|
44
|
+
getCommittee: ()=>Promise.resolve({
|
|
45
|
+
committee: [],
|
|
46
|
+
seed: 1n,
|
|
47
|
+
epoch: 0n
|
|
48
|
+
}),
|
|
44
49
|
getProposerIndexEncoding: ()=>'0x',
|
|
45
50
|
getEpochAndSlotNow: ()=>({
|
|
46
51
|
epoch: 0n,
|
|
@@ -11,7 +11,7 @@ const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
|
11
11
|
const workerPath = path.join(__dirname, '../../dest/testbench/p2p_client_testbench_worker.js');
|
|
12
12
|
const testChainConfig = {
|
|
13
13
|
l1ChainId: 31337,
|
|
14
|
-
|
|
14
|
+
rollupVersion: 1,
|
|
15
15
|
l1Contracts: {
|
|
16
16
|
rollupAddress: EthAddress.random()
|
|
17
17
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/p2p",
|
|
3
|
-
"version": "0.82.
|
|
3
|
+
"version": "0.82.3-nightly.20250403",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -65,14 +65,14 @@
|
|
|
65
65
|
]
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@aztec/constants": "0.82.
|
|
69
|
-
"@aztec/epoch-cache": "0.82.
|
|
70
|
-
"@aztec/foundation": "0.82.
|
|
71
|
-
"@aztec/kv-store": "0.82.
|
|
72
|
-
"@aztec/noir-protocol-circuits-types": "0.82.
|
|
73
|
-
"@aztec/protocol-contracts": "0.82.
|
|
74
|
-
"@aztec/stdlib": "0.82.
|
|
75
|
-
"@aztec/telemetry-client": "0.82.
|
|
68
|
+
"@aztec/constants": "0.82.3-nightly.20250403",
|
|
69
|
+
"@aztec/epoch-cache": "0.82.3-nightly.20250403",
|
|
70
|
+
"@aztec/foundation": "0.82.3-nightly.20250403",
|
|
71
|
+
"@aztec/kv-store": "0.82.3-nightly.20250403",
|
|
72
|
+
"@aztec/noir-protocol-circuits-types": "0.82.3-nightly.20250403",
|
|
73
|
+
"@aztec/protocol-contracts": "0.82.3-nightly.20250403",
|
|
74
|
+
"@aztec/stdlib": "0.82.3-nightly.20250403",
|
|
75
|
+
"@aztec/telemetry-client": "0.82.3-nightly.20250403",
|
|
76
76
|
"@chainsafe/discv5": "9.0.0",
|
|
77
77
|
"@chainsafe/enr": "3.0.0",
|
|
78
78
|
"@chainsafe/libp2p-gossipsub": "13.0.0",
|
|
@@ -101,7 +101,7 @@
|
|
|
101
101
|
"xxhash-wasm": "^1.1.0"
|
|
102
102
|
},
|
|
103
103
|
"devDependencies": {
|
|
104
|
-
"@aztec/archiver": "0.82.
|
|
104
|
+
"@aztec/archiver": "0.82.3-nightly.20250403",
|
|
105
105
|
"@jest/globals": "^29.5.0",
|
|
106
106
|
"@types/jest": "^29.5.0",
|
|
107
107
|
"@types/node": "^18.14.6",
|
package/src/client/p2p_client.ts
CHANGED
|
@@ -187,6 +187,7 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
|
|
|
187
187
|
private synchedBlockHashes: AztecAsyncMap<number, string>;
|
|
188
188
|
private synchedLatestBlockNumber: AztecAsyncSingleton<number>;
|
|
189
189
|
private synchedProvenBlockNumber: AztecAsyncSingleton<number>;
|
|
190
|
+
private synchedLatestSlot: AztecAsyncSingleton<bigint>;
|
|
190
191
|
|
|
191
192
|
private txPool: TxPool;
|
|
192
193
|
private attestationPool: T extends P2PClientType.Full ? AttestationPool : undefined;
|
|
@@ -236,6 +237,7 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
|
|
|
236
237
|
this.synchedBlockHashes = store.openMap('p2p_pool_block_hashes');
|
|
237
238
|
this.synchedLatestBlockNumber = store.openSingleton('p2p_pool_last_l2_block');
|
|
238
239
|
this.synchedProvenBlockNumber = store.openSingleton('p2p_pool_last_proven_l2_block');
|
|
240
|
+
this.synchedLatestSlot = store.openSingleton('p2p_pool_last_l2_slot');
|
|
239
241
|
|
|
240
242
|
this.txPool = mempools.txPool;
|
|
241
243
|
this.attestationPool = mempools.attestationPool!;
|
|
@@ -293,12 +295,12 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
|
|
|
293
295
|
break;
|
|
294
296
|
case 'chain-proven': {
|
|
295
297
|
const from = (await this.getSyncedProvenBlockNum()) + 1;
|
|
296
|
-
const limit = event.
|
|
298
|
+
const limit = event.block.number - from + 1;
|
|
297
299
|
await this.handleProvenL2Blocks(await this.l2BlockSource.getBlocks(from, limit));
|
|
298
300
|
break;
|
|
299
301
|
}
|
|
300
302
|
case 'chain-pruned':
|
|
301
|
-
await this.handlePruneL2Blocks(event.
|
|
303
|
+
await this.handlePruneL2Blocks(event.block.number);
|
|
302
304
|
break;
|
|
303
305
|
default: {
|
|
304
306
|
const _: never = event;
|
|
@@ -380,8 +382,16 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
|
|
|
380
382
|
return this.p2pService.propagate(proposal);
|
|
381
383
|
}
|
|
382
384
|
|
|
383
|
-
public async getAttestationsForSlot(slot: bigint, proposalId
|
|
384
|
-
return (
|
|
385
|
+
public async getAttestationsForSlot(slot: bigint, proposalId?: string): Promise<BlockAttestation[]> {
|
|
386
|
+
return (
|
|
387
|
+
(await (proposalId
|
|
388
|
+
? this.attestationPool?.getAttestationsForSlotAndProposal(slot, proposalId)
|
|
389
|
+
: this.attestationPool?.getAttestationsForSlot(slot))) ?? []
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
public addAttestation(attestation: BlockAttestation): Promise<void> {
|
|
394
|
+
return this.attestationPool?.addAttestations([attestation]) ?? Promise.resolve();
|
|
385
395
|
}
|
|
386
396
|
|
|
387
397
|
// REVIEW: https://github.com/AztecProtocol/aztec-packages/issues/7963
|
|
@@ -592,6 +602,11 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
|
|
|
592
602
|
return (await this.synchedProvenBlockNumber.getAsync()) ?? INITIAL_L2_BLOCK_NUM - 1;
|
|
593
603
|
}
|
|
594
604
|
|
|
605
|
+
/** Returns latest L2 slot for which we have seen an L2 block. */
|
|
606
|
+
public async getSyncedLatestSlot(): Promise<bigint> {
|
|
607
|
+
return (await this.synchedLatestSlot.getAsync()) ?? BigInt(0);
|
|
608
|
+
}
|
|
609
|
+
|
|
595
610
|
/**
|
|
596
611
|
* Method to check the status the p2p client.
|
|
597
612
|
* @returns Information about p2p client status: state & syncedToBlockNum.
|
|
@@ -659,12 +674,13 @@ export class P2PClient<T extends P2PClientType = P2PClientType.Full>
|
|
|
659
674
|
|
|
660
675
|
await this.markTxsAsMinedFromBlocks(blocks.map(b => b.block));
|
|
661
676
|
await this.addAttestationsToPool(blocks);
|
|
662
|
-
const
|
|
677
|
+
const lastBlock = blocks.at(-1)!.block;
|
|
663
678
|
await Promise.all(
|
|
664
679
|
blocks.map(async block => this.synchedBlockHashes.set(block.block.number, (await block.block.hash()).toString())),
|
|
665
680
|
);
|
|
666
|
-
await this.synchedLatestBlockNumber.set(
|
|
667
|
-
this.
|
|
681
|
+
await this.synchedLatestBlockNumber.set(lastBlock.number);
|
|
682
|
+
await this.synchedLatestSlot.set(lastBlock.header.getSlot());
|
|
683
|
+
this.log.verbose(`Synched to latest block ${lastBlock.number}`);
|
|
668
684
|
await this.startServiceIfSynched();
|
|
669
685
|
}
|
|
670
686
|
|
package/src/config.ts
CHANGED
|
@@ -50,7 +50,17 @@ export interface AttestationPool {
|
|
|
50
50
|
deleteAttestationsForSlotAndProposal(slot: bigint, proposalId: string): Promise<void>;
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* Get Attestations for slot
|
|
53
|
+
* Get all Attestations for all proposals for a given slot
|
|
54
|
+
*
|
|
55
|
+
* Retrieve all of the attestations observed pertaining to a given slot
|
|
56
|
+
*
|
|
57
|
+
* @param slot - The slot to query
|
|
58
|
+
* @return BlockAttestations
|
|
59
|
+
*/
|
|
60
|
+
getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Get Attestations for slot and given proposal
|
|
54
64
|
*
|
|
55
65
|
* Retrieve all of the attestations observed pertaining to a given slot
|
|
56
66
|
*
|
|
@@ -58,5 +68,5 @@ export interface AttestationPool {
|
|
|
58
68
|
* @param proposalId - The proposal to query
|
|
59
69
|
* @return BlockAttestations
|
|
60
70
|
*/
|
|
61
|
-
|
|
71
|
+
getAttestationsForSlotAndProposal(slot: bigint, proposalId: string): Promise<BlockAttestation[]>;
|
|
62
72
|
}
|
|
@@ -45,25 +45,45 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
45
45
|
it('should add attestations to pool', async () => {
|
|
46
46
|
const slotNumber = 420;
|
|
47
47
|
const archive = Fr.random();
|
|
48
|
-
const attestations = await Promise.all(
|
|
48
|
+
const attestations = await Promise.all(
|
|
49
|
+
signers.slice(0, -1).map(signer => mockAttestation(signer, slotNumber, archive)),
|
|
50
|
+
);
|
|
49
51
|
|
|
50
52
|
await ap.addAttestations(attestations);
|
|
51
53
|
|
|
52
54
|
// Check metrics have been updated.
|
|
53
55
|
expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(attestations.length);
|
|
54
56
|
|
|
55
|
-
const
|
|
57
|
+
const retrievedAttestations = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), archive.toString());
|
|
58
|
+
expect(retrievedAttestations.length).toBe(attestations.length);
|
|
59
|
+
compareAttestations(retrievedAttestations, attestations);
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
const retrievedAttestationsForSlot = await ap.getAttestationsForSlot(BigInt(slotNumber));
|
|
62
|
+
expect(retrievedAttestationsForSlot.length).toBe(attestations.length);
|
|
63
|
+
compareAttestations(retrievedAttestationsForSlot, attestations);
|
|
58
64
|
|
|
59
|
-
|
|
65
|
+
// Add another one
|
|
66
|
+
const newAttestation = await mockAttestation(signers[NUMBER_OF_SIGNERS_PER_TEST - 1], slotNumber, archive);
|
|
67
|
+
await ap.addAttestations([newAttestation]);
|
|
68
|
+
expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(1);
|
|
69
|
+
const retrievedAttestationsAfterAdd = await ap.getAttestationsForSlotAndProposal(
|
|
70
|
+
BigInt(slotNumber),
|
|
71
|
+
archive.toString(),
|
|
72
|
+
);
|
|
73
|
+
expect(retrievedAttestationsAfterAdd.length).toBe(attestations.length + 1);
|
|
74
|
+
compareAttestations(retrievedAttestationsAfterAdd, [...attestations, newAttestation]);
|
|
75
|
+
const retrievedAttestationsForSlotAfterAdd = await ap.getAttestationsForSlot(BigInt(slotNumber));
|
|
76
|
+
expect(retrievedAttestationsForSlotAfterAdd.length).toBe(attestations.length + 1);
|
|
77
|
+
compareAttestations(retrievedAttestationsForSlotAfterAdd, [...attestations, newAttestation]);
|
|
60
78
|
|
|
61
79
|
// Delete by slot
|
|
62
80
|
await ap.deleteAttestationsForSlot(BigInt(slotNumber));
|
|
81
|
+
expect(metricsMock.recordRemovedObjects).toHaveBeenCalledWith(attestations.length + 1);
|
|
63
82
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
83
|
+
const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlotAndProposal(
|
|
84
|
+
BigInt(slotNumber),
|
|
85
|
+
archive.toString(),
|
|
86
|
+
);
|
|
67
87
|
expect(retreivedAttestationsAfterDelete.length).toBe(0);
|
|
68
88
|
});
|
|
69
89
|
|
|
@@ -82,7 +102,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
82
102
|
// Add them to store and check we end up with only one
|
|
83
103
|
await ap.addAttestations(attestations);
|
|
84
104
|
|
|
85
|
-
const retreivedAttestations = await ap.
|
|
105
|
+
const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), archive.toString());
|
|
86
106
|
expect(retreivedAttestations.length).toBe(1);
|
|
87
107
|
expect(retreivedAttestations[0].toBuffer()).toEqual(attestations[0].toBuffer());
|
|
88
108
|
expect(retreivedAttestations[0].payload.txHashes).toEqual(txs);
|
|
@@ -90,7 +110,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
90
110
|
|
|
91
111
|
// Try adding them on another operation and check they are still not duplicated
|
|
92
112
|
await ap.addAttestations([attestations[0]]);
|
|
93
|
-
expect(await ap.
|
|
113
|
+
expect(await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), archive.toString())).toHaveLength(1);
|
|
94
114
|
});
|
|
95
115
|
|
|
96
116
|
it('should store attestations by differing slot', async () => {
|
|
@@ -103,7 +123,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
103
123
|
const slot = attestation.payload.header.globalVariables.slotNumber;
|
|
104
124
|
const archive = attestation.archive.toString();
|
|
105
125
|
|
|
106
|
-
const retreivedAttestations = await ap.
|
|
126
|
+
const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(slot.toBigInt(), archive);
|
|
107
127
|
expect(retreivedAttestations.length).toBe(1);
|
|
108
128
|
expect(retreivedAttestations[0].toBuffer()).toEqual(attestation.toBuffer());
|
|
109
129
|
expect(retreivedAttestations[0].payload.header.globalVariables.slotNumber).toEqual(slot);
|
|
@@ -123,7 +143,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
123
143
|
const slot = attestation.payload.header.globalVariables.slotNumber;
|
|
124
144
|
const proposalId = attestation.archive.toString();
|
|
125
145
|
|
|
126
|
-
const retreivedAttestations = await ap.
|
|
146
|
+
const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(slot.toBigInt(), proposalId);
|
|
127
147
|
expect(retreivedAttestations.length).toBe(1);
|
|
128
148
|
expect(retreivedAttestations[0].toBuffer()).toEqual(attestation.toBuffer());
|
|
129
149
|
expect(retreivedAttestations[0].payload.header.globalVariables.slotNumber).toEqual(slot);
|
|
@@ -140,7 +160,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
140
160
|
|
|
141
161
|
expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(attestations.length);
|
|
142
162
|
|
|
143
|
-
const retreivedAttestations = await ap.
|
|
163
|
+
const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
|
|
144
164
|
expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
|
|
145
165
|
compareAttestations(retreivedAttestations, attestations);
|
|
146
166
|
|
|
@@ -148,7 +168,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
148
168
|
|
|
149
169
|
expect(metricsMock.recordRemovedObjects).toHaveBeenCalledWith(attestations.length);
|
|
150
170
|
|
|
151
|
-
const gottenAfterDelete = await ap.
|
|
171
|
+
const gottenAfterDelete = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
|
|
152
172
|
expect(gottenAfterDelete.length).toBe(0);
|
|
153
173
|
});
|
|
154
174
|
|
|
@@ -160,13 +180,13 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
160
180
|
|
|
161
181
|
await ap.addAttestations(attestations);
|
|
162
182
|
|
|
163
|
-
const retreivedAttestations = await ap.
|
|
183
|
+
const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
|
|
164
184
|
expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
|
|
165
185
|
compareAttestations(retreivedAttestations, attestations);
|
|
166
186
|
|
|
167
187
|
await ap.deleteAttestationsForSlot(BigInt(slotNumber));
|
|
168
188
|
|
|
169
|
-
const retreivedAttestationsAfterDelete = await ap.
|
|
189
|
+
const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
|
|
170
190
|
expect(retreivedAttestationsAfterDelete.length).toBe(0);
|
|
171
191
|
});
|
|
172
192
|
|
|
@@ -187,7 +207,7 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
187
207
|
expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(attestations.length);
|
|
188
208
|
expect(metricsMock.recordAddedObjects).toHaveBeenCalledWith(attestations2.length);
|
|
189
209
|
|
|
190
|
-
const retreivedAttestations = await ap.
|
|
210
|
+
const retreivedAttestations = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
|
|
191
211
|
expect(retreivedAttestations.length).toBe(NUMBER_OF_SIGNERS_PER_TEST);
|
|
192
212
|
compareAttestations(retreivedAttestations, attestations);
|
|
193
213
|
|
|
@@ -195,10 +215,10 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
195
215
|
|
|
196
216
|
expect(metricsMock.recordRemovedObjects).toHaveBeenCalledWith(attestations.length);
|
|
197
217
|
|
|
198
|
-
const retreivedAttestationsAfterDelete = await ap.
|
|
218
|
+
const retreivedAttestationsAfterDelete = await ap.getAttestationsForSlotAndProposal(BigInt(slotNumber), proposalId);
|
|
199
219
|
expect(retreivedAttestationsAfterDelete.length).toBe(0);
|
|
200
220
|
|
|
201
|
-
const retreivedAttestationsAfterDeleteForOtherProposal = await ap.
|
|
221
|
+
const retreivedAttestationsAfterDeleteForOtherProposal = await ap.getAttestationsForSlotAndProposal(
|
|
202
222
|
BigInt(slotNumber),
|
|
203
223
|
proposalId2,
|
|
204
224
|
);
|
|
@@ -215,14 +235,14 @@ export function describeAttestationPool(getAttestationPool: () => AttestationPoo
|
|
|
215
235
|
|
|
216
236
|
await ap.addAttestations(attestations);
|
|
217
237
|
|
|
218
|
-
const attestationsForSlot1 = await ap.
|
|
238
|
+
const attestationsForSlot1 = await ap.getAttestationsForSlotAndProposal(BigInt(1), proposalId);
|
|
219
239
|
expect(attestationsForSlot1.length).toBe(signers.length);
|
|
220
240
|
|
|
221
241
|
const deleteAttestationsSpy = jest.spyOn(ap, 'deleteAttestationsForSlot');
|
|
222
242
|
|
|
223
243
|
await ap.deleteAttestationsOlderThan(BigInt(73));
|
|
224
244
|
|
|
225
|
-
const attestationsForSlot1AfterDelete = await ap.
|
|
245
|
+
const attestationsForSlot1AfterDelete = await ap.getAttestationsForSlotAndProposal(BigInt(1), proposalId);
|
|
226
246
|
expect(attestationsForSlot1AfterDelete.length).toBe(0);
|
|
227
247
|
|
|
228
248
|
expect(deleteAttestationsSpy).toHaveBeenCalledTimes(5);
|
|
@@ -60,6 +60,9 @@ export class KvAttestationPool implements AttestationPool {
|
|
|
60
60
|
|
|
61
61
|
this.log.verbose(`Added attestation for slot ${slotNumber.toBigInt()} from ${address}`, {
|
|
62
62
|
signature: attestation.signature.toString(),
|
|
63
|
+
slotNumber,
|
|
64
|
+
address,
|
|
65
|
+
proposalId,
|
|
63
66
|
});
|
|
64
67
|
}
|
|
65
68
|
});
|
|
@@ -67,7 +70,19 @@ export class KvAttestationPool implements AttestationPool {
|
|
|
67
70
|
this.metrics.recordAddedObjects(attestations.length);
|
|
68
71
|
}
|
|
69
72
|
|
|
70
|
-
public async getAttestationsForSlot(slot: bigint
|
|
73
|
+
public async getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]> {
|
|
74
|
+
const slotFr = new Fr(slot);
|
|
75
|
+
const proposalIds = await toArray(this.proposalsForSlot.getValuesAsync(slotFr.toString()));
|
|
76
|
+
const attestations: BlockAttestation[] = [];
|
|
77
|
+
|
|
78
|
+
for (const proposalId of proposalIds) {
|
|
79
|
+
attestations.push(...(await this.getAttestationsForSlotAndProposal(slot, proposalId)));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return attestations;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
public async getAttestationsForSlotAndProposal(slot: bigint, proposalId: string): Promise<BlockAttestation[]> {
|
|
71
86
|
const attestationIds = await toArray(
|
|
72
87
|
this.attestationsForProposal.getValuesAsync(this.getProposalKey(slot, proposalId)),
|
|
73
88
|
);
|
|
@@ -15,7 +15,15 @@ export class InMemoryAttestationPool implements AttestationPool {
|
|
|
15
15
|
this.metrics = new PoolInstrumentation(telemetry, PoolName.ATTESTATION_POOL);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
public getAttestationsForSlot(slot: bigint
|
|
18
|
+
public getAttestationsForSlot(slot: bigint): Promise<BlockAttestation[]> {
|
|
19
|
+
return Promise.resolve(
|
|
20
|
+
Array.from(this.attestations.get(slot)?.values() ?? []).flatMap(proposalAttestationMap =>
|
|
21
|
+
Array.from(proposalAttestationMap.values()),
|
|
22
|
+
),
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public getAttestationsForSlotAndProposal(slot: bigint, proposalId: string): Promise<BlockAttestation[]> {
|
|
19
27
|
const slotAttestationMap = this.attestations.get(slot);
|
|
20
28
|
if (slotAttestationMap) {
|
|
21
29
|
const proposalAttestationMap = slotAttestationMap.get(proposalId);
|
|
@@ -40,6 +48,9 @@ export class InMemoryAttestationPool implements AttestationPool {
|
|
|
40
48
|
|
|
41
49
|
this.log.verbose(`Added attestation for slot ${slotNumber.toBigInt()} from ${address}`, {
|
|
42
50
|
signature: attestation.signature.toString(),
|
|
51
|
+
slotNumber,
|
|
52
|
+
address,
|
|
53
|
+
proposalId,
|
|
43
54
|
});
|
|
44
55
|
}
|
|
45
56
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createLogger } from '@aztec/foundation/log';
|
|
2
|
-
import { type AnyTx, Tx, type TxValidationResult, type TxValidator
|
|
2
|
+
import { type AnyTx, Tx, type TxValidationResult, type TxValidator } from '@aztec/stdlib/tx';
|
|
3
3
|
|
|
4
4
|
export interface NullifierSource {
|
|
5
5
|
nullifiersExist: (nullifiers: Buffer[]) => Promise<boolean[]>;
|
|
@@ -14,25 +14,20 @@ export class DoubleSpendTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
async validateTx(tx: T): Promise<TxValidationResult> {
|
|
17
|
-
|
|
18
|
-
// because the AVM will perform merkle insertions as it goes and will fail on
|
|
19
|
-
// duplicate nullifier. In fact we CANNOT check here because the nullifiers
|
|
20
|
-
// have already been inserted, and so they will exist in nullifierSource.
|
|
21
|
-
if (!hasPublicCalls(tx)) {
|
|
22
|
-
const nullifiers = tx instanceof Tx ? tx.data.getNonEmptyNullifiers() : tx.txEffect.nullifiers;
|
|
17
|
+
const nullifiers = tx instanceof Tx ? tx.data.getNonEmptyNullifiers() : tx.txEffect.nullifiers;
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
// Ditch this tx if it has repeated nullifiers
|
|
20
|
+
const uniqueNullifiers = new Set(nullifiers);
|
|
21
|
+
if (uniqueNullifiers.size !== nullifiers.length) {
|
|
22
|
+
this.#log.warn(`Rejecting tx ${await Tx.getHash(tx)} for emitting duplicate nullifiers`);
|
|
23
|
+
return { result: 'invalid', reason: ['Duplicate nullifier in tx'] };
|
|
24
|
+
}
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
26
|
+
if ((await this.#nullifierSource.nullifiersExist(nullifiers.map(n => n.toBuffer()))).some(Boolean)) {
|
|
27
|
+
this.#log.warn(`Rejecting tx ${await Tx.getHash(tx)} for repeating a nullifier`);
|
|
28
|
+
return { result: 'invalid', reason: ['Existing nullifier'] };
|
|
35
29
|
}
|
|
30
|
+
|
|
36
31
|
return { result: 'valid' };
|
|
37
32
|
}
|
|
38
33
|
}
|
|
@@ -5,13 +5,16 @@ import { type AnyTx, Tx, type TxValidationResult, type TxValidator } from '@azte
|
|
|
5
5
|
export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
6
6
|
#log = createLogger('p2p:tx_validator:tx_metadata');
|
|
7
7
|
|
|
8
|
-
constructor(private chainId: Fr, private blockNumber: Fr) {}
|
|
8
|
+
constructor(private chainId: Fr, private rollupVersion: Fr, private blockNumber: Fr) {}
|
|
9
9
|
|
|
10
10
|
async validateTx(tx: T): Promise<TxValidationResult> {
|
|
11
11
|
const errors = [];
|
|
12
12
|
if (!(await this.#hasCorrectChainId(tx))) {
|
|
13
13
|
errors.push('Incorrect chain id');
|
|
14
14
|
}
|
|
15
|
+
if (!(await this.#hasCorrectRollupVersion(tx))) {
|
|
16
|
+
errors.push('Incorrect rollup version');
|
|
17
|
+
}
|
|
15
18
|
if (!(await this.#isValidForBlockNumber(tx))) {
|
|
16
19
|
errors.push('Invalid block number');
|
|
17
20
|
}
|
|
@@ -45,4 +48,17 @@ export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
|
45
48
|
return true;
|
|
46
49
|
}
|
|
47
50
|
}
|
|
51
|
+
|
|
52
|
+
async #hasCorrectRollupVersion(tx: T): Promise<boolean> {
|
|
53
|
+
if (!tx.data.constants.txContext.version.equals(this.rollupVersion)) {
|
|
54
|
+
this.#log.warn(
|
|
55
|
+
`Rejecting tx ${await Tx.getHash(
|
|
56
|
+
tx,
|
|
57
|
+
)} because of incorrect rollup version ${tx.data.constants.txContext.version.toNumber()} != ${this.rollupVersion.toNumber()}`,
|
|
58
|
+
);
|
|
59
|
+
return false;
|
|
60
|
+
} else {
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
48
64
|
}
|
|
@@ -102,6 +102,8 @@ export class LibP2PService<T extends P2PClientType> extends WithTracer implement
|
|
|
102
102
|
*/
|
|
103
103
|
private blockReceivedCallback: (block: BlockProposal) => Promise<BlockAttestation | undefined>;
|
|
104
104
|
|
|
105
|
+
private gossipSubEventHandler: (e: CustomEvent<GossipsubMessage>) => void;
|
|
106
|
+
|
|
105
107
|
constructor(
|
|
106
108
|
private clientType: T,
|
|
107
109
|
private config: P2PConfig,
|
|
@@ -139,6 +141,8 @@ export class LibP2PService<T extends P2PClientType> extends WithTracer implement
|
|
|
139
141
|
this.attestationValidator = new AttestationValidator(epochCache);
|
|
140
142
|
this.blockProposalValidator = new BlockProposalValidator(epochCache);
|
|
141
143
|
|
|
144
|
+
this.gossipSubEventHandler = this.handleGossipSubEvent.bind(this);
|
|
145
|
+
|
|
142
146
|
this.blockReceivedCallback = async (block: BlockProposal): Promise<BlockAttestation | undefined> => {
|
|
143
147
|
this.logger.debug(
|
|
144
148
|
`Handler not yet registered: Block received callback not set. Received block for slot ${block.slotNumber.toNumber()} from peer.`,
|
|
@@ -329,7 +333,7 @@ export class LibP2PService<T extends P2PClientType> extends WithTracer implement
|
|
|
329
333
|
};
|
|
330
334
|
|
|
331
335
|
// add GossipSub listener
|
|
332
|
-
this.node.services.pubsub.addEventListener(GossipSubEvent.MESSAGE, this.
|
|
336
|
+
this.node.services.pubsub.addEventListener(GossipSubEvent.MESSAGE, this.gossipSubEventHandler);
|
|
333
337
|
|
|
334
338
|
// Start running promise for peer discovery
|
|
335
339
|
this.discoveryRunningPromise = new RunningPromise(
|
|
@@ -360,7 +364,7 @@ export class LibP2PService<T extends P2PClientType> extends WithTracer implement
|
|
|
360
364
|
*/
|
|
361
365
|
public async stop() {
|
|
362
366
|
// Remove gossip sub listener
|
|
363
|
-
this.node.services.pubsub.removeEventListener(GossipSubEvent.MESSAGE, this.
|
|
367
|
+
this.node.services.pubsub.removeEventListener(GossipSubEvent.MESSAGE, this.gossipSubEventHandler);
|
|
364
368
|
|
|
365
369
|
// Stop peer manager
|
|
366
370
|
this.logger.debug('Stopping peer manager...');
|
|
@@ -521,7 +525,8 @@ export class LibP2PService<T extends P2PClientType> extends WithTracer implement
|
|
|
521
525
|
await this.mempools.txPool.addTxs([tx]);
|
|
522
526
|
}
|
|
523
527
|
|
|
524
|
-
/**
|
|
528
|
+
/**
|
|
529
|
+
* Process Attestation From Peer
|
|
525
530
|
* When a proposal is received from a peer, we add it to the attestation pool, so it can be accessed by other services.
|
|
526
531
|
*
|
|
527
532
|
* @param attestation - The attestation to process.
|
|
@@ -716,7 +721,11 @@ export class LibP2PService<T extends P2PClientType> extends WithTracer implement
|
|
|
716
721
|
severity: PeerErrorSeverity.HighToleranceError,
|
|
717
722
|
},
|
|
718
723
|
metadataValidator: {
|
|
719
|
-
validator: new MetadataTxValidator(
|
|
724
|
+
validator: new MetadataTxValidator(
|
|
725
|
+
new Fr(this.config.l1ChainId),
|
|
726
|
+
new Fr(this.config.rollupVersion),
|
|
727
|
+
new Fr(blockNumber),
|
|
728
|
+
),
|
|
720
729
|
severity: PeerErrorSeverity.HighToleranceError,
|
|
721
730
|
},
|
|
722
731
|
proofValidator: {
|
|
@@ -55,8 +55,8 @@ export async function createLibp2pNode(
|
|
|
55
55
|
const options: Libp2pOptions = {
|
|
56
56
|
start,
|
|
57
57
|
addresses: {
|
|
58
|
-
listen: [`/ip4/
|
|
59
|
-
announce: [`/ip4/
|
|
58
|
+
listen: [`/ip4/127.0.0.1/tcp/${port}`],
|
|
59
|
+
announce: [`/ip4/127.0.0.1/tcp/${port}`],
|
|
60
60
|
},
|
|
61
61
|
connectionEncryption: [noise()],
|
|
62
62
|
streamMuxers: [yamux()],
|
|
@@ -239,7 +239,7 @@ export function createBootstrapNodeConfig(privateKey: string, port: number, chai
|
|
|
239
239
|
dataDirectory: undefined,
|
|
240
240
|
dataStoreMapSizeKB: 0,
|
|
241
241
|
bootstrapNodes: [],
|
|
242
|
-
listenAddress: '
|
|
242
|
+
listenAddress: '127.0.0.1',
|
|
243
243
|
};
|
|
244
244
|
}
|
|
245
245
|
|
|
@@ -48,12 +48,13 @@ function mockAttestationPool(): AttestationPool {
|
|
|
48
48
|
deleteAttestationsForSlot: () => Promise.resolve(),
|
|
49
49
|
deleteAttestationsForSlotAndProposal: () => Promise.resolve(),
|
|
50
50
|
getAttestationsForSlot: () => Promise.resolve([]),
|
|
51
|
+
getAttestationsForSlotAndProposal: () => Promise.resolve([]),
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
function mockEpochCache(): EpochCacheInterface {
|
|
55
56
|
return {
|
|
56
|
-
getCommittee: () => Promise.resolve([]
|
|
57
|
+
getCommittee: () => Promise.resolve({ committee: [], seed: 1n, epoch: 0n }),
|
|
57
58
|
getProposerIndexEncoding: () => '0x' as `0x${string}`,
|
|
58
59
|
getEpochAndSlotNow: () => ({ epoch: 0n, slot: 0n, ts: 0n }),
|
|
59
60
|
computeProposerIndex: () => 0n,
|