@aztec/p2p 0.0.1-commit.6d63667d → 0.0.1-commit.7cf39cb55
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 +5 -5
- package/dest/client/factory.d.ts.map +1 -1
- package/dest/client/factory.js +44 -10
- package/dest/client/interface.d.ts +37 -15
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +35 -36
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +114 -138
- package/dest/client/test/tx_proposal_collector/proposal_tx_collector_worker.js +1 -1
- 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 +104 -88
- package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/attestation_pool.js +441 -3
- package/dest/mem_pools/attestation_pool/attestation_pool_test_suite.d.ts +2 -2
- 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 +353 -87
- package/dest/mem_pools/attestation_pool/index.d.ts +2 -3
- package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -1
- package/dest/mem_pools/attestation_pool/index.js +1 -2
- package/dest/mem_pools/index.d.ts +3 -2
- package/dest/mem_pools/index.d.ts.map +1 -1
- package/dest/mem_pools/index.js +1 -1
- package/dest/mem_pools/interface.d.ts +5 -5
- package/dest/mem_pools/interface.d.ts.map +1 -1
- 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 +3 -1
- 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 +27 -3
- 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/attestation_validator/fisherman_attestation_validator.d.ts +3 -3
- package/dest/msg_validators/attestation_validator/fisherman_attestation_validator.d.ts.map +1 -1
- 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 +2 -2
- package/dest/services/dummy_service.d.ts +10 -2
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +6 -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 +84 -35
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +364 -265
- 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 +4 -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 +35 -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 +40 -38
- package/dest/test-helpers/testbench-utils.d.ts.map +1 -1
- package/dest/test-helpers/testbench-utils.js +128 -59
- package/dest/testbench/p2p_client_testbench_worker.js +2 -2
- package/package.json +14 -14
- package/src/client/factory.ts +81 -13
- package/src/client/interface.ts +45 -14
- package/src/client/p2p_client.ts +151 -161
- package/src/client/test/tx_proposal_collector/proposal_tx_collector_worker.ts +1 -1
- package/src/config.ts +34 -2
- package/src/index.ts +1 -0
- package/src/mem_pools/attestation_pool/attestation_pool.ts +496 -91
- package/src/mem_pools/attestation_pool/attestation_pool_test_suite.ts +442 -102
- package/src/mem_pools/attestation_pool/index.ts +9 -2
- package/src/mem_pools/index.ts +4 -1
- package/src/mem_pools/interface.ts +4 -4
- package/src/mem_pools/tx_pool_v2/README.md +75 -9
- 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 +1 -1
- 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 +55 -5
- 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/attestation_validator/fisherman_attestation_validator.ts +2 -2
- 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 +19 -14
- package/src/services/dummy_service.ts +12 -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 +360 -269
- 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 +4 -3
- 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 +40 -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 +127 -71
- package/src/testbench/p2p_client_testbench_worker.ts +2 -2
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts +0 -40
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.d.ts.map +0 -1
- package/dest/mem_pools/attestation_pool/kv_attestation_pool.js +0 -218
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts +0 -31
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +0 -1
- package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +0 -180
- package/src/mem_pools/attestation_pool/kv_attestation_pool.ts +0 -320
- package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +0 -264
|
@@ -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>,
|
|
@@ -4,7 +4,8 @@ import { TxArray } from '@aztec/stdlib/tx';
|
|
|
4
4
|
|
|
5
5
|
import type { PeerId } from '@libp2p/interface';
|
|
6
6
|
|
|
7
|
-
import type {
|
|
7
|
+
import type { AttestationPoolApi } from '../../../../mem_pools/attestation_pool/attestation_pool.js';
|
|
8
|
+
import type { TxPoolV2 } from '../../../../mem_pools/tx_pool_v2/interfaces.js';
|
|
8
9
|
import type { ReqRespSubProtocolHandler } from '../../interface.js';
|
|
9
10
|
import { ReqRespStatus, ReqRespStatusError } from '../../status.js';
|
|
10
11
|
import { BitVector } from './bitvector.js';
|
|
@@ -18,9 +19,9 @@ import { BlockTxsRequest, BlockTxsResponse } from './block_txs_reqresp.js';
|
|
|
18
19
|
* @returns the BlockTxs request handler
|
|
19
20
|
*/
|
|
20
21
|
export function reqRespBlockTxsHandler(
|
|
21
|
-
attestationPool:
|
|
22
|
+
attestationPool: AttestationPoolApi,
|
|
22
23
|
archiver: L2BlockSource,
|
|
23
|
-
txPool:
|
|
24
|
+
txPool: TxPoolV2,
|
|
24
25
|
): ReqRespSubProtocolHandler {
|
|
25
26
|
/**
|
|
26
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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SlotNumber } from '@aztec/foundation/branded-types';
|
|
1
2
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
2
3
|
import type { PeerInfo } from '@aztec/stdlib/interfaces/server';
|
|
3
4
|
import type { BlockProposal, CheckpointAttestation, CheckpointProposalCore, Gossipable } from '@aztec/stdlib/p2p';
|
|
@@ -43,6 +44,32 @@ export type P2PCheckpointReceivedCallback = (
|
|
|
43
44
|
|
|
44
45
|
export type AuthReceivedCallback = (peerId: PeerId, authRequest: AuthRequest) => Promise<AuthResponse | undefined>;
|
|
45
46
|
|
|
47
|
+
/** Minimal info passed to the duplicate proposal callback. */
|
|
48
|
+
export type DuplicateProposalInfo = {
|
|
49
|
+
slot: SlotNumber;
|
|
50
|
+
proposer: EthAddress;
|
|
51
|
+
type: 'checkpoint' | 'block';
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Callback for when a duplicate proposal is detected (equivocation).
|
|
56
|
+
* Invoked on the first duplicate (when count goes from 1 to 2).
|
|
57
|
+
*/
|
|
58
|
+
export type P2PDuplicateProposalCallback = (info: DuplicateProposalInfo) => void;
|
|
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
|
+
|
|
46
73
|
/**
|
|
47
74
|
* The interface for a P2P service implementation.
|
|
48
75
|
*/
|
|
@@ -86,6 +113,19 @@ export interface P2PService {
|
|
|
86
113
|
|
|
87
114
|
registerCheckpointReceivedCallback(callback: P2PCheckpointReceivedCallback): void;
|
|
88
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Registers a callback invoked when a duplicate proposal is detected (equivocation).
|
|
118
|
+
* The callback is triggered on the first duplicate (when count goes from 1 to 2).
|
|
119
|
+
*/
|
|
120
|
+
registerDuplicateProposalCallback(callback: P2PDuplicateProposalCallback): void;
|
|
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
|
+
|
|
89
129
|
getEnr(): ENR | undefined;
|
|
90
130
|
|
|
91
131
|
getPeers(includePending?: boolean): PeerInfo[];
|
|
@@ -31,6 +31,24 @@ export type TxCollectionConfig = {
|
|
|
31
31
|
txCollectionNodeRpcMaxBatchSize: number;
|
|
32
32
|
/** Which collector implementation to use for missing txs collection */
|
|
33
33
|
txCollectionMissingTxsCollectorType: MissingTxsCollectorType;
|
|
34
|
+
/** A comma-separated list of file store URLs (s3://, gs://, file://, http://) for tx collection */
|
|
35
|
+
txCollectionFileStoreUrls: string[];
|
|
36
|
+
/** Delay in ms before file store collection starts after slow collection is triggered */
|
|
37
|
+
txCollectionFileStoreSlowDelayMs: number;
|
|
38
|
+
/** Delay in ms before file store collection starts after fast collection is triggered */
|
|
39
|
+
txCollectionFileStoreFastDelayMs: number;
|
|
40
|
+
/** Number of concurrent workers for fast file store collection */
|
|
41
|
+
txCollectionFileStoreFastWorkerCount: number;
|
|
42
|
+
/** Number of concurrent workers for slow file store collection */
|
|
43
|
+
txCollectionFileStoreSlowWorkerCount: number;
|
|
44
|
+
/** Base backoff time in ms for fast file store collection retries */
|
|
45
|
+
txCollectionFileStoreFastBackoffBaseMs: number;
|
|
46
|
+
/** Base backoff time in ms for slow file store collection retries */
|
|
47
|
+
txCollectionFileStoreSlowBackoffBaseMs: number;
|
|
48
|
+
/** Max backoff time in ms for fast file store collection retries */
|
|
49
|
+
txCollectionFileStoreFastBackoffMaxMs: number;
|
|
50
|
+
/** Max backoff time in ms for slow file store collection retries */
|
|
51
|
+
txCollectionFileStoreSlowBackoffMaxMs: number;
|
|
34
52
|
};
|
|
35
53
|
|
|
36
54
|
export const txCollectionConfigMappings: ConfigMappingsType<TxCollectionConfig> = {
|
|
@@ -95,4 +113,54 @@ export const txCollectionConfigMappings: ConfigMappingsType<TxCollectionConfig>
|
|
|
95
113
|
description: 'Which collector implementation to use for missing txs collection (new or old)',
|
|
96
114
|
...enumConfigHelper(['new', 'old'] as const, 'new'),
|
|
97
115
|
},
|
|
116
|
+
txCollectionFileStoreUrls: {
|
|
117
|
+
env: 'TX_COLLECTION_FILE_STORE_URLS',
|
|
118
|
+
description: 'A comma-separated list of file store URLs (s3://, gs://, file://, http://) for tx collection',
|
|
119
|
+
parseEnv: (val: string) =>
|
|
120
|
+
val
|
|
121
|
+
.split(',')
|
|
122
|
+
.map(url => url.trim())
|
|
123
|
+
.filter(url => url.length > 0),
|
|
124
|
+
defaultValue: [],
|
|
125
|
+
},
|
|
126
|
+
txCollectionFileStoreSlowDelayMs: {
|
|
127
|
+
env: 'TX_COLLECTION_FILE_STORE_SLOW_DELAY_MS',
|
|
128
|
+
description: 'Delay before file store collection starts after slow collection',
|
|
129
|
+
...numberConfigHelper(24_000),
|
|
130
|
+
},
|
|
131
|
+
txCollectionFileStoreFastDelayMs: {
|
|
132
|
+
env: 'TX_COLLECTION_FILE_STORE_FAST_DELAY_MS',
|
|
133
|
+
description: 'Delay before file store collection starts after fast collection',
|
|
134
|
+
...numberConfigHelper(2_000),
|
|
135
|
+
},
|
|
136
|
+
txCollectionFileStoreFastWorkerCount: {
|
|
137
|
+
env: 'TX_COLLECTION_FILE_STORE_FAST_WORKER_COUNT',
|
|
138
|
+
description: 'Number of concurrent workers for fast file store collection',
|
|
139
|
+
...numberConfigHelper(5),
|
|
140
|
+
},
|
|
141
|
+
txCollectionFileStoreSlowWorkerCount: {
|
|
142
|
+
env: 'TX_COLLECTION_FILE_STORE_SLOW_WORKER_COUNT',
|
|
143
|
+
description: 'Number of concurrent workers for slow file store collection',
|
|
144
|
+
...numberConfigHelper(2),
|
|
145
|
+
},
|
|
146
|
+
txCollectionFileStoreFastBackoffBaseMs: {
|
|
147
|
+
env: 'TX_COLLECTION_FILE_STORE_FAST_BACKOFF_BASE_MS',
|
|
148
|
+
description: 'Base backoff time in ms for fast file store collection retries',
|
|
149
|
+
...numberConfigHelper(1_000),
|
|
150
|
+
},
|
|
151
|
+
txCollectionFileStoreSlowBackoffBaseMs: {
|
|
152
|
+
env: 'TX_COLLECTION_FILE_STORE_SLOW_BACKOFF_BASE_MS',
|
|
153
|
+
description: 'Base backoff time in ms for slow file store collection retries',
|
|
154
|
+
...numberConfigHelper(5_000),
|
|
155
|
+
},
|
|
156
|
+
txCollectionFileStoreFastBackoffMaxMs: {
|
|
157
|
+
env: 'TX_COLLECTION_FILE_STORE_FAST_BACKOFF_MAX_MS',
|
|
158
|
+
description: 'Max backoff time in ms for fast file store collection retries',
|
|
159
|
+
...numberConfigHelper(5_000),
|
|
160
|
+
},
|
|
161
|
+
txCollectionFileStoreSlowBackoffMaxMs: {
|
|
162
|
+
env: 'TX_COLLECTION_FILE_STORE_SLOW_BACKOFF_MAX_MS',
|
|
163
|
+
description: 'Max backoff time in ms for slow file store collection retries',
|
|
164
|
+
...numberConfigHelper(30_000),
|
|
165
|
+
},
|
|
98
166
|
};
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
SendBatchRequestCollector,
|
|
21
21
|
} from './proposal_tx_collector.js';
|
|
22
22
|
import type { FastCollectionRequest, FastCollectionRequestInput } from './tx_collection.js';
|
|
23
|
-
import type { TxCollectionSink } from './tx_collection_sink.js';
|
|
23
|
+
import type { TxAddContext, TxCollectionSink } from './tx_collection_sink.js';
|
|
24
24
|
import type { TxSource } from './tx_source.js';
|
|
25
25
|
|
|
26
26
|
export class FastTxCollection {
|
|
@@ -77,7 +77,7 @@ export class FastTxCollection {
|
|
|
77
77
|
// This promise is used to await for the collection to finish during the main collectFast method.
|
|
78
78
|
// It gets resolved in `foundTxs` when all txs have been collected, or rejected if the request is aborted or hits the deadline.
|
|
79
79
|
const promise = promiseWithResolvers<void>();
|
|
80
|
-
setTimeout(() => promise.reject(new TimeoutError(`Timed out while collecting txs`)), timeout);
|
|
80
|
+
const timeoutTimer = setTimeout(() => promise.reject(new TimeoutError(`Timed out while collecting txs`)), timeout);
|
|
81
81
|
|
|
82
82
|
const request: FastCollectionRequest = {
|
|
83
83
|
...input,
|
|
@@ -89,6 +89,7 @@ export class FastTxCollection {
|
|
|
89
89
|
};
|
|
90
90
|
|
|
91
91
|
const [duration] = await elapsed(() => this.collectFast(request, { ...opts }));
|
|
92
|
+
clearTimeout(timeoutTimer);
|
|
92
93
|
|
|
93
94
|
this.log.verbose(
|
|
94
95
|
`Collected ${request.foundTxs.size} txs out of ${txHashes.length} for ${input.type} at slot ${blockInfo.slotNumber}`,
|
|
@@ -234,6 +235,7 @@ export class FastTxCollection {
|
|
|
234
235
|
method: 'fast-node-rpc',
|
|
235
236
|
...request.blockInfo,
|
|
236
237
|
},
|
|
238
|
+
this.getAddContext(request),
|
|
237
239
|
);
|
|
238
240
|
|
|
239
241
|
// Clear from the active requests the txs we just requested
|
|
@@ -287,6 +289,7 @@ export class FastTxCollection {
|
|
|
287
289
|
},
|
|
288
290
|
Array.from(request.missingTxHashes).map(txHash => TxHash.fromString(txHash)),
|
|
289
291
|
{ description: `reqresp for slot ${slotNumber}`, method: 'fast-req-resp', ...opts, ...request.blockInfo },
|
|
292
|
+
this.getAddContext(request),
|
|
290
293
|
);
|
|
291
294
|
} catch (err) {
|
|
292
295
|
this.log.error(`Error sending fast reqresp request for txs`, err, {
|
|
@@ -296,6 +299,15 @@ export class FastTxCollection {
|
|
|
296
299
|
}
|
|
297
300
|
}
|
|
298
301
|
|
|
302
|
+
/** Returns the TxAddContext for the given request, used by the sink to add txs to the pool correctly. */
|
|
303
|
+
private getAddContext(request: FastCollectionRequest): TxAddContext {
|
|
304
|
+
if (request.type === 'proposal') {
|
|
305
|
+
return { type: 'proposal', blockHeader: request.blockProposal.blockHeader };
|
|
306
|
+
} else {
|
|
307
|
+
return { type: 'mined', block: request.block };
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
299
311
|
/**
|
|
300
312
|
* Handle txs by marking them as found for the requests that are waiting for them, and resolves the request if all its txs have been found.
|
|
301
313
|
* Called internally and from the main tx collection manager whenever the tx pool emits a tx-added event.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
2
|
+
import { type PromiseWithResolvers, promiseWithResolvers } from '@aztec/foundation/promise';
|
|
3
|
+
import { sleep } from '@aztec/foundation/sleep';
|
|
4
|
+
import { DateProvider } from '@aztec/foundation/timer';
|
|
5
|
+
import { Tx, TxHash } from '@aztec/stdlib/tx';
|
|
6
|
+
|
|
7
|
+
import type { FileStoreTxSource } from './file_store_tx_source.js';
|
|
8
|
+
import type { TxAddContext, TxCollectionSink } from './tx_collection_sink.js';
|
|
9
|
+
|
|
10
|
+
/** Configuration for a FileStoreTxCollection instance. */
|
|
11
|
+
export type FileStoreCollectionConfig = {
|
|
12
|
+
workerCount: number;
|
|
13
|
+
backoffBaseMs: number;
|
|
14
|
+
backoffMaxMs: number;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type FileStoreTxEntry = {
|
|
18
|
+
txHash: string;
|
|
19
|
+
context: TxAddContext;
|
|
20
|
+
deadline: Date;
|
|
21
|
+
attempts: number;
|
|
22
|
+
lastAttemptTime: number;
|
|
23
|
+
nextSourceIndex: number;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Collects txs from file stores as a fallback after P2P methods have been tried.
|
|
28
|
+
* Uses a shared worker pool that pulls entries with priority (fewest attempts first),
|
|
29
|
+
* retries with round-robin across sources, and applies exponential backoff between
|
|
30
|
+
* full cycles through all sources.
|
|
31
|
+
*/
|
|
32
|
+
export class FileStoreTxCollection {
|
|
33
|
+
/** Map from tx hash string to entry for all pending downloads. */
|
|
34
|
+
private entries = new Map<string, FileStoreTxEntry>();
|
|
35
|
+
|
|
36
|
+
/** Worker promises for the shared worker pool. */
|
|
37
|
+
private workers: Promise<void>[] = [];
|
|
38
|
+
|
|
39
|
+
/** Whether the worker pool is running. */
|
|
40
|
+
private running = false;
|
|
41
|
+
|
|
42
|
+
/** Signal used to wake sleeping workers when new entries arrive or stop is called. */
|
|
43
|
+
private wakeSignal: PromiseWithResolvers<void>;
|
|
44
|
+
|
|
45
|
+
constructor(
|
|
46
|
+
private readonly sources: FileStoreTxSource[],
|
|
47
|
+
private readonly txCollectionSink: TxCollectionSink,
|
|
48
|
+
private readonly config: FileStoreCollectionConfig,
|
|
49
|
+
private readonly dateProvider: DateProvider = new DateProvider(),
|
|
50
|
+
private readonly log: Logger = createLogger('p2p:file_store_tx_collection'),
|
|
51
|
+
) {
|
|
52
|
+
this.wakeSignal = promiseWithResolvers<void>();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Starts the shared worker pool. */
|
|
56
|
+
public start(): void {
|
|
57
|
+
if (this.sources.length === 0) {
|
|
58
|
+
this.log.debug('No file store sources configured');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
this.running = true;
|
|
62
|
+
for (let i = 0; i < this.config.workerCount; i++) {
|
|
63
|
+
this.workers.push(this.workerLoop());
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Stops all workers and clears state. */
|
|
68
|
+
public async stop(): Promise<void> {
|
|
69
|
+
this.running = false;
|
|
70
|
+
this.wake();
|
|
71
|
+
await Promise.all(this.workers);
|
|
72
|
+
this.workers = [];
|
|
73
|
+
this.entries.clear();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Adds entries to the shared map and wakes workers. */
|
|
77
|
+
public startCollecting(txHashes: TxHash[], context: TxAddContext, deadline: Date): void {
|
|
78
|
+
if (this.sources.length === 0 || txHashes.length === 0) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (+deadline <= this.dateProvider.now()) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
for (const txHash of txHashes) {
|
|
86
|
+
const hashStr = txHash.toString();
|
|
87
|
+
if (!this.entries.has(hashStr)) {
|
|
88
|
+
this.entries.set(hashStr, {
|
|
89
|
+
txHash: hashStr,
|
|
90
|
+
context,
|
|
91
|
+
deadline,
|
|
92
|
+
attempts: 0,
|
|
93
|
+
lastAttemptTime: 0,
|
|
94
|
+
nextSourceIndex: Math.floor(Math.random() * this.sources.length),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
this.wake();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Removes entries for txs that have been found elsewhere. */
|
|
102
|
+
public foundTxs(txs: Tx[]): void {
|
|
103
|
+
for (const tx of txs) {
|
|
104
|
+
this.entries.delete(tx.getTxHash().toString());
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Clears all pending entries. */
|
|
109
|
+
public clearPending(): void {
|
|
110
|
+
this.entries.clear();
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private async workerLoop(): Promise<void> {
|
|
114
|
+
while (this.running) {
|
|
115
|
+
const action = this.getNextAction();
|
|
116
|
+
if (action.type === 'sleep') {
|
|
117
|
+
await action.promise;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const entry = action.entry;
|
|
122
|
+
const source = this.sources[entry.nextSourceIndex % this.sources.length];
|
|
123
|
+
entry.nextSourceIndex++;
|
|
124
|
+
entry.attempts++;
|
|
125
|
+
entry.lastAttemptTime = this.dateProvider.now();
|
|
126
|
+
|
|
127
|
+
try {
|
|
128
|
+
const result = await this.txCollectionSink.collect(
|
|
129
|
+
hashes => source.getTxsByHash(hashes),
|
|
130
|
+
[TxHash.fromString(entry.txHash)],
|
|
131
|
+
{ description: `file-store ${source.getInfo()}`, method: 'file-store', fileStore: source.getInfo() },
|
|
132
|
+
entry.context,
|
|
133
|
+
);
|
|
134
|
+
if (result.txs.length > 0) {
|
|
135
|
+
this.entries.delete(entry.txHash);
|
|
136
|
+
}
|
|
137
|
+
} catch (err) {
|
|
138
|
+
this.log.trace(`Error downloading tx ${entry.txHash} from ${source.getInfo()}`, { err });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Single-pass scan: removes expired entries, finds the best ready entry, or computes sleep time. */
|
|
144
|
+
private getNextAction(): { type: 'process'; entry: FileStoreTxEntry } | { type: 'sleep'; promise: Promise<void> } {
|
|
145
|
+
const now = this.dateProvider.now();
|
|
146
|
+
let best: FileStoreTxEntry | undefined;
|
|
147
|
+
let earliestReadyAt = Infinity;
|
|
148
|
+
|
|
149
|
+
for (const [key, entry] of this.entries) {
|
|
150
|
+
if (+entry.deadline <= now) {
|
|
151
|
+
this.entries.delete(key);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
const backoffMs = this.getBackoffMs(entry);
|
|
155
|
+
const readyAt = entry.lastAttemptTime + backoffMs;
|
|
156
|
+
if (readyAt > now) {
|
|
157
|
+
earliestReadyAt = Math.min(earliestReadyAt, readyAt);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (!best || entry.attempts < best.attempts) {
|
|
161
|
+
best = entry;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (best) {
|
|
166
|
+
return { type: 'process', entry: best };
|
|
167
|
+
}
|
|
168
|
+
if (earliestReadyAt < Infinity) {
|
|
169
|
+
return { type: 'sleep', promise: this.sleepOrWake(earliestReadyAt - now) };
|
|
170
|
+
}
|
|
171
|
+
return { type: 'sleep', promise: this.waitForWake() };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Computes backoff for an entry. Backoff applies after a full cycle through all sources. */
|
|
175
|
+
private getBackoffMs(entry: FileStoreTxEntry): number {
|
|
176
|
+
const fullCycles = Math.floor(entry.attempts / this.sources.length);
|
|
177
|
+
if (fullCycles === 0) {
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
return Math.min(this.config.backoffBaseMs * Math.pow(2, fullCycles - 1), this.config.backoffMaxMs);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Resolves the current wake signal and creates a new one. */
|
|
184
|
+
private wake(): void {
|
|
185
|
+
this.wakeSignal.resolve();
|
|
186
|
+
this.wakeSignal = promiseWithResolvers<void>();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** Waits until the wake signal is resolved. */
|
|
190
|
+
private async waitForWake(): Promise<void> {
|
|
191
|
+
await this.wakeSignal.promise;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** Sleeps for the given duration or until the wake signal is resolved. */
|
|
195
|
+
private async sleepOrWake(ms: number): Promise<void> {
|
|
196
|
+
await Promise.race([sleep(ms), this.wakeSignal.promise]);
|
|
197
|
+
}
|
|
198
|
+
}
|