@aztec/p2p 0.56.0 → 0.58.0

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.
Files changed (109) hide show
  1. package/dest/client/index.d.ts +6 -3
  2. package/dest/client/index.d.ts.map +1 -1
  3. package/dest/client/index.js +12 -6
  4. package/dest/client/p2p_client.d.ts +30 -12
  5. package/dest/client/p2p_client.d.ts.map +1 -1
  6. package/dest/client/p2p_client.js +375 -335
  7. package/dest/config.js +3 -3
  8. package/dest/index.d.ts +4 -3
  9. package/dest/index.d.ts.map +1 -1
  10. package/dest/index.js +5 -4
  11. package/dest/{attestation_pool → mem_pools/attestation_pool}/attestation_pool.d.ts +2 -1
  12. package/dest/mem_pools/attestation_pool/attestation_pool.d.ts.map +1 -0
  13. package/dest/{attestation_pool → mem_pools/attestation_pool}/attestation_pool.js +1 -1
  14. package/dest/mem_pools/attestation_pool/index.d.ts.map +1 -0
  15. package/dest/{attestation_pool → mem_pools/attestation_pool}/index.js +1 -1
  16. package/dest/{attestation_pool → mem_pools/attestation_pool}/memory_attestation_pool.d.ts +6 -2
  17. package/dest/mem_pools/attestation_pool/memory_attestation_pool.d.ts.map +1 -0
  18. package/dest/mem_pools/attestation_pool/memory_attestation_pool.js +114 -0
  19. package/dest/{attestation_pool → mem_pools/attestation_pool}/mocks.d.ts +2 -1
  20. package/dest/mem_pools/attestation_pool/mocks.d.ts.map +1 -0
  21. package/dest/mem_pools/attestation_pool/mocks.js +31 -0
  22. package/dest/mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.d.ts +7 -0
  23. package/dest/mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.d.ts.map +1 -0
  24. package/dest/mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.js +2 -0
  25. package/dest/mem_pools/epoch_proof_quote_pool/index.d.ts +4 -0
  26. package/dest/mem_pools/epoch_proof_quote_pool/index.d.ts.map +1 -0
  27. package/dest/mem_pools/epoch_proof_quote_pool/index.js +4 -0
  28. package/dest/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.d.ts +12 -0
  29. package/dest/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.d.ts.map +1 -0
  30. package/dest/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.js +30 -0
  31. package/dest/mem_pools/epoch_proof_quote_pool/test_utils.d.ts +8 -0
  32. package/dest/mem_pools/epoch_proof_quote_pool/test_utils.d.ts.map +1 -0
  33. package/dest/mem_pools/epoch_proof_quote_pool/test_utils.js +21 -0
  34. package/dest/mem_pools/index.d.ts +5 -0
  35. package/dest/mem_pools/index.d.ts.map +1 -0
  36. package/dest/mem_pools/index.js +2 -0
  37. package/dest/mem_pools/instrumentation.d.ts +25 -0
  38. package/dest/mem_pools/instrumentation.d.ts.map +1 -0
  39. package/dest/mem_pools/instrumentation.js +70 -0
  40. package/dest/mem_pools/interface.d.ts +12 -0
  41. package/dest/mem_pools/interface.d.ts.map +1 -0
  42. package/dest/mem_pools/interface.js +2 -0
  43. package/dest/mem_pools/tx_pool/aztec_kv_tx_pool.d.ts.map +1 -0
  44. package/dest/{tx_pool → mem_pools/tx_pool}/aztec_kv_tx_pool.js +9 -9
  45. package/dest/mem_pools/tx_pool/index.d.ts.map +1 -0
  46. package/dest/{tx_pool → mem_pools/tx_pool}/index.js +1 -1
  47. package/dest/mem_pools/tx_pool/memory_tx_pool.d.ts.map +1 -0
  48. package/dest/mem_pools/tx_pool/memory_tx_pool.js +111 -0
  49. package/dest/mem_pools/tx_pool/tx_pool.d.ts.map +1 -0
  50. package/dest/{tx_pool → mem_pools/tx_pool}/tx_pool.js +1 -1
  51. package/dest/mem_pools/tx_pool/tx_pool_test_suite.d.ts.map +1 -0
  52. package/dest/mem_pools/tx_pool/tx_pool_test_suite.js +55 -0
  53. package/dest/service/libp2p_service.d.ts +12 -7
  54. package/dest/service/libp2p_service.d.ts.map +1 -1
  55. package/dest/service/libp2p_service.js +453 -381
  56. package/dest/service/reqresp/reqresp.d.ts +0 -1
  57. package/dest/service/reqresp/reqresp.d.ts.map +1 -1
  58. package/dest/service/reqresp/reqresp.js +7 -4
  59. package/package.json +10 -6
  60. package/src/client/index.ts +21 -8
  61. package/src/client/p2p_client.ts +77 -21
  62. package/src/config.ts +2 -2
  63. package/src/index.ts +4 -3
  64. package/src/{attestation_pool → mem_pools/attestation_pool}/attestation_pool.ts +2 -1
  65. package/src/mem_pools/attestation_pool/memory_attestation_pool.ts +141 -0
  66. package/src/{attestation_pool → mem_pools/attestation_pool}/mocks.ts +5 -2
  67. package/src/mem_pools/epoch_proof_quote_pool/epoch_proof_quote_pool.ts +7 -0
  68. package/src/mem_pools/epoch_proof_quote_pool/index.ts +3 -0
  69. package/src/mem_pools/epoch_proof_quote_pool/memory_epoch_proof_quote_pool.ts +42 -0
  70. package/src/mem_pools/epoch_proof_quote_pool/test_utils.ts +26 -0
  71. package/src/mem_pools/index.ts +4 -0
  72. package/src/mem_pools/instrumentation.ts +85 -0
  73. package/src/mem_pools/interface.ts +12 -0
  74. package/src/{tx_pool → mem_pools/tx_pool}/aztec_kv_tx_pool.ts +9 -9
  75. package/src/{tx_pool → mem_pools/tx_pool}/memory_tx_pool.ts +9 -9
  76. package/src/service/libp2p_service.ts +78 -20
  77. package/src/service/reqresp/reqresp.ts +9 -5
  78. package/dest/attestation_pool/attestation_pool.d.ts.map +0 -1
  79. package/dest/attestation_pool/index.d.ts.map +0 -1
  80. package/dest/attestation_pool/memory_attestation_pool.d.ts.map +0 -1
  81. package/dest/attestation_pool/memory_attestation_pool.js +0 -57
  82. package/dest/attestation_pool/mocks.d.ts.map +0 -1
  83. package/dest/attestation_pool/mocks.js +0 -32
  84. package/dest/client/mocks.d.ts +0 -65
  85. package/dest/client/mocks.d.ts.map +0 -1
  86. package/dest/client/mocks.js +0 -106
  87. package/dest/tx_pool/aztec_kv_tx_pool.d.ts.map +0 -1
  88. package/dest/tx_pool/index.d.ts.map +0 -1
  89. package/dest/tx_pool/instrumentation.d.ts +0 -25
  90. package/dest/tx_pool/instrumentation.d.ts.map +0 -1
  91. package/dest/tx_pool/instrumentation.js +0 -61
  92. package/dest/tx_pool/memory_tx_pool.d.ts.map +0 -1
  93. package/dest/tx_pool/memory_tx_pool.js +0 -111
  94. package/dest/tx_pool/tx_pool.d.ts.map +0 -1
  95. package/dest/tx_pool/tx_pool_test_suite.d.ts.map +0 -1
  96. package/dest/tx_pool/tx_pool_test_suite.js +0 -55
  97. package/src/attestation_pool/memory_attestation_pool.ts +0 -71
  98. package/src/client/mocks.ts +0 -129
  99. package/src/tx_pool/instrumentation.ts +0 -73
  100. /package/dest/{attestation_pool → mem_pools/attestation_pool}/index.d.ts +0 -0
  101. /package/dest/{tx_pool → mem_pools/tx_pool}/aztec_kv_tx_pool.d.ts +0 -0
  102. /package/dest/{tx_pool → mem_pools/tx_pool}/index.d.ts +0 -0
  103. /package/dest/{tx_pool → mem_pools/tx_pool}/memory_tx_pool.d.ts +0 -0
  104. /package/dest/{tx_pool → mem_pools/tx_pool}/tx_pool.d.ts +0 -0
  105. /package/dest/{tx_pool → mem_pools/tx_pool}/tx_pool_test_suite.d.ts +0 -0
  106. /package/src/{attestation_pool → mem_pools/attestation_pool}/index.ts +0 -0
  107. /package/src/{tx_pool → mem_pools/tx_pool}/index.ts +0 -0
  108. /package/src/{tx_pool → mem_pools/tx_pool}/tx_pool.ts +0 -0
  109. /package/src/{tx_pool → mem_pools/tx_pool}/tx_pool_test_suite.ts +0 -0
