@aztec/p2p 0.7.2 → 0.7.4

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 (60) hide show
  1. package/.tsbuildinfo +1 -0
  2. package/dest/bootstrap/bootstrap.d.ts +26 -0
  3. package/dest/bootstrap/bootstrap.d.ts.map +1 -0
  4. package/dest/bootstrap/bootstrap.js +92 -0
  5. package/dest/client/index.d.ts +5 -0
  6. package/dest/client/index.d.ts.map +1 -0
  7. package/dest/client/index.js +8 -0
  8. package/dest/client/mocks.d.ts +51 -0
  9. package/dest/client/mocks.d.ts.map +1 -0
  10. package/dest/client/mocks.js +72 -0
  11. package/dest/client/p2p_client.d.ts +175 -0
  12. package/dest/client/p2p_client.d.ts.map +1 -0
  13. package/dest/client/p2p_client.js +206 -0
  14. package/dest/client/p2p_client.test.d.ts +2 -0
  15. package/dest/client/p2p_client.test.d.ts.map +1 -0
  16. package/dest/client/p2p_client.test.js +58 -0
  17. package/dest/config.d.ts +67 -0
  18. package/dest/config.d.ts.map +1 -0
  19. package/dest/config.js +25 -0
  20. package/dest/index.d.ts +6 -0
  21. package/dest/index.d.ts.map +1 -0
  22. package/dest/index.js +6 -0
  23. package/dest/service/dummy_service.d.ts +28 -0
  24. package/dest/service/dummy_service.d.ts.map +1 -0
  25. package/dest/service/dummy_service.js +30 -0
  26. package/dest/service/index.d.ts +3 -0
  27. package/dest/service/index.d.ts.map +1 -0
  28. package/dest/service/index.js +3 -0
  29. package/dest/service/known_txs.d.ts +31 -0
  30. package/dest/service/known_txs.d.ts.map +1 -0
  31. package/dest/service/known_txs.js +52 -0
  32. package/dest/service/known_txs.test.d.ts +2 -0
  33. package/dest/service/known_txs.test.d.ts.map +1 -0
  34. package/dest/service/known_txs.test.js +34 -0
  35. package/dest/service/libp2p_service.d.ts +74 -0
  36. package/dest/service/libp2p_service.d.ts.map +1 -0
  37. package/dest/service/libp2p_service.js +335 -0
  38. package/dest/service/service.d.ts +27 -0
  39. package/dest/service/service.d.ts.map +1 -0
  40. package/dest/service/service.js +2 -0
  41. package/dest/service/tx_messages.d.ts +78 -0
  42. package/dest/service/tx_messages.d.ts.map +1 -0
  43. package/dest/service/tx_messages.js +191 -0
  44. package/dest/service/tx_messages.test.d.ts +2 -0
  45. package/dest/service/tx_messages.test.d.ts.map +1 -0
  46. package/dest/service/tx_messages.test.js +48 -0
  47. package/dest/tx_pool/index.d.ts +3 -0
  48. package/dest/tx_pool/index.d.ts.map +1 -0
  49. package/dest/tx_pool/index.js +3 -0
  50. package/dest/tx_pool/memory_tx_pool.d.ts +52 -0
  51. package/dest/tx_pool/memory_tx_pool.d.ts.map +1 -0
  52. package/dest/tx_pool/memory_tx_pool.js +70 -0
  53. package/dest/tx_pool/tx_pool.d.ts +39 -0
  54. package/dest/tx_pool/tx_pool.d.ts.map +1 -0
  55. package/dest/tx_pool/tx_pool.js +2 -0
  56. package/dest/tx_pool/tx_pool.test.d.ts +2 -0
  57. package/dest/tx_pool/tx_pool.test.d.ts.map +1 -0
  58. package/dest/tx_pool/tx_pool.test.js +20 -0
  59. package/package.json +4 -4
  60. package/Dockerfile +0 -17
