@aztec/p2p 0.87.5 → 0.87.7
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/interface.d.ts +8 -4
- package/dest/client/interface.d.ts.map +1 -1
- package/dest/client/p2p_client.d.ts +4 -3
- package/dest/client/p2p_client.d.ts.map +1 -1
- package/dest/client/p2p_client.js +17 -10
- package/dest/config.d.ts +10 -0
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +12 -2
- package/dest/index.d.ts +1 -0
- package/dest/index.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts +5 -6
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.js +37 -12
- package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts +2 -2
- package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +1 -1
- package/dest/mem_pools/tx_pool/memory_tx_pool.js +1 -3
- package/dest/mem_pools/tx_pool/tx_pool.d.ts +6 -1
- package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -1
- package/dest/msg_validators/msg_seen_validator/msg_seen_validator.d.ts +10 -0
- package/dest/msg_validators/msg_seen_validator/msg_seen_validator.d.ts.map +1 -0
- package/dest/msg_validators/msg_seen_validator/msg_seen_validator.js +36 -0
- package/dest/services/dummy_service.d.ts +1 -1
- package/dest/services/dummy_service.d.ts.map +1 -1
- package/dest/services/dummy_service.js +1 -1
- package/dest/services/index.d.ts +1 -0
- package/dest/services/index.d.ts.map +1 -1
- package/dest/services/index.js +1 -0
- package/dest/services/libp2p/instrumentation.d.ts +11 -0
- package/dest/services/libp2p/instrumentation.d.ts.map +1 -0
- package/dest/services/libp2p/instrumentation.js +29 -0
- package/dest/services/libp2p/libp2p_service.d.ts +8 -5
- package/dest/services/libp2p/libp2p_service.d.ts.map +1 -1
- package/dest/services/libp2p/libp2p_service.js +59 -12
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts +1 -1
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.d.ts.map +1 -1
- package/dest/services/reqresp/connection-sampler/batch_connection_sampler.js +7 -3
- package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts +2 -1
- package/dest/services/reqresp/connection-sampler/connection_sampler.d.ts.map +1 -1
- package/dest/services/reqresp/connection-sampler/connection_sampler.js +8 -3
- package/dest/services/reqresp/protocols/goodbye.d.ts.map +1 -1
- package/dest/services/reqresp/protocols/goodbye.js +3 -1
- package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts +4 -2
- package/dest/services/reqresp/rate-limiter/rate_limiter.d.ts.map +1 -1
- package/dest/services/reqresp/rate-limiter/rate_limiter.js +10 -2
- package/dest/services/reqresp/rate-limiter/rate_limits.js +1 -1
- package/dest/services/reqresp/reqresp.d.ts +3 -3
- package/dest/services/reqresp/reqresp.d.ts.map +1 -1
- package/dest/services/reqresp/reqresp.js +39 -13
- package/dest/services/service.d.ts +3 -2
- package/dest/services/service.d.ts.map +1 -1
- package/dest/services/tx_collector.d.ts +14 -0
- package/dest/services/tx_collector.d.ts.map +1 -0
- package/dest/services/tx_collector.js +76 -0
- package/dest/test-helpers/reqresp-nodes.d.ts +3 -3
- package/dest/test-helpers/reqresp-nodes.d.ts.map +1 -1
- package/dest/test-helpers/reqresp-nodes.js +4 -4
- package/dest/testbench/p2p_client_testbench_worker.js +1 -1
- package/package.json +12 -12
- package/src/client/interface.ts +8 -4
- package/src/client/p2p_client.ts +22 -10
- package/src/config.ts +22 -1
- package/src/index.ts +2 -0
- package/src/mem_pools/tx_pool/aztec_kv_tx_pool.ts +45 -18
- package/src/mem_pools/tx_pool/memory_tx_pool.ts +2 -4
- package/src/mem_pools/tx_pool/tx_pool.ts +7 -1
- package/src/msg_validators/msg_seen_validator/msg_seen_validator.ts +36 -0
- package/src/services/dummy_service.ts +3 -1
- package/src/services/index.ts +1 -0
- package/src/services/libp2p/instrumentation.ts +39 -0
- package/src/services/libp2p/libp2p_service.ts +79 -11
- package/src/services/reqresp/connection-sampler/batch_connection_sampler.ts +4 -2
- package/src/services/reqresp/connection-sampler/connection_sampler.ts +8 -3
- package/src/services/reqresp/protocols/goodbye.ts +3 -1
- package/src/services/reqresp/rate-limiter/rate_limiter.ts +9 -3
- package/src/services/reqresp/rate-limiter/rate_limits.ts +1 -1
- package/src/services/reqresp/reqresp.ts +44 -16
- package/src/services/service.ts +4 -1
- package/src/services/tx_collector.ts +103 -0
- package/src/test-helpers/reqresp-nodes.ts +13 -8
- package/src/testbench/p2p_client_testbench_worker.ts +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// @attribution: lodestar impl for inspiration
|
|
2
|
+
import { compactArray } from '@aztec/foundation/collection';
|
|
2
3
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
3
4
|
import { executeTimeout } from '@aztec/foundation/timer';
|
|
4
5
|
import { PeerErrorSeverity } from '@aztec/stdlib/p2p';
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
type ReqRespResponse,
|
|
26
27
|
ReqRespSubProtocol,
|
|
27
28
|
type ReqRespSubProtocolHandlers,
|
|
29
|
+
type ReqRespSubProtocolRateLimits,
|
|
28
30
|
type ReqRespSubProtocolValidators,
|
|
29
31
|
type SubProtocolMap,
|
|
30
32
|
subProtocolMap,
|
|
@@ -72,6 +74,7 @@ export class ReqResp {
|
|
|
72
74
|
config: P2PReqRespConfig,
|
|
73
75
|
private libp2p: Libp2p,
|
|
74
76
|
private peerScoring: PeerScoring,
|
|
77
|
+
rateLimits: Partial<ReqRespSubProtocolRateLimits> = {},
|
|
75
78
|
telemetryClient: TelemetryClient = getTelemetryClient(),
|
|
76
79
|
) {
|
|
77
80
|
this.logger = createLogger('p2p:reqresp');
|
|
@@ -79,7 +82,7 @@ export class ReqResp {
|
|
|
79
82
|
this.overallRequestTimeoutMs = config.overallRequestTimeoutMs;
|
|
80
83
|
this.individualRequestTimeoutMs = config.individualRequestTimeoutMs;
|
|
81
84
|
|
|
82
|
-
this.rateLimiter = new RequestResponseRateLimiter(peerScoring);
|
|
85
|
+
this.rateLimiter = new RequestResponseRateLimiter(peerScoring, rateLimits);
|
|
83
86
|
|
|
84
87
|
// Connection sampler is used to sample our connected peers
|
|
85
88
|
this.connectionSampler = new ConnectionSampler(libp2p);
|
|
@@ -261,6 +264,7 @@ export class ReqResp {
|
|
|
261
264
|
async sendBatchRequest<SubProtocol extends ReqRespSubProtocol>(
|
|
262
265
|
subProtocol: SubProtocol,
|
|
263
266
|
requests: InstanceType<SubProtocolMap[SubProtocol]['request']>[],
|
|
267
|
+
pinnedPeer: PeerId | undefined,
|
|
264
268
|
timeoutMs = 10000,
|
|
265
269
|
maxPeers = Math.max(10, Math.ceil(requests.length / 3)),
|
|
266
270
|
maxRetryAttempts = 3,
|
|
@@ -274,10 +278,15 @@ export class ReqResp {
|
|
|
274
278
|
const pendingRequestIndices = new Set(requestBuffers.map((_, i) => i));
|
|
275
279
|
|
|
276
280
|
// Create batch sampler with the total number of requests and max peers
|
|
277
|
-
const batchSampler = new BatchConnectionSampler(
|
|
281
|
+
const batchSampler = new BatchConnectionSampler(
|
|
282
|
+
this.connectionSampler,
|
|
283
|
+
requests.length,
|
|
284
|
+
maxPeers,
|
|
285
|
+
compactArray([pinnedPeer]), // Exclude pinned peer from sampling, we will forcefully send all requests to it
|
|
286
|
+
);
|
|
278
287
|
|
|
279
|
-
if (batchSampler.activePeerCount === 0) {
|
|
280
|
-
this.logger.
|
|
288
|
+
if (batchSampler.activePeerCount === 0 && !pinnedPeer) {
|
|
289
|
+
this.logger.warn('No active peers to send requests to');
|
|
281
290
|
return [];
|
|
282
291
|
}
|
|
283
292
|
|
|
@@ -308,6 +317,16 @@ export class ReqResp {
|
|
|
308
317
|
requestBatches.get(peerAsString)!.indices.push(requestIndex);
|
|
309
318
|
}
|
|
310
319
|
|
|
320
|
+
// If there is a pinned peer, we will always send every request to that peer
|
|
321
|
+
// We use the default limits for the subprotocol to avoid hitting the rate limiter
|
|
322
|
+
if (pinnedPeer) {
|
|
323
|
+
const limit = this.rateLimiter.getRateLimits(subProtocol).peerLimit.quotaCount;
|
|
324
|
+
requestBatches.set(pinnedPeer.toString(), {
|
|
325
|
+
peerId: pinnedPeer,
|
|
326
|
+
indices: Array.from(pendingRequestIndices.values()).slice(0, limit),
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
|
|
311
330
|
// Make parallel requests for each peer's batch
|
|
312
331
|
// A batch entry will look something like this:
|
|
313
332
|
// PeerId0: [0, 1, 2, 3]
|
|
@@ -323,6 +342,7 @@ export class ReqResp {
|
|
|
323
342
|
const peerResults: { index: number; response: InstanceType<SubProtocolMap[SubProtocol]['response']> }[] =
|
|
324
343
|
[];
|
|
325
344
|
for (const index of indices) {
|
|
345
|
+
this.logger.trace(`Sending request ${index} to peer ${peerAsString}`);
|
|
326
346
|
const response = await this.sendRequestToPeer(peer, subProtocol, requestBuffers[index]);
|
|
327
347
|
|
|
328
348
|
// Check the status of the response buffer
|
|
@@ -621,8 +641,9 @@ export class ReqResp {
|
|
|
621
641
|
const response = await handler(connection.remotePeer, msg);
|
|
622
642
|
|
|
623
643
|
if (protocol === ReqRespSubProtocol.GOODBYE) {
|
|
644
|
+
// NOTE: The stream was already closed by Goodbye handler
|
|
645
|
+
// peerManager.goodbyeReceived(peerId, reason); will call libp2p.hangUp closing all active streams and connections
|
|
624
646
|
// Don't respond
|
|
625
|
-
await stream.close();
|
|
626
647
|
return;
|
|
627
648
|
}
|
|
628
649
|
|
|
@@ -645,18 +666,25 @@ export class ReqResp {
|
|
|
645
666
|
errorStatus = e.status;
|
|
646
667
|
}
|
|
647
668
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
669
|
+
if (stream.status === 'open') {
|
|
670
|
+
const sendErrorChunk = this.sendErrorChunk(errorStatus);
|
|
671
|
+
// Return and yield the response chunk
|
|
672
|
+
await pipe(
|
|
673
|
+
stream,
|
|
674
|
+
async function* (_source: any) {
|
|
675
|
+
yield* sendErrorChunk;
|
|
676
|
+
},
|
|
677
|
+
stream,
|
|
678
|
+
);
|
|
679
|
+
} else {
|
|
680
|
+
this.logger.debug('Stream already closed, not sending error response', { protocol, err: e, errorStatus });
|
|
681
|
+
}
|
|
658
682
|
} finally {
|
|
659
|
-
|
|
683
|
+
//NOTE: All other status codes indicate closed stream.
|
|
684
|
+
//Either graceful close (closed/closing) or forced close (aborted/reset)
|
|
685
|
+
if (stream.status === 'open') {
|
|
686
|
+
await stream.close();
|
|
687
|
+
}
|
|
660
688
|
}
|
|
661
689
|
}
|
|
662
690
|
|
package/src/services/service.ts
CHANGED
|
@@ -13,6 +13,8 @@ export enum PeerDiscoveryState {
|
|
|
13
13
|
STOPPED = 'stopped',
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
export type P2PBlockReceivedCallback = (block: BlockProposal, sender: PeerId) => Promise<BlockAttestation | undefined>;
|
|
17
|
+
|
|
16
18
|
/**
|
|
17
19
|
* The interface for a P2P service implementation.
|
|
18
20
|
*/
|
|
@@ -57,13 +59,14 @@ export interface P2PService {
|
|
|
57
59
|
sendBatchRequest<Protocol extends ReqRespSubProtocol>(
|
|
58
60
|
protocol: Protocol,
|
|
59
61
|
requests: InstanceType<SubProtocolMap[Protocol]['request']>[],
|
|
62
|
+
pinnedPeerId?: PeerId,
|
|
60
63
|
timeoutMs?: number,
|
|
61
64
|
maxPeers?: number,
|
|
62
65
|
maxRetryAttempts?: number,
|
|
63
66
|
): Promise<(InstanceType<SubProtocolMap[Protocol]['response']> | undefined)[]>;
|
|
64
67
|
|
|
65
68
|
// Leaky abstraction: fix https://github.com/AztecProtocol/aztec-packages/issues/7963
|
|
66
|
-
registerBlockReceivedCallback(callback:
|
|
69
|
+
registerBlockReceivedCallback(callback: P2PBlockReceivedCallback): void;
|
|
67
70
|
|
|
68
71
|
getEnr(): ENR | undefined;
|
|
69
72
|
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { compactArray } from '@aztec/foundation/collection';
|
|
2
|
+
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
3
|
+
import type { BlockProposal } from '@aztec/stdlib/p2p';
|
|
4
|
+
import type { Tx, TxHash } from '@aztec/stdlib/tx';
|
|
5
|
+
|
|
6
|
+
import type { P2PClient } from '../client/p2p_client.js';
|
|
7
|
+
|
|
8
|
+
export class TxCollector {
|
|
9
|
+
constructor(
|
|
10
|
+
private p2pClient: Pick<
|
|
11
|
+
P2PClient,
|
|
12
|
+
'getTxsByHashFromPool' | 'hasTxsInPool' | 'getTxsByHash' | 'validate' | 'requestTxsByHash'
|
|
13
|
+
>,
|
|
14
|
+
private log: Logger = createLogger('p2p:tx-collector'),
|
|
15
|
+
) {}
|
|
16
|
+
|
|
17
|
+
async collectForBlockProposal(
|
|
18
|
+
proposal: BlockProposal,
|
|
19
|
+
peerWhoSentTheProposal: any,
|
|
20
|
+
): Promise<{ txs: Tx[]; missing?: TxHash[] }> {
|
|
21
|
+
if (proposal.payload.txHashes.length === 0) {
|
|
22
|
+
this.log.verbose(`Received block proposal with no transactions, skipping transaction availability check`);
|
|
23
|
+
return { txs: [] };
|
|
24
|
+
}
|
|
25
|
+
// Is this a new style proposal?
|
|
26
|
+
if (proposal.txs && proposal.txs.length > 0 && proposal.txs.length === proposal.payload.txHashes.length) {
|
|
27
|
+
// Yes, any txs that we already have we should use
|
|
28
|
+
this.log.info(`Using new style proposal with ${proposal.txs.length} transactions`);
|
|
29
|
+
|
|
30
|
+
// Request from the pool based on the signed hashes in the payload
|
|
31
|
+
const hashesFromPayload = proposal.payload.txHashes;
|
|
32
|
+
const txsToUse = await this.p2pClient.getTxsByHashFromPool(hashesFromPayload);
|
|
33
|
+
|
|
34
|
+
const missingTxs = txsToUse.filter(tx => tx === undefined).length;
|
|
35
|
+
if (missingTxs > 0) {
|
|
36
|
+
this.log.verbose(
|
|
37
|
+
`Missing ${missingTxs}/${hashesFromPayload.length} transactions in the tx pool, will attempt to take from the proposal`,
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let usedFromProposal = 0;
|
|
42
|
+
|
|
43
|
+
// Fill any holes with txs in the proposal, provided their hash matches the hash in the payload
|
|
44
|
+
for (let i = 0; i < txsToUse.length; i++) {
|
|
45
|
+
if (txsToUse[i] === undefined) {
|
|
46
|
+
// We don't have the transaction, take from the proposal, provided the hash is the same
|
|
47
|
+
const hashOfTxInProposal = await proposal.txs[i].getTxHash();
|
|
48
|
+
if (hashOfTxInProposal.equals(hashesFromPayload[i])) {
|
|
49
|
+
// Hash is equal, we can use the tx from the proposal
|
|
50
|
+
txsToUse[i] = proposal.txs[i];
|
|
51
|
+
usedFromProposal++;
|
|
52
|
+
} else {
|
|
53
|
+
this.log.warn(
|
|
54
|
+
`Unable to take tx: ${hashOfTxInProposal.toString()} from the proposal, it does not match payload hash: ${hashesFromPayload[
|
|
55
|
+
i
|
|
56
|
+
].toString()}`,
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// See if we still have any holes, if there are then we were not successful and will try the old method
|
|
63
|
+
if (txsToUse.some(tx => tx === undefined)) {
|
|
64
|
+
this.log.warn(`Failed to use transactions from proposal. Falling back to old proposal logic`);
|
|
65
|
+
} else {
|
|
66
|
+
this.log.info(
|
|
67
|
+
`Successfully used ${usedFromProposal}/${hashesFromPayload.length} transactions from the proposal`,
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
await this.p2pClient.validate(txsToUse as Tx[]);
|
|
71
|
+
return { txs: txsToUse as Tx[] };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.log.info(`Using old style proposal with ${proposal.payload.txHashes.length} transactions`);
|
|
76
|
+
|
|
77
|
+
// Old style proposal, we will perform a request by hash from pool
|
|
78
|
+
// This will request from network any txs that are missing
|
|
79
|
+
const txHashes: TxHash[] = proposal.payload.txHashes;
|
|
80
|
+
|
|
81
|
+
// This part is just for logging that we are requesting from the network
|
|
82
|
+
const availability = await this.p2pClient.hasTxsInPool(txHashes);
|
|
83
|
+
const notAvailable = availability.filter(availability => availability === false);
|
|
84
|
+
if (notAvailable.length) {
|
|
85
|
+
this.log.verbose(
|
|
86
|
+
`Missing ${notAvailable.length} transactions in the tx pool, will need to request from the network`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// This will request from the network any txs that are missing
|
|
91
|
+
// NOTE: this could still return missing txs so we need to (1) be careful to handle undefined and (2) keep the txs in the correct order for re-execution
|
|
92
|
+
const maybeRetrievedTxs = await this.p2pClient.getTxsByHash(txHashes, peerWhoSentTheProposal);
|
|
93
|
+
const missingTxs = compactArray(
|
|
94
|
+
maybeRetrievedTxs.map((tx, index) => (tx === undefined ? txHashes[index] : undefined)),
|
|
95
|
+
);
|
|
96
|
+
// if we found all txs, this is a noop. If we didn't find all txs then validate the ones we did find and tell the validator to skip attestations because missingTxs.length > 0
|
|
97
|
+
const retrievedTxs = compactArray(maybeRetrievedTxs);
|
|
98
|
+
|
|
99
|
+
await this.p2pClient.validate(retrievedTxs);
|
|
100
|
+
|
|
101
|
+
return { txs: retrievedTxs, missing: missingTxs };
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -33,6 +33,7 @@ import type { P2PReqRespConfig } from '../services/reqresp/config.js';
|
|
|
33
33
|
import {
|
|
34
34
|
ReqRespSubProtocol,
|
|
35
35
|
type ReqRespSubProtocolHandlers,
|
|
36
|
+
type ReqRespSubProtocolRateLimits,
|
|
36
37
|
type ReqRespSubProtocolValidators,
|
|
37
38
|
noopValidator,
|
|
38
39
|
} from '../services/reqresp/interface.js';
|
|
@@ -172,8 +173,12 @@ export const MOCK_SUB_PROTOCOL_VALIDATORS: ReqRespSubProtocolValidators = {
|
|
|
172
173
|
* @param numberOfNodes - the number of nodes to create
|
|
173
174
|
* @returns An array of the created nodes
|
|
174
175
|
*/
|
|
175
|
-
export const createNodes = (
|
|
176
|
-
|
|
176
|
+
export const createNodes = (
|
|
177
|
+
peerScoring: PeerScoring,
|
|
178
|
+
numberOfNodes: number,
|
|
179
|
+
rateLimits: Partial<ReqRespSubProtocolRateLimits> = {},
|
|
180
|
+
): Promise<ReqRespNode[]> => {
|
|
181
|
+
return timesParallel(numberOfNodes, () => createReqResp(peerScoring, rateLimits));
|
|
177
182
|
};
|
|
178
183
|
|
|
179
184
|
export const startNodes = async (
|
|
@@ -192,17 +197,17 @@ export const stopNodes = async (nodes: ReqRespNode[]): Promise<void> => {
|
|
|
192
197
|
};
|
|
193
198
|
|
|
194
199
|
// Create a req resp node, exposing the underlying p2p node
|
|
195
|
-
export const createReqResp = async (
|
|
200
|
+
export const createReqResp = async (
|
|
201
|
+
peerScoring: PeerScoring,
|
|
202
|
+
rateLimits: Partial<ReqRespSubProtocolRateLimits> = {},
|
|
203
|
+
): Promise<ReqRespNode> => {
|
|
196
204
|
const p2p = await createLibp2pNode();
|
|
197
205
|
const config: P2PReqRespConfig = {
|
|
198
206
|
overallRequestTimeoutMs: 4000,
|
|
199
207
|
individualRequestTimeoutMs: 2000,
|
|
200
208
|
};
|
|
201
|
-
const req = new ReqResp(config, p2p, peerScoring);
|
|
202
|
-
return {
|
|
203
|
-
p2p,
|
|
204
|
-
req,
|
|
205
|
-
};
|
|
209
|
+
const req = new ReqResp(config, p2p, peerScoring, rateLimits);
|
|
210
|
+
return { p2p, req };
|
|
206
211
|
};
|
|
207
212
|
|
|
208
213
|
// Given a node list; hand shake all of the nodes with each other
|
|
@@ -49,7 +49,7 @@ function mockTxPool(): TxPool {
|
|
|
49
49
|
getTxStatus: () => Promise.resolve(TxStatus.PENDING),
|
|
50
50
|
getTxsByHash: () => Promise.resolve([]),
|
|
51
51
|
hasTxs: () => Promise.resolve([]),
|
|
52
|
-
|
|
52
|
+
updateConfig: () => {},
|
|
53
53
|
markTxsAsNonEvictable: () => Promise.resolve(),
|
|
54
54
|
};
|
|
55
55
|
}
|