@aztec/p2p 0.0.1-commit.87a0206 → 0.0.1-commit.8f9871590
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/factory.d.ts +3 -3
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +42 -8
- package/dest/client/interface.d.ts +30 -15
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +33 -35
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +107 -137
- package/dest/config.d.ts +22 -4
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +16 -1
- package/dest/index.d.ts +2 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -0
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts +21 -12
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.js +67 -37
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +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 +53 -53
- package/dest/mem_pools/attestation_pool/index.d.ts +2 -2
- package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/index.js +1 -1
- package/dest/mem_pools/attestation_pool/mocks.d.ts +2 -2
- package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/mocks.js +2 -2
- package/dest/mem_pools/index.d.ts +2 -1
- package/dest/mem_pools/index.d.ts.map +1 -1
- package/dest/mem_pools/interface.d.ts +3 -3
- package/dest/mem_pools/interface.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.js +3 -3
- package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts +102 -0
- package/dest/mem_pools/tx_pool_v2/deleted_pool.d.ts.map +1 -0
- package/dest/mem_pools/tx_pool_v2/deleted_pool.js +242 -0
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.js +3 -0
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.js +5 -3
- package/dest/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.js +3 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.js +3 -1
- package/dest/mem_pools/tx_pool_v2/index.d.ts +2 -1
- package/dest/mem_pools/tx_pool_v2/index.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/index.js +1 -0
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts +7 -3
- package/dest/mem_pools/tx_pool_v2/interfaces.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/interfaces.js +2 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts +28 -4
- package/dest/mem_pools/tx_pool_v2/tx_metadata.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_metadata.js +37 -4
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts +8 -2
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_indices.js +15 -2
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts +4 -2
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2.js +6 -2
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts +4 -2
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool_v2/tx_pool_v2_impl.js +110 -78
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts +3 -3
- package/dest/msg_validators/tx_validator/aggregate_tx_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/block_header_validator.d.ts +16 -3
- package/dest/msg_validators/tx_validator/block_header_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/block_header_validator.js +1 -1
- package/dest/msg_validators/tx_validator/double_spend_validator.d.ts +13 -3
- package/dest/msg_validators/tx_validator/double_spend_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/double_spend_validator.js +4 -4
- package/dest/msg_validators/tx_validator/timestamp_validator.d.ts +20 -4
- package/dest/msg_validators/tx_validator/timestamp_validator.d.ts.map +1 -1
- package/dest/msg_validators/tx_validator/timestamp_validator.js +6 -6
- package/dest/services/dummy_service.d.ts +6 -2
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +3 -0
- package/dest/services/encoding.d.ts +2 -2
- package/dest/services/encoding.d.ts.map +1 -1
- package/dest/services/encoding.js +2 -2
- package/dest/services/gossipsub/index.d.ts +3 -0
- package/dest/services/gossipsub/index.d.ts.map +1 -0
- package/dest/services/gossipsub/index.js +2 -0
- package/dest/services/gossipsub/scoring.d.ts +21 -3
- package/dest/services/gossipsub/scoring.d.ts.map +1 -1
- package/dest/services/gossipsub/scoring.js +24 -7
- package/dest/services/gossipsub/topic_score_params.d.ts +161 -0
- package/dest/services/gossipsub/topic_score_params.d.ts.map +1 -0
- package/dest/services/gossipsub/topic_score_params.js +324 -0
- package/dest/services/libp2p/libp2p_service.d.ts +11 -3
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +91 -67
- package/dest/services/peer-manager/peer_scoring.d.ts +1 -1
- package/dest/services/peer-manager/peer_scoring.d.ts.map +1 -1
- package/dest/services/peer-manager/peer_scoring.js +25 -2
- package/dest/services/reqresp/interface.d.ts +10 -1
- package/dest/services/reqresp/interface.d.ts.map +1 -1
- package/dest/services/reqresp/interface.js +15 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts +3 -3
- package/dest/services/reqresp/protocols/block_txs/block_txs_handler.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts +7 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/block_txs/block_txs_reqresp.js +15 -0
- package/dest/services/reqresp/protocols/tx.d.ts +7 -1
- package/dest/services/reqresp/protocols/tx.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/tx.js +20 -0
- package/dest/services/reqresp/reqresp.d.ts +1 -1
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +11 -4
- package/dest/services/service.d.ts +18 -1
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_collection/config.d.ts +19 -1
- package/dest/services/tx_collection/config.d.ts.map +1 -1
- package/dest/services/tx_collection/config.js +46 -0
- package/dest/services/tx_collection/fast_tx_collection.d.ts +3 -1
- package/dest/services/tx_collection/fast_tx_collection.d.ts.map +1 -1
- package/dest/services/tx_collection/fast_tx_collection.js +17 -3
- package/dest/services/tx_collection/file_store_tx_collection.d.ts +53 -0
- package/dest/services/tx_collection/file_store_tx_collection.d.ts.map +1 -0
- package/dest/services/tx_collection/file_store_tx_collection.js +165 -0
- package/dest/services/tx_collection/file_store_tx_source.d.ts +28 -0
- package/dest/services/tx_collection/file_store_tx_source.d.ts.map +1 -0
- package/dest/services/tx_collection/file_store_tx_source.js +59 -0
- package/dest/services/tx_collection/index.d.ts +2 -1
- package/dest/services/tx_collection/index.d.ts.map +1 -1
- package/dest/services/tx_collection/index.js +1 -0
- package/dest/services/tx_collection/slow_tx_collection.d.ts +6 -2
- package/dest/services/tx_collection/slow_tx_collection.d.ts.map +1 -1
- package/dest/services/tx_collection/slow_tx_collection.js +55 -23
- package/dest/services/tx_collection/tx_collection.d.ts +19 -7
- package/dest/services/tx_collection/tx_collection.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_collection.js +75 -3
- package/dest/services/tx_collection/tx_collection_sink.d.ts +15 -6
- package/dest/services/tx_collection/tx_collection_sink.d.ts.map +1 -1
- package/dest/services/tx_collection/tx_collection_sink.js +13 -7
- package/dest/services/tx_file_store/config.d.ts +1 -3
- package/dest/services/tx_file_store/config.d.ts.map +1 -1
- package/dest/services/tx_file_store/config.js +0 -4
- package/dest/services/tx_file_store/tx_file_store.d.ts +4 -3
- package/dest/services/tx_file_store/tx_file_store.d.ts.map +1 -1
- package/dest/services/tx_file_store/tx_file_store.js +8 -5
- package/dest/services/tx_provider.d.ts +3 -3
- package/dest/services/tx_provider.d.ts.map +1 -1
- package/dest/services/tx_provider.js +5 -4
- package/dest/test-helpers/make-test-p2p-clients.d.ts +3 -3
- package/dest/test-helpers/make-test-p2p-clients.d.ts.map +1 -1
- package/dest/test-helpers/mock-pubsub.d.ts +27 -1
- package/dest/test-helpers/mock-pubsub.d.ts.map +1 -1
- package/dest/test-helpers/mock-pubsub.js +97 -2
- package/dest/test-helpers/reqresp-nodes.d.ts +1 -1
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
- package/dest/test-helpers/reqresp-nodes.js +2 -1
- package/dest/test-helpers/testbench-utils.d.ts +32 -24
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.js +103 -36
- package/dest/testbench/p2p_client_testbench_worker.js +3 -3
- package/package.json +14 -14
- package/src/client/factory.ts +78 -9
- package/src/client/interface.ts +32 -13
- package/src/client/p2p_client.ts +135 -157
- package/src/config.ts +34 -2
- package/src/index.ts +1 -0
- package/src/mem_pools/attestation_pool/attestation_pool.ts +95 -44
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +57 -53
- package/src/mem_pools/attestation_pool/index.ts +3 -3
- package/src/mem_pools/attestation_pool/mocks.ts +2 -1
- package/src/mem_pools/index.ts +3 -0
- package/src/mem_pools/interface.ts +2 -2
- package/src/mem_pools/tx_pool/README.md +1 -1
- package/src/mem_pools/tx_pool/eviction/invalid_txs_after_mining_rule.ts +3 -3
- package/src/mem_pools/tx_pool_v2/README.md +76 -10
- package/src/mem_pools/tx_pool_v2/deleted_pool.ts +310 -0
- package/src/mem_pools/tx_pool_v2/eviction/fee_payer_balance_eviction_rule.ts +3 -0
- package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_mining_rule.ts +3 -3
- package/src/mem_pools/tx_pool_v2/eviction/invalid_txs_after_reorg_rule.ts +1 -1
- package/src/mem_pools/tx_pool_v2/eviction/low_priority_eviction_rule.ts +3 -1
- package/src/mem_pools/tx_pool_v2/index.ts +1 -0
- package/src/mem_pools/tx_pool_v2/interfaces.ts +8 -2
- package/src/mem_pools/tx_pool_v2/tx_metadata.ts +57 -7
- package/src/mem_pools/tx_pool_v2/tx_pool_indices.ts +18 -2
- package/src/mem_pools/tx_pool_v2/tx_pool_v2.ts +7 -1
- package/src/mem_pools/tx_pool_v2/tx_pool_v2_impl.ts +122 -80
- package/src/msg_validators/tx_validator/aggregate_tx_validator.ts +2 -2
- package/src/msg_validators/tx_validator/block_header_validator.ts +15 -3
- package/src/msg_validators/tx_validator/double_spend_validator.ts +11 -6
- package/src/msg_validators/tx_validator/timestamp_validator.ts +23 -18
- package/src/services/dummy_service.ts +6 -0
- package/src/services/encoding.ts +2 -2
- package/src/services/gossipsub/README.md +626 -0
- package/src/services/gossipsub/index.ts +2 -0
- package/src/services/gossipsub/scoring.ts +29 -5
- package/src/services/gossipsub/topic_score_params.ts +451 -0
- package/src/services/libp2p/libp2p_service.ts +91 -70
- package/src/services/peer-manager/peer_scoring.ts +25 -0
- package/src/services/reqresp/interface.ts +26 -1
- package/src/services/reqresp/protocols/block_txs/block_txs_handler.ts +2 -2
- package/src/services/reqresp/protocols/block_txs/block_txs_reqresp.ts +17 -0
- package/src/services/reqresp/protocols/tx.ts +22 -0
- package/src/services/reqresp/reqresp.ts +13 -3
- package/src/services/service.ts +20 -0
- package/src/services/tx_collection/config.ts +68 -0
- package/src/services/tx_collection/fast_tx_collection.ts +14 -2
- package/src/services/tx_collection/file_store_tx_collection.ts +198 -0
- package/src/services/tx_collection/file_store_tx_source.ts +73 -0
- package/src/services/tx_collection/index.ts +1 -0
- package/src/services/tx_collection/slow_tx_collection.ts +64 -30
- package/src/services/tx_collection/tx_collection.ts +109 -13
- package/src/services/tx_collection/tx_collection_sink.ts +17 -7
- package/src/services/tx_file_store/config.ts +0 -6
- package/src/services/tx_file_store/tx_file_store.ts +9 -7
- package/src/services/tx_provider.ts +8 -7
- package/src/test-helpers/make-test-p2p-clients.ts +3 -3
- package/src/test-helpers/mock-pubsub.ts +133 -3
- package/src/test-helpers/reqresp-nodes.ts +2 -1
- package/src/test-helpers/testbench-utils.ts +117 -40
- package/src/testbench/p2p_client_testbench_worker.ts +3 -3
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { EpochCacheInterface } from '@aztec/epoch-cache';
|
|
2
2
|
import { BlockNumber, type SlotNumber } from '@aztec/foundation/branded-types';
|
|
3
|
-
import { randomInt } from '@aztec/foundation/crypto/random';
|
|
4
3
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
5
4
|
import { type Logger, createLibp2pComponentLogger, createLogger } from '@aztec/foundation/log';
|
|
6
5
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
@@ -45,7 +44,7 @@ import {
|
|
|
45
44
|
type GossipsubMessage,
|
|
46
45
|
gossipsub,
|
|
47
46
|
} from '@chainsafe/libp2p-gossipsub';
|
|
48
|
-
import { createPeerScoreParams
|
|
47
|
+
import { createPeerScoreParams } from '@chainsafe/libp2p-gossipsub/score';
|
|
49
48
|
import { SignaturePolicy } from '@chainsafe/libp2p-gossipsub/types';
|
|
50
49
|
import { noise } from '@chainsafe/libp2p-noise';
|
|
51
50
|
import { yamux } from '@chainsafe/libp2p-yamux';
|
|
@@ -80,7 +79,8 @@ import { getVersions } from '../../versioning.js';
|
|
|
80
79
|
import { AztecDatastore } from '../data_store.js';
|
|
81
80
|
import { DiscV5Service } from '../discv5/discV5_service.js';
|
|
82
81
|
import { SnappyTransform, fastMsgIdFn, getMsgIdFn, msgIdToStrFn } from '../encoding.js';
|
|
83
|
-
import { gossipScoreThresholds } from '../gossipsub/scoring.js';
|
|
82
|
+
import { APP_SPECIFIC_WEIGHT, gossipScoreThresholds } from '../gossipsub/scoring.js';
|
|
83
|
+
import { createAllTopicScoreParams } from '../gossipsub/topic_score_params.js';
|
|
84
84
|
import type { PeerManagerInterface } from '../peer-manager/interface.js';
|
|
85
85
|
import { PeerManager } from '../peer-manager/peer_manager.js';
|
|
86
86
|
import { PeerScoring } from '../peer-manager/peer_scoring.js';
|
|
@@ -113,6 +113,7 @@ import { ReqResp } from '../reqresp/reqresp.js';
|
|
|
113
113
|
import type {
|
|
114
114
|
P2PBlockReceivedCallback,
|
|
115
115
|
P2PCheckpointReceivedCallback,
|
|
116
|
+
P2PDuplicateAttestationCallback,
|
|
116
117
|
P2PService,
|
|
117
118
|
PeerDiscoveryService,
|
|
118
119
|
} from '../service.js';
|
|
@@ -155,6 +156,9 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
155
156
|
type: 'checkpoint' | 'block';
|
|
156
157
|
}) => void;
|
|
157
158
|
|
|
159
|
+
/** Callback invoked when a duplicate attestation is detected (triggers slashing). */
|
|
160
|
+
private duplicateAttestationCallback?: P2PDuplicateAttestationCallback;
|
|
161
|
+
|
|
158
162
|
/**
|
|
159
163
|
* Callback for when a block is received from a peer.
|
|
160
164
|
* @param block - The block received from the peer.
|
|
@@ -311,11 +315,6 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
311
315
|
const versions = getVersions(config);
|
|
312
316
|
const protocolVersion = compressComponentVersions(versions);
|
|
313
317
|
|
|
314
|
-
const txTopic = createTopicString(TopicType.tx, protocolVersion);
|
|
315
|
-
const blockProposalTopic = createTopicString(TopicType.block_proposal, protocolVersion);
|
|
316
|
-
const checkpointProposalTopic = createTopicString(TopicType.checkpoint_proposal, protocolVersion);
|
|
317
|
-
const checkpointAttestationTopic = createTopicString(TopicType.checkpoint_attestation, protocolVersion);
|
|
318
|
-
|
|
319
318
|
const preferredPeersEnrs: ENR[] = config.preferredPeers.map(enr => ENR.decodeTxt(enr));
|
|
320
319
|
const directPeers = (
|
|
321
320
|
await Promise.all(
|
|
@@ -335,6 +334,15 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
335
334
|
|
|
336
335
|
const announceTcpMultiaddr = config.p2pIp ? [convertToMultiaddr(config.p2pIp, p2pPort, 'tcp')] : [];
|
|
337
336
|
|
|
337
|
+
// Create dynamic topic score params based on network configuration
|
|
338
|
+
const l1Constants = epochCache.getL1Constants();
|
|
339
|
+
const topicScoreParams = createAllTopicScoreParams(protocolVersion, {
|
|
340
|
+
slotDurationMs: l1Constants.slotDuration * 1000,
|
|
341
|
+
heartbeatIntervalMs: config.gossipsubInterval,
|
|
342
|
+
targetCommitteeSize: l1Constants.targetCommitteeSize,
|
|
343
|
+
blockDurationMs: config.blockDurationMs,
|
|
344
|
+
});
|
|
345
|
+
|
|
338
346
|
const node = await createLibp2p({
|
|
339
347
|
start: false,
|
|
340
348
|
peerId,
|
|
@@ -430,28 +438,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
430
438
|
scoreParams: createPeerScoreParams({
|
|
431
439
|
// IPColocation factor can be disabled for local testing - default to -5
|
|
432
440
|
IPColocationFactorWeight: config.debugDisableColocationPenalty ? 0 : -5.0,
|
|
433
|
-
topics:
|
|
434
|
-
[txTopic]: createTopicScoreParams({
|
|
435
|
-
topicWeight: 1,
|
|
436
|
-
invalidMessageDeliveriesWeight: -20,
|
|
437
|
-
invalidMessageDeliveriesDecay: 0.5,
|
|
438
|
-
}),
|
|
439
|
-
[blockProposalTopic]: createTopicScoreParams({
|
|
440
|
-
topicWeight: 1,
|
|
441
|
-
invalidMessageDeliveriesWeight: -20,
|
|
442
|
-
invalidMessageDeliveriesDecay: 0.5,
|
|
443
|
-
}),
|
|
444
|
-
[checkpointProposalTopic]: createTopicScoreParams({
|
|
445
|
-
topicWeight: 1,
|
|
446
|
-
invalidMessageDeliveriesWeight: -20,
|
|
447
|
-
invalidMessageDeliveriesDecay: 0.5,
|
|
448
|
-
}),
|
|
449
|
-
[checkpointAttestationTopic]: createTopicScoreParams({
|
|
450
|
-
topicWeight: 1,
|
|
451
|
-
invalidMessageDeliveriesWeight: -20,
|
|
452
|
-
invalidMessageDeliveriesDecay: 0.5,
|
|
453
|
-
}),
|
|
454
|
-
},
|
|
441
|
+
topics: topicScoreParams,
|
|
455
442
|
}),
|
|
456
443
|
}) as (components: GossipSubComponents) => GossipSub,
|
|
457
444
|
components: (components: { connectionManager: ConnectionManager }) => ({
|
|
@@ -477,8 +464,12 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
477
464
|
epochCache,
|
|
478
465
|
);
|
|
479
466
|
|
|
480
|
-
//
|
|
481
|
-
|
|
467
|
+
// Configure application-specific scoring for gossipsub.
|
|
468
|
+
// The weight scales app score to align with gossipsub thresholds:
|
|
469
|
+
// - Disconnect (-50) × 10 = -500 = gossipThreshold (stops receiving gossip)
|
|
470
|
+
// - Ban (-100) × 10 = -1000 = publishThreshold (cannot publish)
|
|
471
|
+
// Note: positive topic scores can offset penalties, so alignment is best-effort.
|
|
472
|
+
node.services.pubsub.score.params.appSpecificWeight = APP_SPECIFIC_WEIGHT;
|
|
482
473
|
node.services.pubsub.score.params.appSpecificScore = (peerId: string) =>
|
|
483
474
|
peerManager.shouldDisableP2PGossip(peerId) ? -Infinity : peerManager.getPeerScore(peerId);
|
|
484
475
|
|
|
@@ -685,6 +676,15 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
685
676
|
this.duplicateProposalCallback = callback;
|
|
686
677
|
}
|
|
687
678
|
|
|
679
|
+
/**
|
|
680
|
+
* Registers a callback to be invoked when a duplicate attestation is detected.
|
|
681
|
+
* A validator signing attestations for different proposals at the same slot.
|
|
682
|
+
* This callback is triggered on the first duplicate (when count goes from 1 to 2).
|
|
683
|
+
*/
|
|
684
|
+
public registerDuplicateAttestationCallback(callback: P2PDuplicateAttestationCallback): void {
|
|
685
|
+
this.duplicateAttestationCallback = callback;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
688
|
/**
|
|
689
689
|
* Subscribes to a topic.
|
|
690
690
|
* @param topic - The topic to subscribe to.
|
|
@@ -902,20 +902,33 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
902
902
|
const validationFunc: () => Promise<ReceivedMessageValidationResult<Tx>> = async () => {
|
|
903
903
|
const tx = Tx.fromBuffer(payloadData);
|
|
904
904
|
const isValid = await this.validatePropagatedTx(tx, source);
|
|
905
|
-
|
|
905
|
+
if (!isValid) {
|
|
906
|
+
this.logger.trace(`Rejecting invalid propagated tx`, {
|
|
907
|
+
[Attributes.P2P_ID]: source.toString(),
|
|
908
|
+
});
|
|
909
|
+
return { result: TopicValidatorResult.Reject };
|
|
910
|
+
}
|
|
911
|
+
|
|
912
|
+
// Propagate only on pool acceptance
|
|
913
|
+
const txHash = tx.getTxHash();
|
|
914
|
+
const addResult = await this.mempools.txPool.addPendingTxs([tx], { source: 'gossip' });
|
|
915
|
+
|
|
916
|
+
const wasAccepted = addResult.accepted.some(h => h.equals(txHash));
|
|
917
|
+
const wasIgnored = addResult.ignored.some(h => h.equals(txHash));
|
|
906
918
|
|
|
907
919
|
this.logger.trace(`Validate propagated tx`, {
|
|
908
920
|
isValid,
|
|
909
|
-
|
|
921
|
+
wasAccepted,
|
|
922
|
+
wasIgnored,
|
|
910
923
|
[Attributes.P2P_ID]: source.toString(),
|
|
911
924
|
});
|
|
912
925
|
|
|
913
|
-
if (
|
|
914
|
-
return { result: TopicValidatorResult.
|
|
915
|
-
} else if (
|
|
926
|
+
if (wasAccepted) {
|
|
927
|
+
return { result: TopicValidatorResult.Accept, obj: tx };
|
|
928
|
+
} else if (wasIgnored) {
|
|
916
929
|
return { result: TopicValidatorResult.Ignore, obj: tx };
|
|
917
930
|
} else {
|
|
918
|
-
return { result: TopicValidatorResult.
|
|
931
|
+
return { result: TopicValidatorResult.Reject };
|
|
919
932
|
}
|
|
920
933
|
};
|
|
921
934
|
|
|
@@ -924,6 +937,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
924
937
|
return;
|
|
925
938
|
}
|
|
926
939
|
|
|
940
|
+
// Tx was accepted into pool and will be propagated - just log and record metrics
|
|
927
941
|
const txHash = tx.getTxHash();
|
|
928
942
|
const txHashString = txHash.toString();
|
|
929
943
|
this.logger.verbose(`Received tx ${txHashString} from external peer ${source.toString()} via gossip`, {
|
|
@@ -931,13 +945,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
931
945
|
txHash: txHashString,
|
|
932
946
|
});
|
|
933
947
|
|
|
934
|
-
if (this.config.dropTransactions && randomInt(1000) < this.config.dropTransactionsProbability * 1000) {
|
|
935
|
-
this.logger.warn(`Intentionally dropping tx ${txHashString} (probability rule)`);
|
|
936
|
-
return;
|
|
937
|
-
}
|
|
938
|
-
|
|
939
948
|
this.instrumentation.incrementTxReceived(1);
|
|
940
|
-
await this.mempools.txPool.addTxs([tx]);
|
|
941
949
|
}
|
|
942
950
|
|
|
943
951
|
/**
|
|
@@ -991,40 +999,53 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
991
999
|
return { result: TopicValidatorResult.Ignore, obj: attestation };
|
|
992
1000
|
}
|
|
993
1001
|
|
|
994
|
-
// Get committee size for the attestation's slot
|
|
995
|
-
const slot = attestation.payload.header.slotNumber;
|
|
996
|
-
const { committee } = await this.epochCache.getCommittee(slot);
|
|
997
|
-
const committeeSize = committee?.length ?? 0;
|
|
998
|
-
|
|
999
1002
|
// Try to add the attestation: this handles existence check, cap check, and adding in one call
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1003
|
+
// count is the number of attestations by this signer for this slot (for duplicate detection)
|
|
1004
|
+
const slot = attestation.payload.header.slotNumber;
|
|
1005
|
+
const { added, alreadyExists, count } =
|
|
1006
|
+
await this.mempools.attestationPool.tryAddCheckpointAttestation(attestation);
|
|
1004
1007
|
|
|
1005
1008
|
this.logger.trace(`Validate propagated checkpoint attestation`, {
|
|
1006
1009
|
added,
|
|
1007
1010
|
alreadyExists,
|
|
1011
|
+
count,
|
|
1008
1012
|
[Attributes.SLOT_NUMBER]: slot.toString(),
|
|
1009
1013
|
[Attributes.P2P_ID]: peerId.toString(),
|
|
1010
1014
|
});
|
|
1011
1015
|
|
|
1012
|
-
//
|
|
1016
|
+
// Exact same attestation received, no need to re-broadcast
|
|
1013
1017
|
if (alreadyExists) {
|
|
1014
1018
|
return { result: TopicValidatorResult.Ignore, obj: attestation };
|
|
1015
1019
|
}
|
|
1016
1020
|
|
|
1017
|
-
// Could not add (cap reached), no need to re-broadcast
|
|
1021
|
+
// Could not add (cap reached for signer), no need to re-broadcast
|
|
1018
1022
|
if (!added) {
|
|
1019
|
-
this.logger.warn(`Dropping checkpoint attestation due to
|
|
1023
|
+
this.logger.warn(`Dropping checkpoint attestation due to cap`, {
|
|
1020
1024
|
slot: slot.toString(),
|
|
1021
1025
|
archive: attestation.archive.toString(),
|
|
1022
1026
|
source: peerId.toString(),
|
|
1027
|
+
attester: attestation.getSender()?.toString(),
|
|
1028
|
+
count,
|
|
1023
1029
|
});
|
|
1024
1030
|
return { result: TopicValidatorResult.Ignore, obj: attestation };
|
|
1025
1031
|
}
|
|
1026
1032
|
|
|
1027
|
-
//
|
|
1033
|
+
// Check if this is a duplicate attestation (signer attested to a different proposal at the same slot)
|
|
1034
|
+
// count is the number of attestations by this signer for this slot
|
|
1035
|
+
if (count === 2) {
|
|
1036
|
+
const attester = attestation.getSender();
|
|
1037
|
+
if (attester) {
|
|
1038
|
+
this.logger.warn(`Detected duplicate attestation (equivocation) at slot ${slot}`, {
|
|
1039
|
+
slot: slot.toString(),
|
|
1040
|
+
archive: attestation.archive.toString(),
|
|
1041
|
+
source: peerId.toString(),
|
|
1042
|
+
attester: attester.toString(),
|
|
1043
|
+
});
|
|
1044
|
+
this.duplicateAttestationCallback?.({ slot, attester });
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Attestation was added successfully - accept it so other nodes can also detect the equivocation
|
|
1028
1049
|
return { result: TopicValidatorResult.Accept, obj: attestation };
|
|
1029
1050
|
}
|
|
1030
1051
|
|
|
@@ -1070,8 +1091,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1070
1091
|
}
|
|
1071
1092
|
|
|
1072
1093
|
// Try to add the proposal: this handles existence check, cap check, and adding in one call
|
|
1073
|
-
const { added, alreadyExists,
|
|
1074
|
-
const isEquivocated =
|
|
1094
|
+
const { added, alreadyExists, count } = await this.mempools.attestationPool.tryAddBlockProposal(block);
|
|
1095
|
+
const isEquivocated = count !== undefined && count > 1;
|
|
1075
1096
|
|
|
1076
1097
|
// Duplicate proposal received, no need to re-broadcast
|
|
1077
1098
|
if (alreadyExists) {
|
|
@@ -1090,7 +1111,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1090
1111
|
this.logger.warn(`Penalizing peer for block proposal exceeding per-position cap`, {
|
|
1091
1112
|
...block.toBlockInfo(),
|
|
1092
1113
|
indexWithinCheckpoint: block.indexWithinCheckpoint,
|
|
1093
|
-
|
|
1114
|
+
count,
|
|
1094
1115
|
proposer: block.getSender()?.toString(),
|
|
1095
1116
|
source: peerId.toString(),
|
|
1096
1117
|
});
|
|
@@ -1107,7 +1128,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1107
1128
|
proposer: proposer?.toString(),
|
|
1108
1129
|
});
|
|
1109
1130
|
// Invoke the duplicate callback on the first duplicate spotted only
|
|
1110
|
-
if (proposer &&
|
|
1131
|
+
if (proposer && count === 2) {
|
|
1111
1132
|
this.duplicateProposalCallback?.({ slot: block.slotNumber, proposer, type: 'block' });
|
|
1112
1133
|
}
|
|
1113
1134
|
return { result: TopicValidatorResult.Accept, obj: block, metadata: { isEquivocated } };
|
|
@@ -1132,8 +1153,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1132
1153
|
...block.toBlockInfo(),
|
|
1133
1154
|
});
|
|
1134
1155
|
|
|
1135
|
-
// Mark the txs in this proposal as
|
|
1136
|
-
await this.mempools.txPool.
|
|
1156
|
+
// Mark the txs in this proposal as protected
|
|
1157
|
+
await this.mempools.txPool.protectTxs(block.txHashes, block.blockHeader);
|
|
1137
1158
|
|
|
1138
1159
|
// Call the block received callback to validate the proposal.
|
|
1139
1160
|
// Note: Validators do NOT attest to individual blocks, only to checkpoint proposals.
|
|
@@ -1224,8 +1245,8 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1224
1245
|
// Try to add the checkpoint proposal core: this handles existence check, cap check, and adding in one call
|
|
1225
1246
|
const checkpointCore = checkpoint.toCore();
|
|
1226
1247
|
const tryAddResult = await this.mempools.attestationPool.tryAddCheckpointProposal(checkpointCore);
|
|
1227
|
-
const { added, alreadyExists,
|
|
1228
|
-
const isEquivocated =
|
|
1248
|
+
const { added, alreadyExists, count } = tryAddResult;
|
|
1249
|
+
const isEquivocated = count !== undefined && count > 1;
|
|
1229
1250
|
|
|
1230
1251
|
// Duplicate proposal received, do not re-broadcast
|
|
1231
1252
|
if (alreadyExists) {
|
|
@@ -1246,7 +1267,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1246
1267
|
this.peerManager.penalizePeer(peerId, PeerErrorSeverity.HighToleranceError);
|
|
1247
1268
|
this.logger.warn(`Penalizing peer for checkpoint proposal exceeding per-slot cap`, {
|
|
1248
1269
|
...checkpoint.toCheckpointInfo(),
|
|
1249
|
-
|
|
1270
|
+
count,
|
|
1250
1271
|
source: peerId.toString(),
|
|
1251
1272
|
});
|
|
1252
1273
|
return { result: TopicValidatorResult.Reject, obj: checkpoint, metadata: { isEquivocated, processBlock } };
|
|
@@ -1262,7 +1283,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1262
1283
|
proposer: proposer?.toString(),
|
|
1263
1284
|
});
|
|
1264
1285
|
// Invoke the duplicate callback on the first duplicate spotted only
|
|
1265
|
-
if (proposer &&
|
|
1286
|
+
if (proposer && count === 2) {
|
|
1266
1287
|
this.duplicateProposalCallback?.({ slot: checkpoint.slotNumber, proposer, type: 'checkpoint' });
|
|
1267
1288
|
}
|
|
1268
1289
|
return {
|
|
@@ -1520,10 +1541,10 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1520
1541
|
@trackSpan('Libp2pService.validatePropagatedTx', tx => ({
|
|
1521
1542
|
[Attributes.TX_HASH]: tx.getTxHash().toString(),
|
|
1522
1543
|
}))
|
|
1523
|
-
|
|
1544
|
+
protected async validatePropagatedTx(tx: Tx, peerId: PeerId): Promise<boolean> {
|
|
1524
1545
|
const currentBlockNumber = await this.archiver.getBlockNumber();
|
|
1525
1546
|
|
|
1526
|
-
// We accept transactions if they are not expired by the next slot (checked based on the
|
|
1547
|
+
// We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
|
|
1527
1548
|
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
1528
1549
|
const messageValidators = await this.createMessageValidators(currentBlockNumber, nextSlotTimestamp);
|
|
1529
1550
|
|
|
@@ -1578,7 +1599,7 @@ export class LibP2PService<T extends P2PClientType = P2PClientType.Full> extends
|
|
|
1578
1599
|
public async validate(txs: Tx[]): Promise<void> {
|
|
1579
1600
|
const currentBlockNumber = await this.archiver.getBlockNumber();
|
|
1580
1601
|
|
|
1581
|
-
// We accept transactions if they are not expired by the next slot (checked based on the
|
|
1602
|
+
// We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
|
|
1582
1603
|
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
1583
1604
|
const messageValidators = await this.createMessageValidators(currentBlockNumber, nextSlotTimestamp);
|
|
1584
1605
|
|
|
@@ -14,6 +14,21 @@ import type { PeerId } from '@libp2p/interface';
|
|
|
14
14
|
|
|
15
15
|
import type { P2PConfig } from '../../config.js';
|
|
16
16
|
|
|
17
|
+
/**
|
|
18
|
+
* Application-level peer penalties.
|
|
19
|
+
*
|
|
20
|
+
* These scores are multiplied by appSpecificWeight (10) when contributing to gossipsub score.
|
|
21
|
+
* The values are designed to align with gossipsub thresholds:
|
|
22
|
+
*
|
|
23
|
+
* - LowToleranceError (50): 1 error → app score -50 → gossipsub -500 → gossipThreshold
|
|
24
|
+
* - MidToleranceError (10): 5 errors → app score -50 → gossipsub -500 → gossipThreshold
|
|
25
|
+
* - HighToleranceError (2): 25 errors → app score -50 → gossipsub -500 → gossipThreshold
|
|
26
|
+
*
|
|
27
|
+
* Examples of each severity:
|
|
28
|
+
* - LowToleranceError: Invalid messages, deserialization errors, manipulation attempts
|
|
29
|
+
* - MidToleranceError: Hash mismatches, protocol violations
|
|
30
|
+
* - HighToleranceError: Rate limit exceeded, failed responses, transient errors
|
|
31
|
+
*/
|
|
17
32
|
const DefaultPeerPenalties = {
|
|
18
33
|
[PeerErrorSeverity.LowToleranceError]: 50,
|
|
19
34
|
[PeerErrorSeverity.MidToleranceError]: 10,
|
|
@@ -26,6 +41,16 @@ export enum PeerScoreState {
|
|
|
26
41
|
Healthy,
|
|
27
42
|
}
|
|
28
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Score thresholds for peer states.
|
|
46
|
+
*
|
|
47
|
+
* These values align with gossipsub thresholds when multiplied by appSpecificWeight (10):
|
|
48
|
+
* - MIN_SCORE_BEFORE_DISCONNECT (-50) × 10 = -500 = gossipThreshold
|
|
49
|
+
* - MIN_SCORE_BEFORE_BAN (-100) × 10 = -1000 = publishThreshold
|
|
50
|
+
*
|
|
51
|
+
* This ensures that when a peer is disconnected at the application level,
|
|
52
|
+
* they also stop receiving gossip, and when banned, they cannot publish.
|
|
53
|
+
*/
|
|
29
54
|
// TODO: move into config / constants
|
|
30
55
|
const MIN_SCORE_BEFORE_BAN = -100;
|
|
31
56
|
const MIN_SCORE_BEFORE_DISCONNECT = -50;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
2
|
import { L2Block } from '@aztec/stdlib/block';
|
|
3
|
+
import { MAX_L2_BLOCK_SIZE_KB } from '@aztec/stdlib/p2p';
|
|
3
4
|
import { TxArray, TxHashArray } from '@aztec/stdlib/tx';
|
|
4
5
|
|
|
5
6
|
import type { PeerId } from '@libp2p/interface';
|
|
@@ -7,8 +8,13 @@ import type { PeerId } from '@libp2p/interface';
|
|
|
7
8
|
import type { P2PReqRespConfig } from './config.js';
|
|
8
9
|
import type { ConnectionSampler } from './connection-sampler/connection_sampler.js';
|
|
9
10
|
import { AuthRequest, AuthResponse } from './protocols/auth.js';
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
BlockTxsRequest,
|
|
13
|
+
BlockTxsResponse,
|
|
14
|
+
calculateBlockTxsResponseSize,
|
|
15
|
+
} from './protocols/block_txs/block_txs_reqresp.js';
|
|
11
16
|
import { StatusMessage } from './protocols/status.js';
|
|
17
|
+
import { calculateTxResponseSize } from './protocols/tx.js';
|
|
12
18
|
import type { ReqRespStatus } from './status.js';
|
|
13
19
|
|
|
14
20
|
/*
|
|
@@ -211,6 +217,25 @@ export const subProtocolMap = {
|
|
|
211
217
|
},
|
|
212
218
|
};
|
|
213
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Type for a function that calculates the expected response size in KB for a given request.
|
|
222
|
+
*/
|
|
223
|
+
export type ExpectedResponseSizeCalculator = (requestBuffer: Buffer) => number;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Map of sub-protocols to their expected response size calculators.
|
|
227
|
+
* These are used to validate that responses don't exceed expected sizes based on request parameters.
|
|
228
|
+
*/
|
|
229
|
+
export const subProtocolSizeCalculators: Record<ReqRespSubProtocol, ExpectedResponseSizeCalculator> = {
|
|
230
|
+
[ReqRespSubProtocol.TX]: calculateTxResponseSize,
|
|
231
|
+
[ReqRespSubProtocol.BLOCK_TXS]: calculateBlockTxsResponseSize,
|
|
232
|
+
[ReqRespSubProtocol.BLOCK]: () => MAX_L2_BLOCK_SIZE_KB,
|
|
233
|
+
[ReqRespSubProtocol.STATUS]: () => 1,
|
|
234
|
+
[ReqRespSubProtocol.PING]: () => 1,
|
|
235
|
+
[ReqRespSubProtocol.AUTH]: () => 1,
|
|
236
|
+
[ReqRespSubProtocol.GOODBYE]: () => 1, // No response expected, but provide minimal limit
|
|
237
|
+
};
|
|
238
|
+
|
|
214
239
|
export interface ReqRespInterface {
|
|
215
240
|
start(
|
|
216
241
|
subProtocolHandlers: Partial<ReqRespSubProtocolHandlers>,
|
|
@@ -5,7 +5,7 @@ import { TxArray } from '@aztec/stdlib/tx';
|
|
|
5
5
|
import type { PeerId } from '@libp2p/interface';
|
|
6
6
|
|
|
7
7
|
import type { AttestationPoolApi } from '../../../../mem_pools/attestation_pool/attestation_pool.js';
|
|
8
|
-
import type {
|
|
8
|
+
import type { TxPoolV2 } from '../../../../mem_pools/tx_pool_v2/interfaces.js';
|
|
9
9
|
import type { ReqRespSubProtocolHandler } from '../../interface.js';
|
|
10
10
|
import { ReqRespStatus, ReqRespStatusError } from '../../status.js';
|
|
11
11
|
import { BitVector } from './bitvector.js';
|
|
@@ -21,7 +21,7 @@ import { BlockTxsRequest, BlockTxsResponse } from './block_txs_reqresp.js';
|
|
|
21
21
|
export function reqRespBlockTxsHandler(
|
|
22
22
|
attestationPool: AttestationPoolApi,
|
|
23
23
|
archiver: L2BlockSource,
|
|
24
|
-
txPool:
|
|
24
|
+
txPool: TxPoolV2,
|
|
25
25
|
): ReqRespSubProtocolHandler {
|
|
26
26
|
/**
|
|
27
27
|
* Handler for block txs requests
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
2
2
|
import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
|
|
3
|
+
import { MAX_TX_SIZE_KB } from '@aztec/stdlib/p2p';
|
|
3
4
|
import { TxArray, type TxHash, TxHashArray } from '@aztec/stdlib/tx';
|
|
4
5
|
|
|
5
6
|
import { BitVector } from './bitvector.js';
|
|
@@ -125,3 +126,19 @@ export class BlockTxsResponse {
|
|
|
125
126
|
return new BlockTxsResponse(Fr.ZERO, new TxArray(), BitVector.init(0, []));
|
|
126
127
|
}
|
|
127
128
|
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Calculate the expected response size for a BLOCK_TXS request.
|
|
132
|
+
* @param requestBuffer - The serialized request buffer containing BlockTxsRequest
|
|
133
|
+
* @returns Expected response size in KB
|
|
134
|
+
*/
|
|
135
|
+
export function calculateBlockTxsResponseSize(requestBuffer: Buffer): number {
|
|
136
|
+
try {
|
|
137
|
+
const request = BlockTxsRequest.fromBuffer(requestBuffer);
|
|
138
|
+
const requestedTxCount = request.txIndices.getTrueIndices().length;
|
|
139
|
+
return requestedTxCount * MAX_TX_SIZE_KB + 1; // +1 KB overhead for serialization
|
|
140
|
+
} catch {
|
|
141
|
+
// If we can't parse the request, fall back to allowing a single transaction response
|
|
142
|
+
return MAX_TX_SIZE_KB + 1;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { chunk } from '@aztec/foundation/collection';
|
|
2
|
+
import { MAX_TX_SIZE_KB } from '@aztec/stdlib/p2p';
|
|
2
3
|
import { TxArray, TxHash, TxHashArray } from '@aztec/stdlib/tx';
|
|
3
4
|
|
|
4
5
|
import type { PeerId } from '@libp2p/interface';
|
|
@@ -55,3 +56,24 @@ export function reqRespTxHandler(mempools: MemPools): ReqRespSubProtocolHandler
|
|
|
55
56
|
export function chunkTxHashesRequest(hashes: TxHash[], chunkSize = 1): Array<TxHashArray> {
|
|
56
57
|
return chunk(hashes, chunkSize).map(chunk => new TxHashArray(...chunk));
|
|
57
58
|
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Calculate the expected response size for a TX request.
|
|
62
|
+
* @param requestBuffer - The serialized request buffer containing TxHashArray
|
|
63
|
+
* @returns Expected response size in KB
|
|
64
|
+
*/
|
|
65
|
+
export function calculateTxResponseSize(requestBuffer: Buffer): number {
|
|
66
|
+
try {
|
|
67
|
+
const txHashes = TxHashArray.fromBuffer(requestBuffer);
|
|
68
|
+
// TxHashArray.fromBuffer returns empty array on parse failure, so check for that
|
|
69
|
+
if (txHashes.length === 0 && requestBuffer.length > 0) {
|
|
70
|
+
// If we got an empty array but had a non-empty buffer, parsing likely failed
|
|
71
|
+
// Fall back to allowing a single transaction response
|
|
72
|
+
return MAX_TX_SIZE_KB + 1;
|
|
73
|
+
}
|
|
74
|
+
return Math.max(txHashes.length, 1) * MAX_TX_SIZE_KB + 1; // +1 KB overhead, at least 1 tx
|
|
75
|
+
} catch {
|
|
76
|
+
// If we can't parse the request, fall back to allowing a single transaction response
|
|
77
|
+
return MAX_TX_SIZE_KB + 1;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
type ReqRespSubProtocolValidators,
|
|
37
37
|
type SubProtocolMap,
|
|
38
38
|
responseFromBuffer,
|
|
39
|
+
subProtocolSizeCalculators,
|
|
39
40
|
} from './interface.js';
|
|
40
41
|
import { ReqRespMetrics } from './metrics.js';
|
|
41
42
|
import {
|
|
@@ -437,6 +438,9 @@ export class ReqResp implements ReqRespInterface {
|
|
|
437
438
|
try {
|
|
438
439
|
this.metrics.recordRequestSent(subProtocol);
|
|
439
440
|
|
|
441
|
+
// Calculate expected response size based on the request payload
|
|
442
|
+
const expectedSizeKb = subProtocolSizeCalculators[subProtocol](payload);
|
|
443
|
+
|
|
440
444
|
this.logger.trace(`Sending request to peer ${peerId.toString()} on sub protocol ${subProtocol}`);
|
|
441
445
|
stream = await this.connectionSampler.dialProtocol(peerId, subProtocol, dialTimeout);
|
|
442
446
|
this.logger.trace(
|
|
@@ -444,11 +448,14 @@ export class ReqResp implements ReqRespInterface {
|
|
|
444
448
|
);
|
|
445
449
|
|
|
446
450
|
const timeoutErr = new IndividualReqRespTimeoutError();
|
|
451
|
+
// Create a wrapper to pass the expected size to readMessage
|
|
452
|
+
const readMessageWithSizeLimit = (source: AsyncIterable<Uint8ArrayList>) =>
|
|
453
|
+
this.readMessage(source, expectedSizeKb);
|
|
447
454
|
const [_, resp] = await executeTimeout(
|
|
448
455
|
signal =>
|
|
449
456
|
Promise.all([
|
|
450
457
|
pipeline([payload], stream!.sink, { signal }),
|
|
451
|
-
pipeline(stream!.source,
|
|
458
|
+
pipeline(stream!.source, readMessageWithSizeLimit, { signal }),
|
|
452
459
|
]),
|
|
453
460
|
this.individualRequestTimeoutMs,
|
|
454
461
|
() => timeoutErr,
|
|
@@ -510,8 +517,11 @@ export class ReqResp implements ReqRespInterface {
|
|
|
510
517
|
* The message is split into two components
|
|
511
518
|
* - The first chunk should contain a control byte, indicating the status of the response see `ReqRespStatus`
|
|
512
519
|
* - The second chunk should contain the response data
|
|
520
|
+
*
|
|
521
|
+
* @param source - The async iterable source of data chunks
|
|
522
|
+
* @param maxSizeKb - Optional maximum expected size in KB for the decompressed response
|
|
513
523
|
*/
|
|
514
|
-
private async readMessage(source: AsyncIterable<Uint8ArrayList
|
|
524
|
+
private async readMessage(source: AsyncIterable<Uint8ArrayList>, maxSizeKb?: number): Promise<ReqRespResponse> {
|
|
515
525
|
let status: ReqRespStatus | undefined;
|
|
516
526
|
const chunks: Uint8Array[] = [];
|
|
517
527
|
|
|
@@ -536,7 +546,7 @@ export class ReqResp implements ReqRespInterface {
|
|
|
536
546
|
}
|
|
537
547
|
|
|
538
548
|
const messageData = Buffer.concat(chunks);
|
|
539
|
-
const message: Buffer = this.snappyTransform.inboundTransformData(messageData);
|
|
549
|
+
const message: Buffer = this.snappyTransform.inboundTransformData(messageData, undefined, maxSizeKb);
|
|
540
550
|
|
|
541
551
|
return {
|
|
542
552
|
status: status ?? ReqRespStatus.UNKNOWN,
|
package/src/services/service.ts
CHANGED
|
@@ -57,6 +57,19 @@ export type DuplicateProposalInfo = {
|
|
|
57
57
|
*/
|
|
58
58
|
export type P2PDuplicateProposalCallback = (info: DuplicateProposalInfo) => void;
|
|
59
59
|
|
|
60
|
+
/** Minimal info passed to the duplicate attestation callback. */
|
|
61
|
+
export type DuplicateAttestationInfo = {
|
|
62
|
+
slot: SlotNumber;
|
|
63
|
+
attester: EthAddress;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Callback for when a duplicate attestation is detected (equivocation).
|
|
68
|
+
* A validator signing attestations for different proposals at the same slot.
|
|
69
|
+
* Invoked on the first duplicate (when count goes from 1 to 2).
|
|
70
|
+
*/
|
|
71
|
+
export type P2PDuplicateAttestationCallback = (info: DuplicateAttestationInfo) => void;
|
|
72
|
+
|
|
60
73
|
/**
|
|
61
74
|
* The interface for a P2P service implementation.
|
|
62
75
|
*/
|
|
@@ -106,6 +119,13 @@ export interface P2PService {
|
|
|
106
119
|
*/
|
|
107
120
|
registerDuplicateProposalCallback(callback: P2PDuplicateProposalCallback): void;
|
|
108
121
|
|
|
122
|
+
/**
|
|
123
|
+
* Registers a callback invoked when a duplicate attestation is detected (equivocation).
|
|
124
|
+
* A validator signing attestations for different proposals at the same slot.
|
|
125
|
+
* The callback is triggered on the first duplicate (when count goes from 1 to 2).
|
|
126
|
+
*/
|
|
127
|
+
registerDuplicateAttestationCallback(callback: P2PDuplicateAttestationCallback): void;
|
|
128
|
+
|
|
109
129
|
getEnr(): ENR | undefined;
|
|
110
130
|
|
|
111
131
|
getPeers(includePending?: boolean): PeerInfo[];
|