@@ -0,0 +1,28 @@
1
+ import { Tx, TxHash } from '@aztec/types';
2
+ import { P2PService } from './service.js';
3
+ /**
4
+ * A dummy implementation of the P2P Service.
5
+ */
6
+ export declare class DummyP2PService implements P2PService {
7
+ /**
8
+ * Starts the dummy implementation.
9
+ * @returns A resolved promise.
10
+ */
11
+ start(): Promise<void>;
12
+ /**
13
+ * Stops the dummy imaplementation.
14
+ * @returns A resolved promise.
15
+ */
16
+ stop(): Promise<void>;
17
+ /**
18
+ * Called to have the given transaction propagated through the P2P network.
19
+ * @param _ - The transaction to be propagated.
20
+ */
21
+ propagateTx(_: Tx): void;
22
+ /**
23
+ * Called upon receipt of settled transactions.
24
+ * @param _ - The hashes of the settled transactions.
25
+ */
26
+ settledTxs(_: TxHash[]): void;
27
+ }
28
+ //# sourceMappingURL=dummy_service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dummy_service.d.ts","sourceRoot":"","sources":["../../src/service/dummy_service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAE1C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C;;GAEG;AACH,qBAAa,eAAgB,YAAW,UAAU;IAChD;;;OAGG;IACI,KAAK;IAIZ;;;OAGG;IACI,IAAI;IAIX;;;OAGG;IACI,WAAW,CAAC,CAAC,EAAE,EAAE;IAExB;;;OAGG;IACI,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE;CAC9B"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * A dummy implementation of the P2P Service.
3
+ */
4
+ export class DummyP2PService {
5
+ /**
6
+ * Starts the dummy implementation.
7
+ * @returns A resolved promise.
8
+ */
9
+ start() {
10
+ return Promise.resolve();
11
+ }
12
+ /**
13
+ * Stops the dummy imaplementation.
14
+ * @returns A resolved promise.
15
+ */
16
+ stop() {
17
+ return Promise.resolve();
18
+ }
19
+ /**
20
+ * Called to have the given transaction propagated through the P2P network.
21
+ * @param _ - The transaction to be propagated.
22
+ */
23
+ propagateTx(_) { }
24
+ /**
25
+ * Called upon receipt of settled transactions.
26
+ * @param _ - The hashes of the settled transactions.
27
+ */
28
+ settledTxs(_) { }
29
+ }
30
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZHVtbXlfc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9zZXJ2aWNlL2R1bW15X3NlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBSUE7O0dBRUc7QUFDSCxNQUFNLE9BQU8sZUFBZTtJQUMxQjs7O09BR0c7SUFDSSxLQUFLO1FBQ1YsT0FBTyxPQUFPLENBQUMsT0FBTyxFQUFFLENBQUM7SUFDM0IsQ0FBQztJQUVEOzs7T0FHRztJQUNJLElBQUk7UUFDVCxPQUFPLE9BQU8sQ0FBQyxPQUFPLEVBQUUsQ0FBQztJQUMzQixDQUFDO0lBRUQ7OztPQUdHO0lBQ0ksV0FBVyxDQUFDLENBQUssSUFBRyxDQUFDO0lBRTVCOzs7T0FHRztJQUNJLFVBQVUsQ0FBQyxDQUFXLElBQUcsQ0FBQztDQUNsQyJ9
@@ -0,0 +1,3 @@
1
+ export * from './service.js';
2
+ export * from './libp2p_service.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/service/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from './service.js';
2
+ export * from './libp2p_service.js';
3
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZS9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLGNBQWMsQ0FBQztBQUM3QixjQUFjLHFCQUFxQixDQUFDIn0=
@@ -0,0 +1,31 @@
1
+ import { PeerId } from '@libp2p/interface-peer-id';
2
+ /**
3
+ * Keeps a record of which Peers have 'seen' which transactions.
4
+ */
5
+ export declare class KnownTxLookup {
6
+ private lookup;
7
+ constructor();
8
+ /**
9
+ * Inform this lookup that a peer has 'seen' a transaction.
10
+ * @param peerId - The peerId of the peer that has 'seen' the transaction.
11
+ * @param txHash - The thHash of the 'seen' transaction.
12
+ */
13
+ addPeerForTx(peerId: PeerId, txHash: string): void;
14
+ /**
15
+ * Determine if a peer has 'seen' a transaction.
16
+ * @param peerId - The peerId of the peer.
17
+ * @param txHash - The thHash of the transaction.
18
+ * @returns A boolean indicating if the transaction has been 'seen' by the peer.
19
+ */
20
+ hasPeerSeenTx(peerId: PeerId, txHash: string): boolean;
21
+ /**
22
+ * Updates the lookup from the result of settled txs
23
+ * These txs will be cleared out of the lookup.
24
+ * It is possible that some txs could still be gossiped for a
25
+ * short period of time meaning they come back into this lookup
26
+ * but this should be infrequent and cause no undesirable effects
27
+ * @param txHashes - The hashes of the newly settled transactions
28
+ */
29
+ handleSettledTxs(txHashes: string[]): void;
30
+ }
31
+ //# sourceMappingURL=known_txs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"known_txs.d.ts","sourceRoot":"","sources":["../../src/service/known_txs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAEnD;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAqD;;IAInE;;;;OAIG;IACI,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IAYlD;;;;;OAKG;IACI,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;IASnD;;;;;;;OAOG;IACI,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE;CAK3C"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Keeps a record of which Peers have 'seen' which transactions.
3
+ */
4
+ export class KnownTxLookup {
5
+ constructor() {
6
+ this.lookup = {};
7
+ }
8
+ /**
9
+ * Inform this lookup that a peer has 'seen' a transaction.
10
+ * @param peerId - The peerId of the peer that has 'seen' the transaction.
11
+ * @param txHash - The thHash of the 'seen' transaction.
12
+ */
13
+ addPeerForTx(peerId, txHash) {
14
+ const peerIdAsString = peerId.toString();
15
+ const existingLookup = this.lookup[txHash];
16
+ if (existingLookup === undefined) {
17
+ const newLookup = {};
18
+ newLookup[peerIdAsString] = true;
19
+ this.lookup[txHash] = newLookup;
20
+ return;
21
+ }
22
+ existingLookup[peerIdAsString] = true;
23
+ }
24
+ /**
25
+ * Determine if a peer has 'seen' a transaction.
26
+ * @param peerId - The peerId of the peer.
27
+ * @param txHash - The thHash of the transaction.
28
+ * @returns A boolean indicating if the transaction has been 'seen' by the peer.
29
+ */
30
+ hasPeerSeenTx(peerId, txHash) {
31
+ const existingLookup = this.lookup[txHash];
32
+ if (existingLookup === undefined) {
33
+ return false;
34
+ }
35
+ const peerIdAsString = peerId.toString();
36
+ return !!existingLookup[peerIdAsString];
37
+ }
38
+ /**
39
+ * Updates the lookup from the result of settled txs
40
+ * These txs will be cleared out of the lookup.
41
+ * It is possible that some txs could still be gossiped for a
42
+ * short period of time meaning they come back into this lookup
43
+ * but this should be infrequent and cause no undesirable effects
44
+ * @param txHashes - The hashes of the newly settled transactions
45
+ */
46
+ handleSettledTxs(txHashes) {
47
+ for (const txHash of txHashes) {
48
+ delete this.lookup[txHash];
49
+ }
50
+ }
51
+ }
52
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoia25vd25fdHhzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3NlcnZpY2Uva25vd25fdHhzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBOztHQUVHO0FBQ0gsTUFBTSxPQUFPLGFBQWE7SUFHeEI7UUFGUSxXQUFNLEdBQWtELEVBQUUsQ0FBQztJQUVwRCxDQUFDO0lBRWhCOzs7O09BSUc7SUFDSSxZQUFZLENBQUMsTUFBYyxFQUFFLE1BQWM7UUFDaEQsTUFBTSxjQUFjLEdBQUcsTUFBTSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ3pDLE1BQU0sY0FBYyxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDM0MsSUFBSSxjQUFjLEtBQUssU0FBUyxFQUFFO1lBQ2hDLE1BQU0sU0FBUyxHQUErQixFQUFFLENBQUM7WUFDakQsU0FBUyxDQUFDLGNBQWMsQ0FBQyxHQUFHLElBQUksQ0FBQztZQUNqQyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxHQUFHLFNBQVMsQ0FBQztZQUNoQyxPQUFPO1NBQ1I7UUFDRCxjQUFjLENBQUMsY0FBYyxDQUFDLEdBQUcsSUFBSSxDQUFDO0lBQ3hDLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNJLGFBQWEsQ0FBQyxNQUFjLEVBQUUsTUFBYztRQUNqRCxNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQzNDLElBQUksY0FBYyxLQUFLLFNBQVMsRUFBRTtZQUNoQyxPQUFPLEtBQUssQ0FBQztTQUNkO1FBQ0QsTUFBTSxjQUFjLEdBQUcsTUFBTSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ3pDLE9BQU8sQ0FBQyxDQUFDLGNBQWMsQ0FBQyxjQUFjLENBQUMsQ0FBQztJQUMxQyxDQUFDO0lBRUQ7Ozs7Ozs7T0FPRztJQUNJLGdCQUFnQixDQUFDLFFBQWtCO1FBQ3hDLEtBQUssTUFBTSxNQUFNLElBQUksUUFBUSxFQUFFO1lBQzdCLE9BQU8sSUFBSSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsQ0FBQztTQUM1QjtJQUNILENBQUM7Q0FDRiJ9
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=known_txs.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"known_txs.test.d.ts","sourceRoot":"","sources":["../../src/service/known_txs.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,34 @@
1
+ import { randomBytes } from '@aztec/foundation/crypto';
2
+ import { TxHash } from '@aztec/types';
3
+ import { expect } from '@jest/globals';
4
+ import { mock } from 'jest-mock-extended';
5
+ import { KnownTxLookup } from './known_txs.js';
6
+ const createMockPeerId = (peerId) => {
7
+ return mock({
8
+ toString: () => peerId,
9
+ });
10
+ };
11
+ const createTxHash = () => {
12
+ return new TxHash(randomBytes(32));
13
+ };
14
+ describe('Known Txs', () => {
15
+ it('Returns false when a peer has not seen a tx', () => {
16
+ const knownTxs = new KnownTxLookup();
17
+ const peer = createMockPeerId('Peer 1');
18
+ const txHash = createTxHash();
19
+ expect(knownTxs.hasPeerSeenTx(peer, txHash.toString())).toEqual(false);
20
+ });
21
+ it('Returns true when a peer has seen a tx', () => {
22
+ const knownTxs = new KnownTxLookup();
23
+ const peer = createMockPeerId('Peer 1');
24
+ const peer2 = createMockPeerId('Peer 2');
25
+ const txHash = createTxHash();
26
+ knownTxs.addPeerForTx(peer, txHash.toString());
27
+ expect(knownTxs.hasPeerSeenTx(peer, txHash.toString())).toEqual(true);
28
+ expect(knownTxs.hasPeerSeenTx(peer2, txHash.toString())).toEqual(false);
29
+ knownTxs.addPeerForTx(peer2, txHash.toString());
30
+ expect(knownTxs.hasPeerSeenTx(peer, txHash.toString())).toEqual(true);
31
+ expect(knownTxs.hasPeerSeenTx(peer2, txHash.toString())).toEqual(true);
32
+ });
33
+ });
34
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoia25vd25fdHhzLnRlc3QuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZS9rbm93bl90eHMudGVzdC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDdkQsT0FBTyxFQUFFLE1BQU0sRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUV0QyxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRXZDLE9BQU8sRUFBRSxJQUFJLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUUxQyxPQUFPLEVBQUUsYUFBYSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFFL0MsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLE1BQWMsRUFBVSxFQUFFO0lBQ2xELE9BQU8sSUFBSSxDQUFnQjtRQUN6QixRQUFRLEVBQUUsR0FBRyxFQUFFLENBQUMsTUFBTTtLQUN2QixDQUFDLENBQUM7QUFDTCxDQUFDLENBQUM7QUFFRixNQUFNLFlBQVksR0FBRyxHQUFHLEVBQUU7SUFDeEIsT0FBTyxJQUFJLE1BQU0sQ0FBQyxXQUFXLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztBQUNyQyxDQUFDLENBQUM7QUFFRixRQUFRLENBQUMsV0FBVyxFQUFFLEdBQUcsRUFBRTtJQUN6QixFQUFFLENBQUMsNkNBQTZDLEVBQUUsR0FBRyxFQUFFO1FBQ3JELE1BQU0sUUFBUSxHQUFHLElBQUksYUFBYSxFQUFFLENBQUM7UUFFckMsTUFBTSxJQUFJLEdBQUcsZ0JBQWdCLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDeEMsTUFBTSxNQUFNLEdBQUcsWUFBWSxFQUFFLENBQUM7UUFFOUIsTUFBTSxDQUFDLFFBQVEsQ0FBQyxhQUFhLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ3pFLENBQUMsQ0FBQyxDQUFDO0lBRUgsRUFBRSxDQUFDLHdDQUF3QyxFQUFFLEdBQUcsRUFBRTtRQUNoRCxNQUFNLFFBQVEsR0FBRyxJQUFJLGFBQWEsRUFBRSxDQUFDO1FBRXJDLE1BQU0sSUFBSSxHQUFHLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3hDLE1BQU0sS0FBSyxHQUFHLGdCQUFnQixDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3pDLE1BQU0sTUFBTSxHQUFHLFlBQVksRUFBRSxDQUFDO1FBRTlCLFFBQVEsQ0FBQyxZQUFZLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO1FBRS9DLE1BQU0sQ0FBQyxRQUFRLENBQUMsYUFBYSxDQUFDLElBQUksRUFBRSxNQUFNLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN0RSxNQUFNLENBQUMsUUFBUSxDQUFDLGFBQWEsQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7UUFFeEUsUUFBUSxDQUFDLFlBQVksQ0FBQyxLQUFLLEVBQUUsTUFBTSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7UUFFaEQsTUFBTSxDQUFDLFFBQVEsQ0FBQyxhQUFhLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3RFLE1BQU0sQ0FBQyxRQUFRLENBQUMsYUFBYSxDQUFDLEtBQUssRUFBRSxNQUFNLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUN6RSxDQUFDLENBQUMsQ0FBQztBQUNMLENBQUMsQ0FBQyxDQUFDIn0=
@@ -0,0 +1,74 @@
1
+ import { Tx, TxHash } from '@aztec/types';
2
+ import { PeerId } from '@libp2p/interface-peer-id';
3
+ import { Libp2p } from 'libp2p';
4
+ import { P2PConfig } from '../config.js';
5
+ import { TxPool } from '../index.js';
6
+ import { P2PService } from './service.js';
7
+ /**
8
+ * Create a libp2p peer ID.
9
+ * @returns The peer ID.
10
+ */
11
+ export declare function createLibP2PPeerId(): Promise<import("@libp2p/interface/peer-id").Ed25519PeerId>;
12
+ /**
13
+ * Exports a given peer id to a string representation.
14
+ * @param peerId - The peerId instance to be converted.
15
+ * @returns The peer id as a string.
16
+ */
17
+ export declare function exportLibP2PPeerIdToString(peerId: PeerId): string;
18
+ /**
19
+ * Lib P2P implementation of the P2PService interface.
20
+ */
21
+ export declare class LibP2PService implements P2PService {
22
+ private config;
23
+ private node;
24
+ private protocolId;
25
+ private txPool;
26
+ private logger;
27
+ private jobQueue;
28
+ private timeout;
29
+ private knownTxLookup;
30
+ constructor(config: P2PConfig, node: Libp2p, protocolId: string, txPool: TxPool, logger?: import("@aztec/foundation/log").DebugLogger);
31
+ /**
32
+ * Starts the LibP2P service.
33
+ * @returns An empty promise.
34
+ */
35
+ start(): Promise<void>;
36
+ /**
37
+ * Stops the LibP2P service.
38
+ * @returns An empty promise.
39
+ */
40
+ stop(): Promise<void>;
41
+ /**
42
+ * Creates an instance of the LibP2P service.
43
+ * @param config - The configuration to use when creating the service.
44
+ * @param txPool - The transaction pool to be accessed by the service.
45
+ * @returns The new service.
46
+ */
47
+ static new(config: P2PConfig, txPool: TxPool): Promise<LibP2PService>;
48
+ /**
49
+ * Propagates the provided transaction to peers.
50
+ * @param tx - The transaction to propagate.
51
+ */
52
+ propagateTx(tx: Tx): void;
53
+ /**
54
+ * Handles the settling of a new batch of transactions.
55
+ * @param txHashes - The hashes of the newly settled transactions.
56
+ */
57
+ settledTxs(txHashes: TxHash[]): void;
58
+ private handleProtocolDial;
59
+ private consumeInboundStream;
60
+ private handleNewConnection;
61
+ private processMessage;
62
+ private processReceivedTxHashes;
63
+ private processReceivedGetTransactionsRequest;
64
+ private processReceivedTxs;
65
+ private processTxFromPeer;
66
+ private sendTxToPeers;
67
+ private sendTxHashesMessageToPeer;
68
+ private sendGetTransactionsMessageToPeer;
69
+ private sendTransactionsMessageToPeer;
70
+ private sendRawMessageToPeer;
71
+ private getTxPeers;
72
+ private isBootstrapPeer;
73
+ }
74
+ //# sourceMappingURL=libp2p_service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"libp2p_service.d.ts","sourceRoot":"","sources":["../../src/service/libp2p_service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAM1C,OAAO,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAC;AAOnD,OAAO,EAAE,MAAM,EAAkD,MAAM,QAAQ,CAAC;AAIhF,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAErC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAc1C;;;GAGG;AACH,wBAAsB,kBAAkB,+DAEvC;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,UAExD;AAED;;GAEG;AACH,qBAAa,aAAc,YAAW,UAAU;IAK5C,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,UAAU;IAClB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,MAAM;IARhB,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,OAAO,CAAuC;IACtD,OAAO,CAAC,aAAa,CAAsC;gBAEjD,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,MAAM,8CAA4C;IAG5D;;;OAGG;IACU,KAAK;IA2ClB;;;OAGG;IACU,IAAI;IAQjB;;;;;OAKG;WACiB,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM;IA4DzD;;;OAGG;IACI,WAAW,CAAC,EAAE,EAAE,EAAE,GAAG,IAAI;IAIhC;;;OAGG;IACI,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,IAAI;YAI7B,kBAAkB;YAelB,oBAAoB;IAYlC,OAAO,CAAC,mBAAmB;YAYb,cAAc;YAiBd,uBAAuB;YAevB,qCAAqC;YAgBrC,kBAAkB;YAalB,iBAAiB;YASjB,aAAa;YAsBb,yBAAyB;YAazB,gCAAgC;YAShC,6BAA6B;YAW7B,oBAAoB;IAMlC,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,eAAe;CAGxB"}
@@ -0,0 +1,335 @@
1
+ import { SerialQueue } from '@aztec/foundation/fifo';
2
+ import { createDebugLogger } from '@aztec/foundation/log';
3
+ import { noise } from '@chainsafe/libp2p-noise';
4
+ import { yamux } from '@chainsafe/libp2p-yamux';
5
+ import { bootstrap } from '@libp2p/bootstrap';
6
+ import { kadDHT } from '@libp2p/kad-dht';
7
+ import { mplex } from '@libp2p/mplex';
8
+ import { createEd25519PeerId, createFromProtobuf, exportToProtobuf } from '@libp2p/peer-id-factory';
9
+ import { tcp } from '@libp2p/tcp';
10
+ import { pipe } from 'it-pipe';
11
+ import { createLibp2p } from 'libp2p';
12
+ import { autoNATService } from 'libp2p/autonat';
13
+ import { identifyService } from 'libp2p/identify';
14
+ import { KnownTxLookup } from './known_txs.js';
15
+ import { Messages, createGetTransactionsRequestMessage, createTransactionHashesMessage, createTransactionsMessage, decodeGetTransactionsRequestMessage, decodeTransactionHashesMessage, decodeTransactionsMessage, getEncodedMessage, } from './tx_messages.js';
16
+ const INITIAL_PEER_REFRESH_INTERVAL = 20000;
17
+ /**
18
+ * Create a libp2p peer ID.
19
+ * @returns The peer ID.
20
+ */
21
+ export async function createLibP2PPeerId() {
22
+ return await createEd25519PeerId();
23
+ }
24
+ /**
25
+ * Exports a given peer id to a string representation.
26
+ * @param peerId - The peerId instance to be converted.
27
+ * @returns The peer id as a string.
28
+ */
29
+ export function exportLibP2PPeerIdToString(peerId) {
30
+ return Buffer.from(exportToProtobuf(peerId)).toString('hex');
31
+ }
32
+ /**
33
+ * Lib P2P implementation of the P2PService interface.
34
+ */
35
+ export class LibP2PService {
36
+ constructor(config, node, protocolId, txPool, logger = createDebugLogger('aztec:libp2p_service')) {
37
+ this.config = config;
38
+ this.node = node;
39
+ this.protocolId = protocolId;
40
+ this.txPool = txPool;
41
+ this.logger = logger;
42
+ this.jobQueue = new SerialQueue();
43
+ this.timeout = undefined;
44
+ this.knownTxLookup = new KnownTxLookup();
45
+ }
46
+ /**
47
+ * Starts the LibP2P service.
48
+ * @returns An empty promise.
49
+ */
50
+ async start() {
51
+ if (this.node.isStarted()) {
52
+ throw new Error('P2P service already started');
53
+ }
54
+ const { enableNat, tcpListenIp, tcpListenPort, announceHostname, announcePort } = this.config;
55
+ this.logger(`Starting P2P node on ${tcpListenIp}:${tcpListenPort}`);
56
+ if (announceHostname)
57
+ this.logger(`Announcing at ${announceHostname}:${announcePort ?? tcpListenPort}`);
58
+ if (enableNat)
59
+ this.logger(`Enabling NAT in libp2p module`);
60
+ this.node.addEventListener('peer:discovery', evt => {
61
+ const peerId = evt.detail.id;
62
+ if (this.isBootstrapPeer(peerId)) {
63
+ this.logger(`Discovered bootstrap peer ${peerId.toString()}`);
64
+ }
65
+ });
66
+ this.node.addEventListener('peer:connect', evt => {
67
+ const peerId = evt.detail;
68
+ this.handleNewConnection(peerId);
69
+ });
70
+ this.node.addEventListener('peer:disconnect', evt => {
71
+ const peerId = evt.detail;
72
+ if (this.isBootstrapPeer(peerId)) {
73
+ this.logger(`Disconnect from bootstrap peer ${peerId.toString()}`);
74
+ }
75
+ else {
76
+ this.logger(`Disconnected from transaction peer ${peerId.toString()}`);
77
+ }
78
+ });
79
+ this.jobQueue.start();
80
+ await this.node.start();
81
+ await this.node.handle(this.protocolId, (incoming) => this.jobQueue.put(() => Promise.resolve(this.handleProtocolDial(incoming))));
82
+ const dht = this.node.services['kadDHT'];
83
+ this.logger(`Started P2P client as ${await dht.getMode()} with Peer ID ${this.node.peerId.toString()}`);
84
+ this.timeout = setTimeout(async () => {
85
+ this.logger(`Refreshing routing table...`);
86
+ await dht.refreshRoutingTable();
87
+ }, INITIAL_PEER_REFRESH_INTERVAL);
88
+ }
89
+ /**
90
+ * Stops the LibP2P service.
91
+ * @returns An empty promise.
92
+ */
93
+ async stop() {
94
+ if (this.timeout) {
95
+ clearTimeout(this.timeout);
96
+ }
97
+ await this.jobQueue.end();
98
+ await this.node.stop();
99
+ }
100
+ /**
101
+ * Creates an instance of the LibP2P service.
102
+ * @param config - The configuration to use when creating the service.
103
+ * @param txPool - The transaction pool to be accessed by the service.
104
+ * @returns The new service.
105
+ */
106
+ static async new(config, txPool) {
107
+ const { enableNat, tcpListenIp, tcpListenPort, announceHostname, announcePort, serverMode, minPeerCount, maxPeerCount, } = config;
108
+ const peerId = config.peerIdPrivateKey
109
+ ? await createFromProtobuf(Buffer.from(config.peerIdPrivateKey, 'hex'))
110
+ : await createLibP2PPeerId();
111
+ const opts = {
112
+ start: false,
113
+ peerId,
114
+ addresses: {
115
+ listen: [`/ip4/${tcpListenIp}/tcp/${tcpListenPort}`],
116
+ announce: announceHostname ? [`/ip4/${announceHostname}/tcp/${announcePort ?? tcpListenPort}`] : [],
117
+ },
118
+ transports: [tcp()],
119
+ streamMuxers: [yamux(), mplex()],
120
+ connectionEncryption: [noise()],
121
+ connectionManager: {
122
+ minConnections: minPeerCount,
123
+ maxConnections: maxPeerCount,
124
+ },
125
+ peerDiscovery: [
126
+ bootstrap({
127
+ list: config.bootstrapNodes,
128
+ }),
129
+ ],
130
+ };
131
+ const services = {
132
+ identify: identifyService({
133
+ protocolPrefix: 'aztec',
134
+ }),
135
+ kadDHT: kadDHT({
136
+ protocolPrefix: 'aztec',
137
+ clientMode: !serverMode,
138
+ }),
139
+ };
140
+ if (enableNat) {
141
+ services.nat = autoNATService({
142
+ protocolPrefix: 'aztec',
143
+ });
144
+ }
145
+ const node = await createLibp2p({
146
+ ...opts,
147
+ services,
148
+ });
149
+ const protocolId = config.transactionProtocol;
150
+ return new LibP2PService(config, node, protocolId, txPool);
151
+ }
152
+ /**
153
+ * Propagates the provided transaction to peers.
154
+ * @param tx - The transaction to propagate.
155
+ */
156
+ propagateTx(tx) {
157
+ void this.jobQueue.put(() => Promise.resolve(this.sendTxToPeers(tx)));
158
+ }
159
+ /**
160
+ * Handles the settling of a new batch of transactions.
161
+ * @param txHashes - The hashes of the newly settled transactions.
162
+ */
163
+ settledTxs(txHashes) {
164
+ this.knownTxLookup.handleSettledTxs(txHashes.map(x => x.toString()));
165
+ }
166
+ async handleProtocolDial(incomingStreamData) {
167
+ try {
168
+ const { message, peer } = await this.consumeInboundStream(incomingStreamData);
169
+ if (!message.length) {
170
+ this.logger(`Ignoring 0 byte message from peer${peer.toString()}`);
171
+ }
172
+ await this.processMessage(message, peer);
173
+ }
174
+ catch (err) {
175
+ this.logger.error(`Failed to handle received message from peer ${incomingStreamData.connection.remotePeer.toString()}`, err);
176
+ }
177
+ }
178
+ async consumeInboundStream(incomingStreamData) {
179
+ let buffer = Buffer.alloc(0);
180
+ await pipe(incomingStreamData.stream, async (source) => {
181
+ for await (const msg of source) {
182
+ const payload = msg.subarray();
183
+ buffer = Buffer.concat([buffer, Buffer.from(payload)]);
184
+ }
185
+ });
186
+ await incomingStreamData.stream.close();
187
+ return { message: buffer, peer: incomingStreamData.connection.remotePeer };
188
+ }
189
+ handleNewConnection(peerId) {
190
+ if (this.isBootstrapPeer(peerId)) {
191
+ this.logger(`Connected to bootstrap peer ${peerId.toString()}`);
192
+ }
193
+ else {
194
+ this.logger(`Connected to transaction peer ${peerId.toString()}`);
195
+ // send the peer our current pooled transaction hashes
196
+ void this.jobQueue.put(async () => {
197
+ await this.sendTxHashesMessageToPeer(peerId);
198
+ });
199
+ }
200
+ }
201
+ async processMessage(message, peerId) {
202
+ const type = message.readUInt32BE(0);
203
+ const encodedMessage = getEncodedMessage(message);
204
+ switch (type) {
205
+ case Messages.POOLED_TRANSACTIONS:
206
+ await this.processReceivedTxs(encodedMessage, peerId);
207
+ return;
208
+ case Messages.POOLED_TRANSACTION_HASHES:
209
+ await this.processReceivedTxHashes(encodedMessage, peerId);
210
+ return;
211
+ case Messages.GET_TRANSACTIONS:
212
+ await this.processReceivedGetTransactionsRequest(encodedMessage, peerId);
213
+ return;
214
+ }
215
+ throw new Error(`Unknown message type ${type}`);
216
+ }
217
+ async processReceivedTxHashes(encodedMessage, peerId) {
218
+ try {
219
+ const txHashes = decodeTransactionHashesMessage(encodedMessage);
220
+ this.logger(`Received tx hash messages from ${peerId.toString()}`);
221
+ // we send a message requesting the transactions that we don't have from the set of received hashes
222
+ const requiredHashes = txHashes.filter(hash => !this.txPool.hasTx(hash));
223
+ if (!requiredHashes.length) {
224
+ return;
225
+ }
226
+ await this.sendGetTransactionsMessageToPeer(txHashes, peerId);
227
+ }
228
+ catch (err) {
229
+ this.logger.error(`Failed to process received tx hashes`, err);
230
+ }
231
+ }
232
+ async processReceivedGetTransactionsRequest(encodedMessage, peerId) {
233
+ try {
234
+ this.logger(`Received get txs messages from ${peerId.toString()}`);
235
+ // get the transactions in the list that we have and return them
236
+ const removeUndefined = (value) => value != undefined;
237
+ const txHashes = decodeGetTransactionsRequestMessage(encodedMessage);
238
+ const txs = txHashes.map(x => this.txPool.getTxByHash(x)).filter(removeUndefined);
239
+ if (!txs.length) {
240
+ return;
241
+ }
242
+ await this.sendTransactionsMessageToPeer(txs, peerId);
243
+ }
244
+ catch (err) {
245
+ this.logger.error(`Failed to process get txs request`, err);
246
+ }
247
+ }
248
+ async processReceivedTxs(encodedMessage, peerId) {
249
+ try {
250
+ const txs = decodeTransactionsMessage(encodedMessage);
251
+ // Could optimise here and process all txs at once
252
+ // Propagation would need to filter and send custom tx set per peer
253
+ for (const tx of txs) {
254
+ await this.processTxFromPeer(tx, peerId);
255
+ }
256
+ }
257
+ catch (err) {
258
+ this.logger.error(`Failed to process pooled transactions message`, err);
259
+ }
260
+ }
261
+ async processTxFromPeer(tx, peerId) {
262
+ const txHash = await tx.getTxHash();
263
+ const txHashString = txHash.toString();
264
+ this.knownTxLookup.addPeerForTx(peerId, txHashString);
265
+ this.logger(`Received tx ${txHashString} from peer ${peerId.toString()}`);
266
+ await this.txPool.addTxs([tx]);
267
+ this.propagateTx(tx);
268
+ }
269
+ async sendTxToPeers(tx) {
270
+ const txs = createTransactionsMessage([tx]);
271
+ const payload = new Uint8Array(txs);
272
+ const peers = this.getTxPeers();
273
+ const txHash = await tx.getTxHash();
274
+ const txHashString = txHash.toString();
275
+ for (const peer of peers) {
276
+ try {
277
+ if (this.knownTxLookup.hasPeerSeenTx(peer, txHashString)) {
278
+ this.logger(`Not sending tx ${txHashString} to peer ${peer.toString()} as they have already seen it`);
279
+ continue;
280
+ }
281
+ this.logger(`Sending tx ${txHashString} to peer ${peer.toString()}`);
282
+ await this.sendRawMessageToPeer(payload, peer);
283
+ this.knownTxLookup.addPeerForTx(peer, txHashString);
284
+ }
285
+ catch (err) {
286
+ this.logger.error(`Failed to send txs to peer ${peer.toString()}`, err);
287
+ continue;
288
+ }
289
+ }
290
+ }
291
+ async sendTxHashesMessageToPeer(peer) {
292
+ try {
293
+ const hashes = this.txPool.getAllTxHashes();
294
+ if (!hashes.length) {
295
+ return;
296
+ }
297
+ const message = createTransactionHashesMessage(hashes);
298
+ await this.sendRawMessageToPeer(new Uint8Array(message), peer);
299
+ }
300
+ catch (err) {
301
+ this.logger.error(`Failed to send tx hashes to peer ${peer.toString()}`, err);
302
+ }
303
+ }
304
+ async sendGetTransactionsMessageToPeer(hashes, peer) {
305
+ try {
306
+ const message = createGetTransactionsRequestMessage(hashes);
307
+ await this.sendRawMessageToPeer(new Uint8Array(message), peer);
308
+ }
309
+ catch (err) {
310
+ this.logger.error(`Failed to send tx request to peer ${peer.toString()}`, err);
311
+ }
312
+ }
313
+ async sendTransactionsMessageToPeer(txs, peer) {
314
+ // don't filter out any transactions based on what we think the peer has seen,
315
+ // we have been explicitly asked for these transactions
316
+ const message = createTransactionsMessage(txs);
317
+ await this.sendRawMessageToPeer(message, peer);
318
+ for (const tx of txs) {
319
+ const hash = await tx.getTxHash();
320
+ this.knownTxLookup.addPeerForTx(peer, hash.toString());
321
+ }
322
+ }
323
+ async sendRawMessageToPeer(message, peer) {
324
+ const stream = await this.node.dialProtocol(peer, this.protocolId);
325
+ await pipe([message], stream);
326
+ await stream.close();
327
+ }
328
+ getTxPeers() {
329
+ return this.node.getPeers().filter(peer => !this.isBootstrapPeer(peer));
330
+ }
331
+ isBootstrapPeer(peer) {
332
+ return this.config.bootstrapNodes.findIndex(bootstrap => bootstrap.includes(peer.toString())) != -1;
333
+ }
334
+ }
335
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGlicDJwX3NlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvc2VydmljZS9saWJwMnBfc2VydmljZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFDckQsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sdUJBQXVCLENBQUM7QUFHMUQsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBQ2hELE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSx5QkFBeUIsQ0FBQztBQUNoRCxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFJOUMsT0FBTyxFQUFjLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ3JELE9BQU8sRUFBRSxLQUFLLEVBQUUsTUFBTSxlQUFlLENBQUM7QUFDdEMsT0FBTyxFQUFFLG1CQUFtQixFQUFFLGtCQUFrQixFQUFFLGdCQUFnQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFDcEcsT0FBTyxFQUFFLEdBQUcsRUFBRSxNQUFNLGFBQWEsQ0FBQztBQUNsQyxPQUFPLEVBQUUsSUFBSSxFQUFFLE1BQU0sU0FBUyxDQUFDO0FBQy9CLE9BQU8sRUFBNEMsWUFBWSxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQ2hGLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUNoRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFJbEQsT0FBTyxFQUFFLGFBQWEsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBRS9DLE9BQU8sRUFDTCxRQUFRLEVBQ1IsbUNBQW1DLEVBQ25DLDhCQUE4QixFQUM5Qix5QkFBeUIsRUFDekIsbUNBQW1DLEVBQ25DLDhCQUE4QixFQUM5Qix5QkFBeUIsRUFDekIsaUJBQWlCLEdBQ2xCLE1BQU0sa0JBQWtCLENBQUM7QUFFMUIsTUFBTSw2QkFB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