@@ -0,0 +1,26 @@
1
+ import { EpochProofQuote, EpochProofQuotePayload } from '@aztec/circuit-types';
2
+ import { EthAddress } from '@aztec/circuits.js';
3
+ import { Buffer32 } from '@aztec/foundation/buffer';
4
+ import { Secp256k1Signer, randomBigInt, randomInt } from '@aztec/foundation/crypto';
5
+
6
+ export function makeRandomEpochProofQuotePayload(): EpochProofQuotePayload {
7
+ return EpochProofQuotePayload.from({
8
+ basisPointFee: randomInt(10000),
9
+ bondAmount: 1000000000000000000n,
10
+ epochToProve: randomBigInt(1000000n),
11
+ prover: EthAddress.random(),
12
+ validUntilSlot: randomBigInt(1000000n),
13
+ });
14
+ }
15
+
16
+ export function makeRandomEpochProofQuote(payload?: EpochProofQuotePayload): {
17
+ quote: EpochProofQuote;
18
+ signer: Secp256k1Signer;
19
+ } {
20
+ const signer = Secp256k1Signer.random();
21
+
22
+ return {
23
+ quote: EpochProofQuote.new(Buffer32.random(), payload ?? makeRandomEpochProofQuotePayload(), signer),
24
+ signer,
25
+ };
26
+ }
@@ -0,0 +1,4 @@
1
+ export { TxPool } from './tx_pool/tx_pool.js';
2
+ export { AttestationPool } from './attestation_pool/attestation_pool.js';
3
+ export { EpochProofQuotePool } from './epoch_proof_quote_pool/epoch_proof_quote_pool.js';
4
+ export { type MemPools } from './interface.js';
@@ -0,0 +1,85 @@
1
+ import { type Gossipable } from '@aztec/circuit-types';
2
+ import { Attributes, type Histogram, Metrics, type TelemetryClient, type UpDownCounter } from '@aztec/telemetry-client';
3
+
4
+ /**
5
+ * Instrumentation class for the Pools (TxPool, AttestationPool, etc).
6
+ */
7
+ export class PoolInstrumentation<PoolObject extends Gossipable> {
8
+ /** The number of txs in the mempool */
9
+ private objectsInMempool: UpDownCounter;
10
+ /** Tracks tx size */
11
+ private objectSize: Histogram;
12
+
13
+ private defaultAttributes;
14
+
15
+ constructor(telemetry: TelemetryClient, name: string) {
16
+ const meter = telemetry.getMeter(name);
17
+ this.defaultAttributes = { [Attributes.POOL_NAME]: name };
18
+
19
+ this.objectsInMempool = meter.createUpDownCounter(Metrics.MEMPOOL_TX_COUNT, {
20
+ description: 'The current number of transactions in the mempool',
21
+ });
22
+
23
+ this.objectSize = meter.createHistogram(Metrics.MEMPOOL_TX_SIZE, {
24
+ unit: 'By',
25
+ description: 'The size of transactions in the mempool',
26
+ advice: {
27
+ explicitBucketBoundaries: [
28
+ 5_000, // 5KB
29
+ 10_000,
30
+ 20_000,
31
+ 50_000,
32
+ 75_000,
33
+ 100_000, // 100KB
34
+ 200_000,
35
+ ],
36
+ },
37
+ });
38
+ }
39
+
40
+ public recordSize(poolObject: PoolObject) {
41
+ this.objectSize.record(poolObject.getSize());
42
+ }
43
+
44
+ /**
45
+ * Updates the metrics with the new objects.
46
+ * @param txs - The objects to record
47
+ */
48
+ public recordAddedObjects(count = 1, status?: string) {
49
+ if (count < 0) {
50
+ throw new Error('Count must be positive');
51
+ }
52
+ if (count === 0) {
53
+ return;
54
+ }
55
+ const attributes = status
56
+ ? {
57
+ ...this.defaultAttributes,
58
+ [Attributes.STATUS]: status,
59
+ }
60
+ : this.defaultAttributes;
61
+
62
+ this.objectsInMempool.add(count, attributes);
63
+ }
64
+
65
+ /**
66
+ * Updates the metrics by removing objects from the mempool.
67
+ * @param count - The number of objects to remove from the mempool
68
+ */
69
+ public recordRemovedObjects(count = 1, status?: string) {
70
+ if (count < 0) {
71
+ throw new Error('Count must be positive');
72
+ }
73
+ if (count === 0) {
74
+ return;
75
+ }
76
+
77
+ const attributes = status
78
+ ? {
79
+ ...this.defaultAttributes,
80
+ [Attributes.STATUS]: status,
81
+ }
82
+ : this.defaultAttributes;
83
+ this.objectsInMempool.add(-1 * count, attributes);
84
+ }
85
+ }
@@ -0,0 +1,12 @@
1
+ import { type AttestationPool } from './attestation_pool/attestation_pool.js';
2
+ import { type EpochProofQuotePool } from './epoch_proof_quote_pool/epoch_proof_quote_pool.js';
3
+ import { type TxPool } from './tx_pool/tx_pool.js';
4
+
5
+ /**
6
+ * A interface the combines all mempools
7
+ */
8
+ export interface MemPools {
9
+ txPool: TxPool;
10
+ attestationPool: AttestationPool;
11
+ epochProofQuotePool: EpochProofQuotePool;
12
+ }
@@ -4,7 +4,7 @@ import { type Logger, createDebugLogger } from '@aztec/foundation/log';
4
4
  import { type AztecKVStore, type AztecMap, type AztecSet } from '@aztec/kv-store';
