@aztec/p2p 0.55.0 → 0.55.1
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/index.d.ts +2 -2
- package/dest/client/index.d.ts.map +1 -1
- package/dest/client/index.js +43 -38
- package/dest/config.d.ts +49 -0
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +66 -2
- package/dest/index.d.ts +1 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +2 -1
- package/dest/service/libp2p_service.d.ts +10 -11
- package/dest/service/libp2p_service.d.ts.map +1 -1
- package/dest/service/libp2p_service.js +103 -18
- package/dest/service/peer_manager.d.ts +9 -13
- package/dest/service/peer_manager.d.ts.map +1 -1
- package/dest/service/peer_manager.js +15 -1
- package/dest/service/peer_scoring.d.ts +32 -0
- package/dest/service/peer_scoring.d.ts.map +1 -0
- package/dest/service/peer_scoring.js +67 -0
- package/dest/tx_validator/aggregate_tx_validator.d.ts +7 -0
- package/dest/tx_validator/aggregate_tx_validator.d.ts.map +1 -0
- package/dest/tx_validator/aggregate_tx_validator.js +23 -0
- package/dest/tx_validator/data_validator.d.ts +6 -0
- package/dest/tx_validator/data_validator.d.ts.map +1 -0
- package/dest/tx_validator/data_validator.js +47 -0
- package/dest/tx_validator/double_spend_validator.d.ts +12 -0
- package/dest/tx_validator/double_spend_validator.d.ts.map +1 -0
- package/dest/tx_validator/double_spend_validator.js +53 -0
- package/dest/tx_validator/index.d.ts +6 -0
- package/dest/tx_validator/index.d.ts.map +1 -0
- package/dest/tx_validator/index.js +6 -0
- package/dest/tx_validator/metadata_validator.d.ts +10 -0
- package/dest/tx_validator/metadata_validator.d.ts.map +1 -0
- package/dest/tx_validator/metadata_validator.js +50 -0
- package/dest/tx_validator/tx_proof_validator.d.ts +9 -0
- package/dest/tx_validator/tx_proof_validator.d.ts.map +1 -0
- package/dest/tx_validator/tx_proof_validator.js +29 -0
- package/dest/util.d.ts +7 -0
- package/dest/util.d.ts.map +1 -1
- package/dest/util.js +1 -1
- package/package.json +6 -6
- package/src/client/index.ts +65 -47
- package/src/config.ts +127 -0
- package/src/index.ts +1 -0
- package/src/service/libp2p_service.ts +137 -24
- package/src/service/peer_manager.ts +23 -4
- package/src/service/peer_scoring.ts +81 -0
- package/src/tx_validator/aggregate_tx_validator.ts +24 -0
- package/src/tx_validator/data_validator.ts +61 -0
- package/src/tx_validator/double_spend_validator.ts +65 -0
- package/src/tx_validator/index.ts +5 -0
- package/src/tx_validator/metadata_validator.ts +61 -0
- package/src/tx_validator/tx_proof_validator.ts +28 -0
- package/src/util.ts +8 -0
|
@@ -1,36 +1,49 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BlockAttestation,
|
|
3
3
|
BlockProposal,
|
|
4
|
+
type ClientProtocolCircuitVerifier,
|
|
4
5
|
type Gossipable,
|
|
6
|
+
type L2BlockSource,
|
|
7
|
+
MerkleTreeId,
|
|
5
8
|
type RawGossipMessage,
|
|
6
9
|
TopicType,
|
|
7
10
|
TopicTypeMap,
|
|
8
11
|
Tx,
|
|
9
12
|
TxHash,
|
|
13
|
+
type WorldStateSynchronizer,
|
|
10
14
|
} from '@aztec/circuit-types';
|
|
15
|
+
import { Fr } from '@aztec/circuits.js';
|
|
11
16
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
12
17
|
import { SerialQueue } from '@aztec/foundation/queue';
|
|
13
18
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
14
19
|
import type { AztecKVStore } from '@aztec/kv-store';
|
|
15
20
|
|
|
16
21
|
import { type ENR } from '@chainsafe/enr';
|
|
17
|
-
import { type
|
|
22
|
+
import { type GossipSub, type GossipSubComponents, gossipsub } from '@chainsafe/libp2p-gossipsub';
|
|
23
|
+
import { createPeerScoreParams, createTopicScoreParams } from '@chainsafe/libp2p-gossipsub/score';
|
|
18
24
|
import { noise } from '@chainsafe/libp2p-noise';
|
|
19
25
|
import { yamux } from '@chainsafe/libp2p-yamux';
|
|
20
26
|
import { identify } from '@libp2p/identify';
|
|
21
|
-
import type { PeerId
|
|
27
|
+
import type { PeerId } from '@libp2p/interface';
|
|
22
28
|
import '@libp2p/kad-dht';
|
|
23
29
|
import { mplex } from '@libp2p/mplex';
|
|
24
30
|
import { createFromJSON, createSecp256k1PeerId } from '@libp2p/peer-id-factory';
|
|
25
31
|
import { tcp } from '@libp2p/tcp';
|
|
26
|
-
import {
|
|
32
|
+
import { createLibp2p } from 'libp2p';
|
|
27
33
|
|
|
28
34
|
import { type AttestationPool } from '../attestation_pool/attestation_pool.js';
|
|
29
35
|
import { type P2PConfig } from '../config.js';
|
|
30
36
|
import { type TxPool } from '../tx_pool/index.js';
|
|
31
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
DataTxValidator,
|
|
39
|
+
DoubleSpendTxValidator,
|
|
40
|
+
MetadataTxValidator,
|
|
41
|
+
TxProofValidator,
|
|
42
|
+
} from '../tx_validator/index.js';
|
|
43
|
+
import { type PubSubLibp2p, convertToMultiaddr } from '../util.js';
|
|
32
44
|
import { AztecDatastore } from './data_store.js';
|
|
33
45
|
import { PeerManager } from './peer_manager.js';
|
|
46
|
+
import { PeerErrorSeverity } from './peer_scoring.js';
|
|
34
47
|
import { pingHandler, statusHandler } from './reqresp/handlers.js';
|
|
35
48
|
import {
|
|
36
49
|
DEFAULT_SUB_PROTOCOL_HANDLERS,
|
|
@@ -45,11 +58,6 @@ import {
|
|
|
45
58
|
import { ReqResp } from './reqresp/reqresp.js';
|
|
46
59
|
import type { P2PService, PeerDiscoveryService } from './service.js';
|
|
47
60
|
|
|
48
|
-
export interface PubSubLibp2p extends Libp2p {
|
|
49
|
-
services: {
|
|
50
|
-
pubsub: PubSub<GossipsubEvents>;
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
61
|
/**
|
|
54
62
|
* Create a libp2p peer ID from the private key if provided, otherwise creates a new random ID.
|
|
55
63
|
* @param privateKey - Optional peer ID private key as hex string
|
|
@@ -90,10 +98,17 @@ export class LibP2PService implements P2PService {
|
|
|
90
98
|
private peerDiscoveryService: PeerDiscoveryService,
|
|
91
99
|
private txPool: TxPool,
|
|
92
100
|
private attestationPool: AttestationPool,
|
|
101
|
+
private l2BlockSource: L2BlockSource,
|
|
102
|
+
private proofVerifier: ClientProtocolCircuitVerifier,
|
|
103
|
+
private worldStateSynchronizer: WorldStateSynchronizer,
|
|
93
104
|
private requestResponseHandlers: ReqRespSubProtocolHandlers = DEFAULT_SUB_PROTOCOL_HANDLERS,
|
|
94
105
|
private logger = createDebugLogger('aztec:libp2p_service'),
|
|
95
106
|
) {
|
|
96
107
|
this.peerManager = new PeerManager(node, peerDiscoveryService, config, logger);
|
|
108
|
+
this.node.services.pubsub.score.params.appSpecificScore = (peerId: string) => {
|
|
109
|
+
return this.peerManager.getPeerScore(peerId);
|
|
110
|
+
};
|
|
111
|
+
this.node.services.pubsub.score.params.appSpecificWeight = 10;
|
|
97
112
|
this.reqresp = new ReqResp(config, node);
|
|
98
113
|
|
|
99
114
|
this.blockReceivedCallback = (block: BlockProposal): Promise<BlockAttestation | undefined> => {
|
|
@@ -136,15 +151,15 @@ export class LibP2PService implements P2PService {
|
|
|
136
151
|
|
|
137
152
|
// add GossipSub listener
|
|
138
153
|
this.node.services.pubsub.addEventListener('gossipsub:message', async e => {
|
|
139
|
-
const { msg } = e.detail;
|
|
154
|
+
const { msg, propagationSource: peerId } = e.detail;
|
|
140
155
|
this.logger.debug(`Received PUBSUB message.`);
|
|
141
156
|
|
|
142
|
-
await this.jobQueue.put(() => this.handleNewGossipMessage(msg));
|
|
157
|
+
await this.jobQueue.put(() => this.handleNewGossipMessage(msg, peerId));
|
|
143
158
|
});
|
|
144
159
|
|
|
145
160
|
// Start running promise for peer discovery
|
|
146
161
|
this.discoveryRunningPromise = new RunningPromise(() => {
|
|
147
|
-
this.peerManager.
|
|
162
|
+
this.peerManager.heartbeat();
|
|
148
163
|
}, this.config.peerCheckIntervalMS);
|
|
149
164
|
this.discoveryRunningPromise.start();
|
|
150
165
|
await this.reqresp.start(this.requestResponseHandlers);
|
|
@@ -181,6 +196,9 @@ export class LibP2PService implements P2PService {
|
|
|
181
196
|
peerId: PeerId,
|
|
182
197
|
txPool: TxPool,
|
|
183
198
|
attestationPool: AttestationPool,
|
|
199
|
+
l2BlockSource: L2BlockSource,
|
|
200
|
+
proofVerifier: ClientProtocolCircuitVerifier,
|
|
201
|
+
worldStateSynchronizer: WorldStateSynchronizer,
|
|
184
202
|
store: AztecKVStore,
|
|
185
203
|
) {
|
|
186
204
|
const { tcpListenAddress, tcpAnnounceAddress, minPeerCount, maxPeerCount } = config;
|
|
@@ -223,13 +241,22 @@ export class LibP2PService implements P2PService {
|
|
|
223
241
|
}),
|
|
224
242
|
pubsub: gossipsub({
|
|
225
243
|
allowPublishToZeroTopicPeers: true,
|
|
226
|
-
D:
|
|
227
|
-
Dlo:
|
|
228
|
-
Dhi:
|
|
229
|
-
heartbeatInterval:
|
|
230
|
-
mcacheLength:
|
|
231
|
-
mcacheGossip:
|
|
232
|
-
|
|
244
|
+
D: config.gossipsubD,
|
|
245
|
+
Dlo: config.gossipsubDlo,
|
|
246
|
+
Dhi: config.gossipsubDhi,
|
|
247
|
+
heartbeatInterval: config.gossipsubInterval,
|
|
248
|
+
mcacheLength: config.gossipsubMcacheLength,
|
|
249
|
+
mcacheGossip: config.gossipsubMcacheGossip,
|
|
250
|
+
scoreParams: createPeerScoreParams({
|
|
251
|
+
topics: {
|
|
252
|
+
[Tx.p2pTopic]: createTopicScoreParams({
|
|
253
|
+
topicWeight: 1,
|
|
254
|
+
invalidMessageDeliveriesWeight: -20,
|
|
255
|
+
invalidMessageDeliveriesDecay: 0.5,
|
|
256
|
+
}),
|
|
257
|
+
},
|
|
258
|
+
}),
|
|
259
|
+
}) as (components: GossipSubComponents) => GossipSub,
|
|
233
260
|
},
|
|
234
261
|
});
|
|
235
262
|
|
|
@@ -252,7 +279,17 @@ export class LibP2PService implements P2PService {
|
|
|
252
279
|
[TX_REQ_PROTOCOL]: txHandler,
|
|
253
280
|
};
|
|
254
281
|
|
|
255
|
-
return new LibP2PService(
|
|
282
|
+
return new LibP2PService(
|
|
283
|
+
config,
|
|
284
|
+
node,
|
|
285
|
+
peerDiscoveryService,
|
|
286
|
+
txPool,
|
|
287
|
+
attestationPool,
|
|
288
|
+
l2BlockSource,
|
|
289
|
+
proofVerifier,
|
|
290
|
+
worldStateSynchronizer,
|
|
291
|
+
requestResponseHandlers,
|
|
292
|
+
);
|
|
256
293
|
}
|
|
257
294
|
|
|
258
295
|
/**
|
|
@@ -323,10 +360,10 @@ export class LibP2PService implements P2PService {
|
|
|
323
360
|
* @param topic - The message's topic.
|
|
324
361
|
* @param data - The message data
|
|
325
362
|
*/
|
|
326
|
-
private async handleNewGossipMessage(message: RawGossipMessage) {
|
|
363
|
+
private async handleNewGossipMessage(message: RawGossipMessage, peerId: PeerId) {
|
|
327
364
|
if (message.topic === Tx.p2pTopic) {
|
|
328
365
|
const tx = Tx.fromBuffer(Buffer.from(message.data));
|
|
329
|
-
await this.processTxFromPeer(tx);
|
|
366
|
+
await this.processTxFromPeer(tx, peerId);
|
|
330
367
|
}
|
|
331
368
|
if (message.topic === BlockAttestation.p2pTopic) {
|
|
332
369
|
const attestation = BlockAttestation.fromBuffer(Buffer.from(message.data));
|
|
@@ -376,11 +413,87 @@ export class LibP2PService implements P2PService {
|
|
|
376
413
|
void this.jobQueue.put(() => Promise.resolve(this.sendToPeers(message)));
|
|
377
414
|
}
|
|
378
415
|
|
|
379
|
-
private async processTxFromPeer(tx: Tx): Promise<void> {
|
|
416
|
+
private async processTxFromPeer(tx: Tx, peerId: PeerId): Promise<void> {
|
|
380
417
|
const txHash = tx.getTxHash();
|
|
381
418
|
const txHashString = txHash.toString();
|
|
382
419
|
this.logger.verbose(`Received tx ${txHashString} from external peer.`);
|
|
383
|
-
|
|
420
|
+
|
|
421
|
+
const isValidTx = await this.validateTx(tx, peerId);
|
|
422
|
+
|
|
423
|
+
if (isValidTx) {
|
|
424
|
+
await this.txPool.addTxs([tx]);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
private async validateTx(tx: Tx, peerId: PeerId): Promise<boolean> {
|
|
429
|
+
const blockNumber = (await this.l2BlockSource.getBlockNumber()) + 1;
|
|
430
|
+
// basic data validation
|
|
431
|
+
const dataValidator = new DataTxValidator();
|
|
432
|
+
const [_, dataInvalidTxs] = await dataValidator.validateTxs([tx]);
|
|
433
|
+
if (dataInvalidTxs.length > 0) {
|
|
434
|
+
// penalize
|
|
435
|
+
this.node.services.pubsub.score.markInvalidMessageDelivery(peerId.toString(), Tx.p2pTopic);
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// metadata validation
|
|
440
|
+
const metadataValidator = new MetadataTxValidator(new Fr(this.config.l1ChainId), new Fr(blockNumber));
|
|
441
|
+
const [__, metaInvalidTxs] = await metadataValidator.validateTxs([tx]);
|
|
442
|
+
if (metaInvalidTxs.length > 0) {
|
|
443
|
+
// penalize
|
|
444
|
+
this.node.services.pubsub.score.markInvalidMessageDelivery(peerId.toString(), Tx.p2pTopic);
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// double spend validation
|
|
449
|
+
const doubleSpendValidator = new DoubleSpendTxValidator({
|
|
450
|
+
getNullifierIndex: async (nullifier: Fr) => {
|
|
451
|
+
const merkleTree = this.worldStateSynchronizer.getLatest();
|
|
452
|
+
const index = await merkleTree.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
|
|
453
|
+
return index;
|
|
454
|
+
},
|
|
455
|
+
});
|
|
456
|
+
const [___, doubleSpendInvalidTxs] = await doubleSpendValidator.validateTxs([tx]);
|
|
457
|
+
if (doubleSpendInvalidTxs.length > 0) {
|
|
458
|
+
// check if nullifier is older than 20 blocks
|
|
459
|
+
if (blockNumber - this.config.severePeerPenaltyBlockLength > 0) {
|
|
460
|
+
const snapshotValidator = new DoubleSpendTxValidator({
|
|
461
|
+
getNullifierIndex: async (nullifier: Fr) => {
|
|
462
|
+
const merkleTree = this.worldStateSynchronizer.getSnapshot(
|
|
463
|
+
blockNumber - this.config.severePeerPenaltyBlockLength,
|
|
464
|
+
);
|
|
465
|
+
const index = await merkleTree.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
|
|
466
|
+
return index;
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
const [____, snapshotInvalidTxs] = await snapshotValidator.validateTxs([tx]);
|
|
471
|
+
// High penalty if nullifier is older than 20 blocks
|
|
472
|
+
if (snapshotInvalidTxs.length > 0) {
|
|
473
|
+
// penalize
|
|
474
|
+
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.LowToleranceError);
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// penalize
|
|
479
|
+
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// proof validation
|
|
484
|
+
const proofValidator = new TxProofValidator(this.proofVerifier);
|
|
485
|
+
const [_____, proofInvalidTxs] = await proofValidator.validateTxs([tx]);
|
|
486
|
+
if (proofInvalidTxs.length > 0) {
|
|
487
|
+
// penalize
|
|
488
|
+
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.MidToleranceError);
|
|
489
|
+
return false;
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return true;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
public getPeerScore(peerId: PeerId): number {
|
|
496
|
+
return this.node.services.pubsub.score.score(peerId.toString());
|
|
384
497
|
}
|
|
385
498
|
|
|
386
499
|
private async sendToPeers<T extends Gossipable>(message: T) {
|
|
@@ -3,9 +3,10 @@ import { createDebugLogger } from '@aztec/foundation/log';
|
|
|
3
3
|
import { type ENR } from '@chainsafe/enr';
|
|
4
4
|
import { type PeerId } from '@libp2p/interface';
|
|
5
5
|
import { type Multiaddr } from '@multiformats/multiaddr';
|
|
6
|
-
import { type Libp2p } from 'libp2p';
|
|
7
6
|
|
|
8
7
|
import { type P2PConfig } from '../config.js';
|
|
8
|
+
import { type PubSubLibp2p } from '../util.js';
|
|
9
|
+
import { type PeerErrorSeverity, PeerScoring } from './peer_scoring.js';
|
|
9
10
|
import { type PeerDiscoveryService } from './service.js';
|
|
10
11
|
|
|
11
12
|
const MAX_DIAL_ATTEMPTS = 3;
|
|
@@ -20,12 +21,15 @@ type CachedPeer = {
|
|
|
20
21
|
|
|
21
22
|
export class PeerManager {
|
|
22
23
|
private cachedPeers: Map<string, CachedPeer> = new Map();
|
|
24
|
+
private peerScoring: PeerScoring;
|
|
25
|
+
|
|
23
26
|
constructor(
|
|
24
|
-
private libP2PNode:
|
|
27
|
+
private libP2PNode: PubSubLibp2p,
|
|
25
28
|
private peerDiscoveryService: PeerDiscoveryService,
|
|
26
29
|
private config: P2PConfig,
|
|
27
30
|
private logger = createDebugLogger('aztec:p2p:peer_manager'),
|
|
28
31
|
) {
|
|
32
|
+
this.peerScoring = new PeerScoring(config);
|
|
29
33
|
// Handle new established connections
|
|
30
34
|
this.libP2PNode.addEventListener('peer:connect', evt => {
|
|
31
35
|
const peerId = evt.detail;
|
|
@@ -52,10 +56,25 @@ export class PeerManager {
|
|
|
52
56
|
});
|
|
53
57
|
}
|
|
54
58
|
|
|
59
|
+
public heartbeat() {
|
|
60
|
+
this.discover();
|
|
61
|
+
this.peerScoring.decayAllScores();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public penalizePeer(peerId: PeerId, penalty: PeerErrorSeverity) {
|
|
65
|
+
const id = peerId.toString();
|
|
66
|
+
const penaltyValue = this.peerScoring.peerPenalties[penalty];
|
|
67
|
+
this.peerScoring.updateScore(id, -penaltyValue);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public getPeerScore(peerId: string): number {
|
|
71
|
+
return this.peerScoring.getScore(peerId);
|
|
72
|
+
}
|
|
73
|
+
|
|
55
74
|
/**
|
|
56
75
|
* Discovers peers.
|
|
57
76
|
*/
|
|
58
|
-
|
|
77
|
+
private discover() {
|
|
59
78
|
// Get current connections
|
|
60
79
|
const connections = this.libP2PNode.getConnections();
|
|
61
80
|
|
|
@@ -155,7 +174,7 @@ export class PeerManager {
|
|
|
155
174
|
}
|
|
156
175
|
}
|
|
157
176
|
|
|
158
|
-
async dialPeer(peer: CachedPeer) {
|
|
177
|
+
private async dialPeer(peer: CachedPeer) {
|
|
159
178
|
const id = peer.peerId.toString();
|
|
160
179
|
await this.libP2PNode.peerStore.merge(peer.peerId, { multiaddrs: [peer.multiaddrTcp] });
|
|
161
180
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { type P2PConfig } from '../config.js';
|
|
2
|
+
|
|
3
|
+
export enum PeerErrorSeverity {
|
|
4
|
+
/**
|
|
5
|
+
* Not malicious action, but it must not be tolerated
|
|
6
|
+
* ~2 occurrences will get the peer banned
|
|
7
|
+
*/
|
|
8
|
+
LowToleranceError = 'LowToleranceError',
|
|
9
|
+
/**
|
|
10
|
+
* Negative action that can be tolerated only sometimes
|
|
11
|
+
* ~10 occurrences will get the peer banned
|
|
12
|
+
*/
|
|
13
|
+
MidToleranceError = 'MidToleranceError',
|
|
14
|
+
/**
|
|
15
|
+
* Some error that can be tolerated multiple times
|
|
16
|
+
* ~50 occurrences will get the peer banned
|
|
17
|
+
*/
|
|
18
|
+
HighToleranceError = 'HighToleranceError',
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const DefaultPeerPenalties = {
|
|
22
|
+
[PeerErrorSeverity.LowToleranceError]: 2,
|
|
23
|
+
[PeerErrorSeverity.MidToleranceError]: 10,
|
|
24
|
+
[PeerErrorSeverity.HighToleranceError]: 50,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export class PeerScoring {
|
|
28
|
+
private scores: Map<string, number> = new Map();
|
|
29
|
+
private lastUpdateTime: Map<string, number> = new Map();
|
|
30
|
+
private decayInterval = 1000 * 60; // 1 minute
|
|
31
|
+
private decayFactor = 0.9;
|
|
32
|
+
peerPenalties: { [key in PeerErrorSeverity]: number };
|
|
33
|
+
|
|
34
|
+
constructor(config: P2PConfig) {
|
|
35
|
+
const orderedValues = config.peerPenaltyValues.sort((a, b) => a - b);
|
|
36
|
+
this.peerPenalties = {
|
|
37
|
+
[PeerErrorSeverity.HighToleranceError]:
|
|
38
|
+
orderedValues[0] ?? DefaultPeerPenalties[PeerErrorSeverity.LowToleranceError],
|
|
39
|
+
[PeerErrorSeverity.MidToleranceError]:
|
|
40
|
+
orderedValues[1] ?? DefaultPeerPenalties[PeerErrorSeverity.MidToleranceError],
|
|
41
|
+
[PeerErrorSeverity.LowToleranceError]:
|
|
42
|
+
orderedValues[2] ?? DefaultPeerPenalties[PeerErrorSeverity.HighToleranceError],
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
updateScore(peerId: string, scoreDelta: number): void {
|
|
47
|
+
const currentTime = Date.now();
|
|
48
|
+
const lastUpdate = this.lastUpdateTime.get(peerId) || currentTime;
|
|
49
|
+
const timePassed = currentTime - lastUpdate;
|
|
50
|
+
const decayPeriods = Math.floor(timePassed / this.decayInterval);
|
|
51
|
+
|
|
52
|
+
let currentScore = this.scores.get(peerId) || 0;
|
|
53
|
+
|
|
54
|
+
// Apply decay
|
|
55
|
+
currentScore *= Math.pow(this.decayFactor, decayPeriods);
|
|
56
|
+
|
|
57
|
+
// Apply new score delta
|
|
58
|
+
currentScore += scoreDelta;
|
|
59
|
+
|
|
60
|
+
this.scores.set(peerId, currentScore);
|
|
61
|
+
this.lastUpdateTime.set(peerId, currentTime);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
decayAllScores(): void {
|
|
65
|
+
const currentTime = Date.now();
|
|
66
|
+
for (const [peerId, lastUpdate] of this.lastUpdateTime.entries()) {
|
|
67
|
+
const timePassed = currentTime - lastUpdate;
|
|
68
|
+
const decayPeriods = Math.floor(timePassed / this.decayInterval);
|
|
69
|
+
if (decayPeriods > 0) {
|
|
70
|
+
let score = this.scores.get(peerId) || 0;
|
|
71
|
+
score *= Math.pow(this.decayFactor, decayPeriods);
|
|
72
|
+
this.scores.set(peerId, score);
|
|
73
|
+
this.lastUpdateTime.set(peerId, currentTime);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
getScore(peerId: string): number {
|
|
79
|
+
return this.scores.get(peerId) || 0;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ProcessedTx, type Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
|
+
|
|
3
|
+
export class AggregateTxValidator<T extends Tx | ProcessedTx> implements TxValidator<T> {
|
|
4
|
+
#validators: TxValidator<T>[];
|
|
5
|
+
constructor(...validators: TxValidator<T>[]) {
|
|
6
|
+
if (validators.length === 0) {
|
|
7
|
+
throw new Error('At least one validator must be provided');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
this.#validators = validators;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
|
|
14
|
+
const invalidTxs: T[] = [];
|
|
15
|
+
let txPool = txs;
|
|
16
|
+
for (const validator of this.#validators) {
|
|
17
|
+
const [valid, invalid] = await validator.validateTxs(txPool);
|
|
18
|
+
invalidTxs.push(...invalid);
|
|
19
|
+
txPool = valid;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return [txPool, invalidTxs];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
3
|
+
|
|
4
|
+
export class DataTxValidator implements TxValidator<Tx> {
|
|
5
|
+
#log = createDebugLogger('aztec:sequencer:tx_validator:tx_data');
|
|
6
|
+
|
|
7
|
+
validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
|
|
8
|
+
const validTxs: Tx[] = [];
|
|
9
|
+
const invalidTxs: Tx[] = [];
|
|
10
|
+
for (const tx of txs) {
|
|
11
|
+
if (!this.#hasCorrectExecutionRequests(tx)) {
|
|
12
|
+
invalidTxs.push(tx);
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
validTxs.push(tx);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return Promise.resolve([validTxs, invalidTxs]);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
#hasCorrectExecutionRequests(tx: Tx): boolean {
|
|
23
|
+
const callRequests = [
|
|
24
|
+
...tx.data.getRevertiblePublicCallRequests(),
|
|
25
|
+
...tx.data.getNonRevertiblePublicCallRequests(),
|
|
26
|
+
];
|
|
27
|
+
if (callRequests.length !== tx.enqueuedPublicFunctionCalls.length) {
|
|
28
|
+
this.#log.warn(
|
|
29
|
+
`Rejecting tx ${Tx.getHash(tx)} because of mismatch number of execution requests for public calls. Expected ${
|
|
30
|
+
callRequests.length
|
|
31
|
+
}. Got ${tx.enqueuedPublicFunctionCalls.length}.`,
|
|
32
|
+
);
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const invalidExecutionRequestIndex = tx.enqueuedPublicFunctionCalls.findIndex(
|
|
37
|
+
(execRequest, i) => !execRequest.isForCallRequest(callRequests[i]),
|
|
38
|
+
);
|
|
39
|
+
if (invalidExecutionRequestIndex !== -1) {
|
|
40
|
+
this.#log.warn(
|
|
41
|
+
`Rejecting tx ${Tx.getHash(
|
|
42
|
+
tx,
|
|
43
|
+
)} because of incorrect execution requests for public call at index ${invalidExecutionRequestIndex}.`,
|
|
44
|
+
);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const teardownCallRequest = tx.data.getTeardownPublicCallRequest();
|
|
49
|
+
const isInvalidTeardownExecutionRequest =
|
|
50
|
+
(!teardownCallRequest && !tx.publicTeardownFunctionCall.isEmpty()) ||
|
|
51
|
+
(teardownCallRequest && !tx.publicTeardownFunctionCall.isForCallRequest(teardownCallRequest));
|
|
52
|
+
if (isInvalidTeardownExecutionRequest) {
|
|
53
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} because of incorrect teardown execution requests.`);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// TODO: Check logs.
|
|
61
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type AnyTx, Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
|
+
import { Fr } from '@aztec/circuits.js';
|
|
3
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
|
+
|
|
5
|
+
export interface NullifierSource {
|
|
6
|
+
getNullifierIndex: (nullifier: Fr) => Promise<bigint | undefined>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class DoubleSpendTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
10
|
+
#log = createDebugLogger('aztec:sequencer:tx_validator:tx_double_spend');
|
|
11
|
+
#nullifierSource: NullifierSource;
|
|
12
|
+
|
|
13
|
+
constructor(nullifierSource: NullifierSource, private readonly isValidatingBlock: boolean = true) {
|
|
14
|
+
this.#nullifierSource = nullifierSource;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
|
|
18
|
+
const validTxs: T[] = [];
|
|
19
|
+
const invalidTxs: T[] = [];
|
|
20
|
+
const thisBlockNullifiers = new Set<bigint>();
|
|
21
|
+
|
|
22
|
+
for (const tx of txs) {
|
|
23
|
+
if (!(await this.#uniqueNullifiers(tx, thisBlockNullifiers))) {
|
|
24
|
+
invalidTxs.push(tx);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
validTxs.push(tx);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return [validTxs, invalidTxs];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async #uniqueNullifiers(tx: AnyTx, thisBlockNullifiers: Set<bigint>): Promise<boolean> {
|
|
35
|
+
const nullifiers = tx.data.getNonEmptyNullifiers().map(x => x.toBigInt());
|
|
36
|
+
|
|
37
|
+
// Ditch this tx if it has repeated nullifiers
|
|
38
|
+
const uniqueNullifiers = new Set(nullifiers);
|
|
39
|
+
if (uniqueNullifiers.size !== nullifiers.length) {
|
|
40
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for emitting duplicate nullifiers`);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (this.isValidatingBlock) {
|
|
45
|
+
for (const nullifier of nullifiers) {
|
|
46
|
+
if (thisBlockNullifiers.has(nullifier)) {
|
|
47
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating a nullifier in the same block`);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
thisBlockNullifiers.add(nullifier);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const nullifierIndexes = await Promise.all(nullifiers.map(n => this.#nullifierSource.getNullifierIndex(new Fr(n))));
|
|
56
|
+
|
|
57
|
+
const hasDuplicates = nullifierIndexes.some(index => index !== undefined);
|
|
58
|
+
if (hasDuplicates) {
|
|
59
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating nullifiers present in state trees`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { type AnyTx, Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
|
+
import { type Fr } from '@aztec/circuits.js';
|
|
3
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
|
+
|
|
5
|
+
export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
6
|
+
#log = createDebugLogger('aztec:sequencer:tx_validator:tx_metadata');
|
|
7
|
+
|
|
8
|
+
constructor(private chainId: Fr, private blockNumber: Fr) {}
|
|
9
|
+
|
|
10
|
+
validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
|
|
11
|
+
const validTxs: T[] = [];
|
|
12
|
+
const invalidTxs: T[] = [];
|
|
13
|
+
for (const tx of txs) {
|
|
14
|
+
if (!this.#hasCorrectChainId(tx)) {
|
|
15
|
+
invalidTxs.push(tx);
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (!this.#isValidForBlockNumber(tx)) {
|
|
20
|
+
invalidTxs.push(tx);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
validTxs.push(tx);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return Promise.resolve([validTxs, invalidTxs]);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#hasCorrectChainId(tx: T): boolean {
|
|
31
|
+
if (!tx.data.constants.txContext.chainId.equals(this.chainId)) {
|
|
32
|
+
this.#log.warn(
|
|
33
|
+
`Rejecting tx ${Tx.getHash(
|
|
34
|
+
tx,
|
|
35
|
+
)} because of incorrect chain ${tx.data.constants.txContext.chainId.toNumber()} != ${this.chainId.toNumber()}`,
|
|
36
|
+
);
|
|
37
|
+
return false;
|
|
38
|
+
} else {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
#isValidForBlockNumber(tx: T): boolean {
|
|
44
|
+
const target =
|
|
45
|
+
tx instanceof Tx
|
|
46
|
+
? tx.data.forRollup?.rollupValidationRequests || tx.data.forPublic!.validationRequests.forRollup
|
|
47
|
+
: tx.data.rollupValidationRequests;
|
|
48
|
+
const maxBlockNumber = target.maxBlockNumber;
|
|
49
|
+
|
|
50
|
+
if (maxBlockNumber.isSome && maxBlockNumber.value < this.blockNumber) {
|
|
51
|
+
this.#log.warn(
|
|
52
|
+
`Rejecting tx ${Tx.getHash(tx)} for low max block number. Tx max block number: ${
|
|
53
|
+
maxBlockNumber.value
|
|
54
|
+
}, current block number: ${this.blockNumber}.`,
|
|
55
|
+
);
|
|
56
|
+
return false;
|
|
57
|
+
} else {
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type ClientProtocolCircuitVerifier, Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
3
|
+
|
|
4
|
+
export class TxProofValidator implements TxValidator<Tx> {
|
|
5
|
+
#log = createDebugLogger('aztec:sequencer:tx_validator:private_proof');
|
|
6
|
+
|
|
7
|
+
constructor(private verifier: ClientProtocolCircuitVerifier) {}
|
|
8
|
+
|
|
9
|
+
async validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
|
|
10
|
+
const validTxs: Tx[] = [];
|
|
11
|
+
const invalidTxs: Tx[] = [];
|
|
12
|
+
|
|
13
|
+
for (const tx of txs) {
|
|
14
|
+
if (await this.verifier.verifyProof(tx)) {
|
|
15
|
+
validTxs.push(tx);
|
|
16
|
+
} else {
|
|
17
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for invalid proof`);
|
|
18
|
+
invalidTxs.push(tx);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return [validTxs, invalidTxs];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
validateTx(tx: Tx): Promise<boolean> {
|
|
26
|
+
return this.verifier.verifyProof(tx);
|
|
27
|
+
}
|
|
28
|
+
}
|
package/src/util.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
import type { GossipSub } from '@chainsafe/libp2p-gossipsub';
|
|
1
2
|
import { resolve } from 'dns/promises';
|
|
3
|
+
import type { Libp2p } from 'libp2p';
|
|
4
|
+
|
|
5
|
+
export interface PubSubLibp2p extends Libp2p {
|
|
6
|
+
services: {
|
|
7
|
+
pubsub: GossipSub;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
2
10
|
|
|
3
11
|
/**
|
|
4
12
|
* Converts an address string to a multiaddr string.
|