5
5
  import { type TelemetryClient } from '@aztec/telemetry-client';
6
6
 
7
- import { TxPoolInstrumentation } from './instrumentation.js';
7
+ import { PoolInstrumentation } from '../instrumentation.js';
8
8
  import { type TxPool } from './tx_pool.js';
9
9
 
10
10
  /**
@@ -23,7 +23,7 @@ export class AztecKVTxPool implements TxPool {
23
23
 
24
24
  #log: Logger;
25
25
 
26
- #metrics: TxPoolInstrumentation;
26
+ #metrics: PoolInstrumentation<Tx>;
27
27
 
28
28
  /**
29
29
  * Class constructor for in-memory TxPool. Initiates our transaction pool as a JS Map.
@@ -37,7 +37,7 @@ export class AztecKVTxPool implements TxPool {
37
37
 
38
38
  this.#store = store;
39
39
  this.#log = log;
40
- this.#metrics = new TxPoolInstrumentation(telemetry, 'AztecKVTxPool');
40
+ this.#metrics = new PoolInstrumentation(telemetry, 'AztecKVTxPool');
41
41
  }
42
42
 
43
43
  public markAsMined(txHashes: TxHash[]): Promise<void> {
@@ -51,8 +51,8 @@ export class AztecKVTxPool implements TxPool {
51
51
  void this.#pendingTxs.delete(key);
52
52
  }
53
53
  }
54
- this.#metrics.recordRemovedTxs('pending', deleted);
55
- this.#metrics.recordAddedTxs('mined', txHashes.length);
54
+ this.#metrics.recordRemovedObjects(deleted, 'pending');
55
+ this.#metrics.recordAddedObjects(txHashes.length, 'mined');
56
56
  });
57
57
  }
58
58
 
@@ -107,11 +107,11 @@ export class AztecKVTxPool implements TxPool {
107
107
  pendingCount++;
108
108
  // REFACTOR: Use an lmdb conditional write to avoid race conditions with this write tx
109
109
  void this.#pendingTxs.add(key);
110
- this.#metrics.recordTxSize(tx);
110
+ this.#metrics.recordSize(tx);
111
111
  }
112
112
  }
113
113
 
114
- this.#metrics.recordAddedTxs('pending', pendingCount);
114
+ this.#metrics.recordAddedObjects(pendingCount, 'pending');
115
115
  });
116
116
  }
117
117
 
@@ -138,8 +138,8 @@ export class AztecKVTxPool implements TxPool {
138
138
  }
139
139
  }
140
140
 
141
- this.#metrics.recordRemovedTxs('pending', pendingDeleted);
142
- this.#metrics.recordRemovedTxs('mined', minedDeleted);
141
+ this.#metrics.recordRemovedObjects(pendingDeleted, 'pending');
142
+ this.#metrics.recordRemovedObjects(minedDeleted, 'mined');
143
143
  });
144
144
  }
145
145
 
@@ -3,7 +3,7 @@ import { type TxAddedToPoolStats } from '@aztec/circuit-types/stats';
3
3
  import { createDebugLogger } from '@aztec/foundation/log';
4
4
  import { type TelemetryClient } from '@aztec/telemetry-client';
5
5
 
6
- import { TxPoolInstrumentation } from './instrumentation.js';
6
+ import { PoolInstrumentation } from '../instrumentation.js';
7
7
  import { type TxPool } from './tx_pool.js';
8
8
 
9
9
  /**
@@ -17,7 +17,7 @@ export class InMemoryTxPool implements TxPool {
17
17
  private minedTxs: Set<bigint>;
18
18
  private pendingTxs: Set<bigint>;
19
19
 
20
- private metrics: TxPoolInstrumentation;
20
+ private metrics: PoolInstrumentation<Tx>;
21
21
 
22
22
  /**
23
23
  * Class constructor for in-memory TxPool. Initiates our transaction pool as a JS Map.
@@ -27,7 +27,7 @@ export class InMemoryTxPool implements TxPool {
27
27
  this.txs = new Map<bigint, Tx>();
28
28
  this.minedTxs = new Set();
29
29
  this.pendingTxs = new Set();
30
- this.metrics = new TxPoolInstrumentation(telemetry, 'InMemoryTxPool');
30
+ this.metrics = new PoolInstrumentation(telemetry, 'InMemoryTxPool');
31
31
  }
32
32
 
33
33
  public markAsMined(txHashes: TxHash[]): Promise<void> {
@@ -36,8 +36,8 @@ export class InMemoryTxPool implements TxPool {
36
36
  this.minedTxs.add(key);
37
37
  this.pendingTxs.delete(key);
38
38
  }
39
- this.metrics.recordRemovedTxs('pending', txHashes.length);
40
- this.metrics.recordAddedTxs('mined', txHashes.length);
39
+ this.metrics.recordRemovedObjects(txHashes.length, 'pending');
40
+ this.metrics.recordAddedObjects(txHashes.length, 'mined');
41
41
  return Promise.resolve();
42
42
  }
43
43
 
@@ -88,12 +88,12 @@ export class InMemoryTxPool implements TxPool {
88
88
  this.txs.set(key, tx);
89
89
  if (!this.minedTxs.has(key)) {
90
90
  pending++;
91
- this.metrics.recordTxSize(tx);
91
+ this.metrics.recordSize(tx);
92
92
  this.pendingTxs.add(key);
93
93
  }
94
94
  }
95
95
 
96
- this.metrics.recordAddedTxs('pending', pending);
96
+ this.metrics.recordAddedObjects(pending, 'pending');
97
97
  return Promise.resolve();
98
98
  }
99
99
 
@@ -113,8 +113,8 @@ export class InMemoryTxPool implements TxPool {
113
113
  deletedMined += this.minedTxs.delete(key) ? 1 : 0;
114
114
  }
115
115
 
116
- this.metrics.recordRemovedTxs('pending', deletedPending);
117
- this.metrics.recordRemovedTxs('mined', deletedMined);
116
+ this.metrics.recordRemovedObjects(deletedPending, 'pending');
117
+ this.metrics.recordRemovedObjects(deletedMined, 'mined');
118
118
 
119
119
  return Promise.resolve();
120
120
  }
@@ -2,6 +2,7 @@ import {
2
2
  BlockAttestation,
3
3
  BlockProposal,
4
4
  type ClientProtocolCircuitVerifier,
5
+ EpochProofQuote,
5
6
  type Gossipable,
6
7
  type L2BlockSource,
7
8
  MerkleTreeId,
@@ -17,6 +18,7 @@ import { createDebugLogger } from '@aztec/foundation/log';
17
18
  import { SerialQueue } from '@aztec/foundation/queue';
18
19
  import { RunningPromise } from '@aztec/foundation/running-promise';
19
20
  import type { AztecKVStore } from '@aztec/kv-store';
21
+ import { Attributes, type TelemetryClient, WithTracer, trackSpan } from '@aztec/telemetry-client';
20
22
 
21
23
  import { type ENR } from '@chainsafe/enr';
22
24
  import { type GossipSub, type GossipSubComponents, gossipsub } from '@chainsafe/libp2p-gossipsub';
@@ -31,9 +33,8 @@ import { createFromJSON, createSecp256k1PeerId } from '@libp2p/peer-id-factory';
31
33
  import { tcp } from '@libp2p/tcp';
32
34
  import { createLibp2p } from 'libp2p';
33
35
 
34
- import { type AttestationPool } from '../attestation_pool/attestation_pool.js';
35
36
  import { type P2PConfig } from '../config.js';
36
- import { type TxPool } from '../tx_pool/index.js';
37
+ import { type MemPools } from '../mem_pools/interface.js';
37
38
  import {
38
39
  DataTxValidator,
39
40
  DoubleSpendTxValidator,
@@ -77,7 +78,7 @@ export async function createLibP2PPeerId(privateKey?: string): Promise<PeerId> {
77
78
  /**
78
79
  * Lib P2P implementation of the P2PService interface.
79
80
  */
80
- export class LibP2PService implements P2PService {
81
+ export class LibP2PService extends WithTracer implements P2PService {
81
82
  private jobQueue: SerialQueue = new SerialQueue();
82
83
  private peerManager: PeerManager;
83
84
  private discoveryRunningPromise?: RunningPromise;
@@ -96,14 +97,17 @@ export class LibP2PService implements P2PService {
96
97
  private config: P2PConfig,
97
98
  private node: PubSubLibp2p,
98
99
  private peerDiscoveryService: PeerDiscoveryService,
99
- private txPool: TxPool,
100
- private attestationPool: AttestationPool,
100
+ private mempools: MemPools,
101
101
  private l2BlockSource: L2BlockSource,
102
102
  private proofVerifier: ClientProtocolCircuitVerifier,
103
103
  private worldStateSynchronizer: WorldStateSynchronizer,
104
+ telemetry: TelemetryClient,
104
105
  private requestResponseHandlers: ReqRespSubProtocolHandlers = DEFAULT_SUB_PROTOCOL_HANDLERS,
105
106
  private logger = createDebugLogger('aztec:libp2p_service'),
106
107
  ) {
108
+ // Instatntiate tracer
109
+ super(telemetry, 'LibP2PService');
110
+
107
111
  this.peerManager = new PeerManager(node, peerDiscoveryService, config, logger);
108
112
  this.node.services.pubsub.score.params.appSpecificScore = (peerId: string) => {
109
113
  return this.peerManager.getPeerScore(peerId);
@@ -182,12 +186,12 @@ export class LibP2PService implements P2PService {
182
186
  await this.discoveryRunningPromise?.stop();
183
187
  this.logger.debug('Stopping peer discovery service...');
184
188
  await this.peerDiscoveryService.stop();
189
+ this.logger.debug('Request response service stopped...');
190
+ await this.reqresp.stop();
185
191
  this.logger.debug('Stopping LibP2P...');
186
192
  await this.stopLibP2P();
187
193
  this.logger.info('LibP2P service stopped');
188
194
  this.logger.debug('Stopping request response service...');
189
- await this.reqresp.stop();
190
- this.logger.debug('Request response service stopped...');
191
195
  }
192
196
 
193
197
  /**
@@ -200,12 +204,12 @@ export class LibP2PService implements P2PService {
200
204
  config: P2PConfig,
201
205
  peerDiscoveryService: PeerDiscoveryService,
202
206
  peerId: PeerId,
203
- txPool: TxPool,
204
- attestationPool: AttestationPool,
207
+ mempools: MemPools,
205
208
  l2BlockSource: L2BlockSource,
206
209
  proofVerifier: ClientProtocolCircuitVerifier,
207
210
  worldStateSynchronizer: WorldStateSynchronizer,
208
211
  store: AztecKVStore,
212
+ telemetry: TelemetryClient,
209
213
  ) {
210
214
  const { tcpListenAddress, tcpAnnounceAddress, minPeerCount, maxPeerCount } = config;
211
215
  const bindAddrTcp = convertToMultiaddr(tcpListenAddress, 'tcp');
@@ -260,6 +264,21 @@ export class LibP2PService implements P2PService {
260
264
  invalidMessageDeliveriesWeight: -20,
261
265
  invalidMessageDeliveriesDecay: 0.5,
262
266
  }),
267
+ [BlockAttestation.p2pTopic]: createTopicScoreParams({
268
+ topicWeight: 1,
269
+ invalidMessageDeliveriesWeight: -20,
270
+ invalidMessageDeliveriesDecay: 0.5,
271
+ }),
272
+ [BlockAttestation.p2pTopic]: createTopicScoreParams({
273
+ topicWeight: 1,
274
+ invalidMessageDeliveriesWeight: -20,
275
+ invalidMessageDeliveriesDecay: 0.5,
276
+ }),
277
+ [EpochProofQuote.p2pTopic]: createTopicScoreParams({
278
+ topicWeight: 1,
279
+ invalidMessageDeliveriesWeight: -20,
280
+ invalidMessageDeliveriesDecay: 0.5,
281
+ }),
263
282
  },
264
283
  }),
265
284
  }) as (components: GossipSubComponents) => GossipSub,
@@ -274,7 +293,7 @@ export class LibP2PService implements P2PService {
274
293
  */
275
294
  const txHandler = (msg: Buffer): Promise<Uint8Array> => {
276
295
  const txHash = TxHash.fromBuffer(msg);
277
- const foundTx = txPool.getTxByHash(txHash);
296
+ const foundTx = mempools.txPool.getTxByHash(txHash);
278
297
  const asUint8Array = Uint8Array.from(foundTx ? foundTx.toBuffer() : Buffer.alloc(0));
279
298
  return Promise.resolve(asUint8Array);
280
299
  };
@@ -289,11 +308,11 @@ export class LibP2PService implements P2PService {
289
308
  config,
290
309
  node,
291
310
  peerDiscoveryService,
292
- txPool,
293
- attestationPool,
311
+ mempools,
294
312
  l2BlockSource,
295
313
  proofVerifier,
296
314
  worldStateSynchronizer,
315
+ telemetry,
297
316
  requestResponseHandlers,
298
317
  );
299
318
  }
@@ -372,6 +391,10 @@ export class LibP2PService implements P2PService {
372
391
  const block = BlockProposal.fromBuffer(Buffer.from(message.data));
373
392
  await this.processBlockFromPeer(block);
374
393
  }
394
+ if (message.topic == EpochProofQuote.p2pTopic) {
395
+ const epochProofQuote = EpochProofQuote.fromBuffer(Buffer.from(message.data));
396
+ this.processEpochProofQuoteFromPeer(epochProofQuote);
397
+ }
375
398
 
376
399
  return;
377
400
  }
@@ -381,9 +404,15 @@ export class LibP2PService implements P2PService {
381
404
  *
382
405
  * @param attestation - The attestation to process.
383
406
  */
407
+ @trackSpan('Libp2pService.processAttestationFromPeer', attestation => ({
408
+ [Attributes.BLOCK_NUMBER]: attestation.payload.header.globalVariables.blockNumber.toNumber(),
409
+ [Attributes.SLOT_NUMBER]: attestation.payload.header.globalVariables.slotNumber.toNumber(),
410
+ [Attributes.BLOCK_ARCHIVE]: attestation.archive.toString(),
411
+ [Attributes.P2P_ID]: attestation.p2pMessageIdentifier().toString(),
412
+ }))
384
413
  private async processAttestationFromPeer(attestation: BlockAttestation): Promise<void> {
385
- this.logger.verbose(`Received attestation ${attestation.p2pMessageIdentifier()} from external peer.`);
386
- await this.attestationPool.addAttestations([attestation]);
414
+ this.logger.debug(`Received attestation ${attestation.p2pMessageIdentifier()} from external peer.`);
415
+ await this.mempools.attestationPool.addAttestations([attestation]);
387
416
  }
388
417
 
389
418
  /**Process block from peer
@@ -393,6 +422,12 @@ export class LibP2PService implements P2PService {
393
422
  * @param block - The block to process.
394
423
  */
395
424
  // REVIEW: callback pattern https://github.com/AztecProtocol/aztec-packages/issues/7963
425
+ @trackSpan('Libp2pService.processBlockFromPeer', block => ({
426
+ [Attributes.BLOCK_NUMBER]: block.payload.header.globalVariables.blockNumber.toNumber(),
427
+ [Attributes.SLOT_NUMBER]: block.payload.header.globalVariables.slotNumber.toNumber(),
428
+ [Attributes.BLOCK_ARCHIVE]: block.archive.toString(),
429
+ [Attributes.P2P_ID]: block.p2pMessageIdentifier().toString(),
430
+ }))
396
431
  private async processBlockFromPeer(block: BlockProposal): Promise<void> {
397
432
  this.logger.verbose(`Received block ${block.p2pMessageIdentifier()} from external peer.`);
398
433
  const attestation = await this.blockReceivedCallback(block);
@@ -400,16 +435,39 @@ export class LibP2PService implements P2PService {
400
435
  // TODO: fix up this pattern - the abstraction is not nice
401
436
  // The attestation can be undefined if no handler is registered / the validator deems the block invalid
402
437
  if (attestation != undefined) {
403
- this.propagate(attestation);
438
+ this.logger.verbose(`Broadcasting attestation ${attestation.p2pMessageIdentifier()}`);
439
+ this.broadcastAttestation(attestation);
404
440
  }
405
441
  }
406
442
 
443
+ /**
444
+ * Broadcast an attestation to all peers.
445
+ * @param attestation - The attestation to broadcast.
446
+ */
447
+ @trackSpan('Libp2pService.broadcastAttestation', attestation => ({
448
+ [Attributes.BLOCK_NUMBER]: attestation.payload.header.globalVariables.blockNumber.toNumber(),
449
+ [Attributes.SLOT_NUMBER]: attestation.payload.header.globalVariables.slotNumber.toNumber(),
450
+ [Attributes.BLOCK_ARCHIVE]: attestation.archive.toString(),
451
+ [Attributes.P2P_ID]: attestation.p2pMessageIdentifier().toString(),
452
+ }))
453
+ private broadcastAttestation(attestation: BlockAttestation): void {
454
+ this.propagate(attestation);
455
+ }
456
+
457
+ private processEpochProofQuoteFromPeer(epochProofQuote: EpochProofQuote): void {
458
+ this.logger.verbose(`Received epoch proof quote ${epochProofQuote.p2pMessageIdentifier()} from external peer.`);
459
+ this.mempools.epochProofQuotePool.addQuote(epochProofQuote);
460
+ }
461
+
407
462
  /**
408
463
  * Propagates provided message to peers.
409
464
  * @param message - The message to propagate.
410
465
  */
411
466
  public propagate<T extends Gossipable>(message: T): void {
412
- void this.jobQueue.put(() => Promise.resolve(this.sendToPeers(message)));
467
+ this.logger.debug(`[${message.p2pMessageIdentifier()}] queued`);
468
+ void this.jobQueue.put(async () => {
469
+ await this.sendToPeers(message);
470
+ });
413
471
  }
414
472
 
415
473
  private async processTxFromPeer(tx: Tx, peerId: PeerId): Promise<void> {
@@ -420,7 +478,7 @@ export class LibP2PService implements P2PService {
420
478
  const isValidTx = await this.validatePropagatedTx(tx, peerId);
421
479
 
422
480
  if (isValidTx) {
423
- await this.txPool.addTxs([tx]);
481
+ await this.mempools.txPool.addTxs([tx]);
424
482
  }
425
483
  }
426
484
 
@@ -481,7 +539,7 @@ export class LibP2PService implements P2PService {
481
539
  // double spend validation
482
540
  const doubleSpendValidator = new DoubleSpendTxValidator({
483
541
  getNullifierIndex: async (nullifier: Fr) => {
484
- const merkleTree = this.worldStateSynchronizer.getLatest();
542
+ const merkleTree = this.worldStateSynchronizer.getCommitted();
485
543
  const index = await merkleTree.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
486
544
  return index;
487
545
  },
@@ -533,10 +591,10 @@ export class LibP2PService implements P2PService {
533
591
  const parent = message.constructor as typeof Gossipable;
534
592
 
535
593
  const identifier = message.p2pMessageIdentifier().toString();
536
- this.logger.verbose(`Sending message ${identifier} to peers`);
594
+ this.logger.verbose(`[${identifier}] sending`);
537
595
 
538
596
  const recipientsNum = await this.publishToTopic(parent.p2pTopic, message.toBuffer());
539
- this.logger.verbose(`Sent message ${identifier} to ${recipientsNum} peers`);
597
+ this.logger.verbose(`[${identifier}] sent to ${recipientsNum} peers`);
540
598
  }
541
599
 
542
600
  // Libp2p seems to hang sometimes if new peers are initiating connections.
@@ -36,8 +36,6 @@ import { RequestResponseRateLimiter } from './rate_limiter/rate_limiter.js';
36
36
  export class ReqResp {
37
37
  protected readonly logger: Logger;
38
38
 
39
- private abortController: AbortController = new AbortController();
40
-
41
39
  private overallRequestTimeoutMs: number;
42
40
  private individualRequestTimeoutMs: number;
43
41
 
@@ -78,9 +76,16 @@ export class ReqResp {
78
76
  for (const protocol of Object.keys(this.subProtocolHandlers)) {
79
77
  await this.libp2p.unhandle(protocol);
80
78
  }
79
+
80
+ // Close all active connections
81
+ const closeStreamPromises = this.libp2p.getConnections().map(connection => connection.close());
82
+ await Promise.all(closeStreamPromises);
83
+ this.logger.debug('ReqResp: All active streams closed');
84
+
81
85
  this.rateLimiter.stop();
82
- await this.libp2p.stop();
83
- this.abortController.abort();
86
+ this.logger.debug('ReqResp: Rate limiter stopped');
87
+
88
+ // NOTE: We assume libp2p instance is managed by the caller
84
89
  }
85
90
 
86
91
  /**
@@ -187,7 +192,6 @@ export class ReqResp {
187
192
  let stream: Stream | undefined;
188
193
  try {
189
194
  stream = await this.libp2p.dialProtocol(peerId, subProtocol);
190
-
191
195
  this.logger.debug(`Stream opened with ${peerId.toString()} for ${subProtocol}`);
192
196
 
193
197
  // Open the stream with a timeout
@@ -1 +0,0 @@
1
- {"version":3,"file":"attestation_pool.d.ts","sourceRoot":"","sources":["../../src/attestation_pool/attestation_pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAE7D;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;OAIG;IACH,eAAe,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEjE;;;;OAIG;IACH,kBAAkB,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEpE;;;;;;OAMG;IACH,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEvD;;;;;;;OAOG;IACH,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;CACnE"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/attestation_pool/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAC;AACtC,cAAc,8BAA8B,CAAC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"memory_attestation_pool.d.ts","sourceRoot":"","sources":["../../src/attestation_pool/memory_attestation_pool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAG7D,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,qBAAa,uBAAwB,YAAW,eAAe;IAGjD,OAAO,CAAC,GAAG;IAFvB,OAAO,CAAC,YAAY,CAAoE;gBAEpE,GAAG,yCAA8C;IAI9D,sBAAsB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IASjE,eAAe,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAehE,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtD,kBAAkB,CAAC,YAAY,EAAE,gBAAgB,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CAY3E"}
@@ -1,57 +0,0 @@
1
- import { createDebugLogger } from '@aztec/foundation/log';
2
- export class InMemoryAttestationPool {
3
- constructor(log = createDebugLogger('aztec:attestation_pool')) {
4
- this.log = log;
5
- this.attestations = new Map();
6
- }
7
- getAttestationsForSlot(slot) {
8
- const slotAttestationMap = this.attestations.get(slot);
9
- if (slotAttestationMap) {
10
- return Promise.resolve(Array.from(slotAttestationMap.values()));
11
- }
12
- else {
13
- return Promise.resolve([]);
14
- }
15
- }
16
- addAttestations(attestations) {
17
- for (const attestation of attestations) {
18
- // Perf: order and group by slot before insertion
19
- const slotNumber = attestation.payload.header.globalVariables.slotNumber;
20
- const address = attestation.getSender();
21
- const slotAttestationMap = getSlotOrDefault(this.attestations, slotNumber.toBigInt());
22
- slotAttestationMap.set(address.toString(), attestation);
23
- this.log.verbose(`Added attestation for slot ${slotNumber} from ${address}`);
24
- }
25
- return Promise.resolve();
26
- }
27
- deleteAttestationsForSlot(slot) {
28
- // TODO(md): check if this will free the memory of the inner hash map
29
- this.attestations.delete(slot);
30
- this.log.verbose(`Removed attestation for slot ${slot}`);
31
- return Promise.resolve();
32
- }
33
- deleteAttestations(attestations) {
34
- for (const attestation of attestations) {
35
- const slotNumber = attestation.payload.header.globalVariables.slotNumber;
36
- const slotAttestationMap = this.attestations.get(slotNumber.toBigInt());
37
- if (slotAttestationMap) {
38
- const address = attestation.getSender();
39
- slotAttestationMap.delete(address.toString());
40
- this.log.verbose(`Deleted attestation for slot ${slotNumber} from ${address}`);
41
- }
42
- }
43
- return Promise.resolve();
44
- }
45
- }
46
- /**
47
- * Get Slot or Default
48
- *
49
- * Fetch the slot mapping, if it does not exist, then create a mapping and return it
50
- */
51
- function getSlotOrDefault(map, slot) {
52
- if (!map.has(slot)) {
53
- map.set(slot, new Map());
54
- }
55
- return map.get(slot);
56
- }
57
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWVtb3J5X2F0dGVzdGF0aW9uX3Bvb2wuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXR0ZXN0YXRpb25fcG9vbC9tZW1vcnlfYXR0ZXN0YXRpb25fcG9vbC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFDQSxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUkxRCxNQUFNLE9BQU8sdUJBQXVCO0lBR2xDLFlBQW9CLE1BQU0saUJBQWlCLENBQUMsd0JBQXdCLENBQUM7UUFBakQsUUFBRyxHQUFILEdBQUcsQ0FBOEM7UUFDbkUsSUFBSSxDQUFDLFlBQVksR0FBRyxJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ2hDLENBQUM7SUFFTSxzQkFBc0IsQ0FBQyxJQUFZO1FBQ3hDLE1BQU0sa0JBQWtCLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDdkQsSUFBSSxrQkFBa0IsRUFBRSxDQUFDO1lBQ3ZCLE9BQU8sT0FBTyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLGtCQUFrQixDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUMsQ0FBQztRQUNsRSxDQUFDO2FBQU0sQ0FBQztZQUNOLE9BQU8sT0FBTyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsQ0FBQztRQUM3QixDQUFDO0lBQ0gsQ0FBQztJQUVNLGVBQWUsQ0FBQyxZQUFnQztRQUNyRCxLQUFLLE1BQU0sV0FBVyxJQUFJLFlBQVksRUFBRSxDQUFDO1lBQ3ZDLGlEQUFpRDtZQUNqRCxNQUFNLFVBQVUsR0FBRyxXQUFXLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUMsVUFBVSxDQUFDO1lBRXpFLE1BQU0sT0FBTyxHQUFHLFdBQVcsQ0FBQyxTQUFTLEVBQUUsQ0FBQztZQUV4QyxNQUFNLGtCQUFrQixHQUFHLGdCQUFnQixDQUFDLElBQUksQ0FBQyxZQUFZLEVBQUUsVUFBVSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7WUFDdEYsa0JBQWtCLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxRQUFRLEVBQUUsRUFBRSxXQUFXLENBQUMsQ0FBQztZQUV4RCxJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyw4QkFBOEIsVUFBVSxTQUFTLE9BQU8sRUFBRSxDQUFDLENBQUM7UUFDL0UsQ0FBQztRQUNELE9BQU8sT0FBTyxDQUFDLE9BQU8sRUFBRSxDQUFDO0lBQzNCLENBQUM7SUFFTSx5QkFBeUIsQ0FBQyxJQUFZO1FBQzNDLHFFQUFxRTtRQUNyRSxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMvQixJQUFJLENBQUMsR0FBRyxDQUFDLE9BQU8sQ0FBQyxnQ0FBZ0MsSUFBSSxFQUFFLENBQUMsQ0FBQztRQUN6RCxPQUFPLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUMzQixDQUFDO0lBRU0sa0JBQWtCLENBQUMsWUFBZ0M7UUFDeEQsS0FBSyxNQUFNLFdBQVcsSUFBSSxZQUFZLEVBQUUsQ0FBQztZQUN2QyxNQUFNLFVBQVUsR0FBRyxXQUFXLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUMsVUFBVSxDQUFDO1lBQ3pFLE1BQU0sa0JBQWtCLEdBQUcsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7WUFDeEUsSUFBSSxrQkFBa0IsRUFBRSxDQUFDO2dCQUN2QixNQUFNLE9BQU8sR0FBRyxXQUFXLENBQUMsU0FBUyxFQUFFLENBQUM7Z0JBQ3hDLGtCQUFrQixDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQztnQkFDOUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxPQUFPLENBQUMsZ0NBQWdDLFVBQVUsU0FBUyxPQUFPLEVBQUUsQ0FBQyxDQUFDO1lBQ2pGLENBQUM7UUFDSCxDQUFDO1FBQ0QsT0FBTyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDM0IsQ0FBQztDQUNGO0FBRUQ7Ozs7R0FJRztBQUNILFNBQVMsZ0JBQWdCLENBQ3ZCLEdBQStDLEVBQy9DLElBQVk7SUFFWixJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1FBQ25CLEdBQUcsQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFLElBQUksR0FBRyxFQUE0QixDQUFDLENBQUM7SUFDckQsQ0FBQztJQUNELE9BQU8sR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUUsQ0FBQztBQUN4QixDQUFDIn0=
@@ -1 +0,0 @@
1
- {"version":3,"file":"mocks.d.ts","sourceRoot":"","sources":["../../src/attestation_pool/mocks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAA4B,MAAM,sBAAsB,CAAC;AAKlF,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,MAAM,CAAC;AAG9C;;;;GAIG;AACH,eAAO,MAAM,eAAe,yBAG3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,eAAe,WAAkB,iBAAiB,SAAQ,MAAM,KAAO,QAAQ,gBAAgB,CAa3G,CAAC"}
@@ -1,32 +0,0 @@
1
- import { BlockAttestation, ConsensusPayload, TxHash } from '@aztec/circuit-types';
2
- import { makeHeader } from '@aztec/circuits.js/testing';
3
- import { Signature } from '@aztec/foundation/eth-signature';
4
- import { Fr } from '@aztec/foundation/fields';
5
- import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
6
- /** Generate Account
7
- *
8
- * Create a random signer
9
- * @returns A random viem signer
10
- */
11
- export const generateAccount = () => {
12
- const privateKey = generatePrivateKey();
13
- return privateKeyToAccount(privateKey);
14
- };
15
- /** Mock Attestation
16
- *
17
- * @param signer A viem signer to create a signature
18
- * @param slot The slot number the attestation is for
19
- * @returns A Block Attestation
20
- */
21
- export const mockAttestation = async (signer, slot = 0) => {
22
- // Use arbitrary numbers for all other than slot
23
- const header = makeHeader(1, 2, slot);
24
- const archive = Fr.random();
25
- const txs = [0, 1, 2, 3, 4, 5].map(() => TxHash.random());
26
- const payload = new ConsensusPayload(header, archive, txs);
27
- const message = `0x${payload.getPayloadToSign().toString('hex')}`;
28
- const sigString = await signer.signMessage({ message });
29
- const signature = Signature.from0xString(sigString);
30
- return new BlockAttestation(payload, signature);
31
- };
32
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibW9ja3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvYXR0ZXN0YXRpb25fcG9vbC9tb2Nrcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxFQUFFLE1BQU0sc0JBQXNCLENBQUM7QUFDbEYsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ3hELE9BQU8sRUFBRSxTQUFTLEVBQUUsTUFBTSxpQ0FBaUMsQ0FBQztBQUM1RCxPQUFPLEVBQUUsRUFBRSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFHOUMsT0FBTyxFQUFFLGtCQUFrQixFQUFFLG1CQUFtQixFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRXhFOzs7O0dBSUc7QUFDSCxNQUFNLENBQUMsTUFBTSxlQUFlLEdBQUcsR0FBRyxFQUFFO0lBQ2xDLE1BQU0sVUFBVSxHQUFHLGtCQUFrQixFQUFFLENBQUM7SUFDeEMsT0FBTyxtQkFBbUIsQ0FBQyxVQUFVLENBQUMsQ0FBQztBQUN6QyxDQUFDLENBQUM7QUFFRjs7Ozs7R0FLRztBQUNILE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRyxLQUFLLEVBQUUsTUFBeUIsRUFBRSxPQUFlLENBQUMsRUFBNkIsRUFBRTtJQUM5RyxnREFBZ0Q7SUFDaEQsTUFBTSxNQUFNLEdBQUcsVUFBVSxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFDdEMsTUFBTSxPQUFPLEdBQUcsRUFBRSxDQUFDLE1BQU0sRUFBRSxDQUFDO0lBQzVCLE1BQU0sR0FBRyxHQUFHLENBQUMsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLEVBQUUsQ0FBQyxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLENBQUMsTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDLENBQUM7SUFFMUQsTUFBTSxPQUFPLEdBQUcsSUFBSSxnQkFBZ0IsQ0FBQyxNQUFNLEVBQUUsT0FBTyxFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBRTNELE1BQU0sT0FBTyxHQUFrQixLQUFLLE9BQU8sQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO0lBQ2pGLE1BQU0sU0FBUyxHQUFHLE1BQU0sTUFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUM7SUFFeEQsTUFBTSxTQUFTLEdBQUcsU0FBUyxDQUFDLFlBQVksQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUNwRCxPQUFPLElBQUksZ0JBQWdCLENBQUMsT0FBTyxFQUFFLFNBQVMsQ0FBQyxDQUFDO0FBQ2xELENBQUMsQ0FBQyJ9