@aztec/p2p 0.51.0 → 0.52.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 (36) hide show
  1. package/dest/client/index.d.ts.map +1 -1
  2. package/dest/client/index.js +1 -1
  3. package/dest/client/p2p_client.d.ts +6 -0
  4. package/dest/client/p2p_client.d.ts.map +1 -1
  5. package/dest/client/p2p_client.js +6 -1
  6. package/dest/mocks/index.d.ts +28 -0
  7. package/dest/mocks/index.d.ts.map +1 -0
  8. package/dest/mocks/index.js +78 -0
  9. package/dest/service/dummy_service.d.ts +12 -0
  10. package/dest/service/dummy_service.d.ts.map +1 -1
  11. package/dest/service/dummy_service.js +14 -1
  12. package/dest/service/libp2p_service.d.ts +24 -1
  13. package/dest/service/libp2p_service.d.ts.map +1 -1
  14. package/dest/service/libp2p_service.js +52 -4
  15. package/dest/service/reqresp/handlers.d.ts +12 -2
  16. package/dest/service/reqresp/handlers.d.ts.map +1 -1
  17. package/dest/service/reqresp/handlers.js +13 -3
  18. package/dest/service/reqresp/interface.d.ts +56 -8
  19. package/dest/service/reqresp/interface.d.ts.map +1 -1
  20. package/dest/service/reqresp/interface.js +57 -8
  21. package/dest/service/reqresp/reqresp.d.ts +16 -4
  22. package/dest/service/reqresp/reqresp.d.ts.map +1 -1
  23. package/dest/service/reqresp/reqresp.js +22 -14
  24. package/dest/service/service.d.ts +9 -0
  25. package/dest/service/service.d.ts.map +1 -1
  26. package/dest/service/service.js +1 -1
  27. package/package.json +6 -6
  28. package/src/client/index.ts +2 -0
  29. package/src/client/p2p_client.ts +12 -0
  30. package/src/mocks/index.ts +101 -0
  31. package/src/service/dummy_service.ts +18 -0
  32. package/src/service/libp2p_service.ts +75 -1
  33. package/src/service/reqresp/handlers.ts +14 -4
  34. package/src/service/reqresp/interface.ts +95 -8
  35. package/src/service/reqresp/reqresp.ts +36 -19
  36. package/src/service/service.ts +14 -0
@@ -6,6 +6,7 @@ import {
6
6
  TopicType,
7
7
  TopicTypeMap,
8
8
  Tx,
9
+ TxHash,
9
10
  } from '@aztec/circuit-types';
10
11
  import { createDebugLogger } from '@aztec/foundation/log';
11
12
  import { SerialQueue } from '@aztec/foundation/queue';
@@ -30,6 +31,18 @@ import { type TxPool } from '../tx_pool/index.js';
30
31
  import { convertToMultiaddr } from '../util.js';
31
32
  import { AztecDatastore } from './data_store.js';
32
33
  import { PeerManager } from './peer_manager.js';
34
+ import { pingHandler, statusHandler } from './reqresp/handlers.js';
35
+ import {
36
+ DEFAULT_SUB_PROTOCOL_HANDLERS,
37
+ PING_PROTOCOL,
38
+ type ReqRespSubProtocol,
39
+ type ReqRespSubProtocolHandlers,
40
+ STATUS_PROTOCOL,
41
+ type SubProtocolMap,
42
+ TX_REQ_PROTOCOL,
43
+ subProtocolMap,
44
+ } from './reqresp/interface.js';
45
+ import { ReqResp } from './reqresp/reqresp.js';
33
46
  import type { P2PService, PeerDiscoveryService } from './service.js';
34
47
 
35
48
  export interface PubSubLibp2p extends Libp2p {
@@ -61,6 +74,14 @@ export class LibP2PService implements P2PService {
61
74
  private peerManager: PeerManager;
62
75
  private discoveryRunningPromise?: RunningPromise;
63
76
 
77
+ // Request and response sub service
78
+ private reqresp: ReqResp;
79
+
80
+ /**
81
+ * Callback for when a block is received from a peer.
82
+ * @param block - The block received from the peer.
83
+ * @returns The attestation for the block, if any.
84
+ */
64
85
  private blockReceivedCallback: (block: BlockProposal) => Promise<BlockAttestation | undefined>;
65
86
 
66
87
  constructor(
@@ -69,9 +90,11 @@ export class LibP2PService implements P2PService {
69
90
  private peerDiscoveryService: PeerDiscoveryService,
70
91
  private txPool: TxPool,
71
92
  private attestationPool: AttestationPool,
93
+ private requestResponseHandlers: ReqRespSubProtocolHandlers = DEFAULT_SUB_PROTOCOL_HANDLERS,
72
94
  private logger = createDebugLogger('aztec:libp2p_service'),
73
95
  ) {
74
96
  this.peerManager = new PeerManager(node, peerDiscoveryService, config, logger);
97
+ this.reqresp = new ReqResp(node);
75
98
 
76
99
  this.blockReceivedCallback = (block: BlockProposal): Promise<BlockAttestation | undefined> => {
77
100
  this.logger.verbose(
@@ -124,6 +147,7 @@ export class LibP2PService implements P2PService {
124
147
  this.peerManager.discover();
125
148
  }, this.config.peerCheckIntervalMS);
126
149
  this.discoveryRunningPromise.start();
150
+ await this.reqresp.start(this.requestResponseHandlers);
127
151
  }
128
152
 
129
153
  /**
@@ -140,6 +164,9 @@ export class LibP2PService implements P2PService {
140
164
  this.logger.debug('Stopping LibP2P...');
141
165
  await this.stopLibP2P();
142
166
  this.logger.info('LibP2P service stopped');
167
+ this.logger.debug('Stopping request response service...');
168
+ await this.reqresp.stop();
169
+ this.logger.debug('Request response service stopped...');
143
170
  }
144
171
 
145
172
  /**
@@ -206,9 +233,56 @@ export class LibP2PService implements P2PService {
206
233
  },
207
234
  });
208
235
 
209
- return new LibP2PService(config, node, peerDiscoveryService, txPool, attestationPool);
236
+ // Create request response protocol handlers
237
+ /**
238
+ * Handler for tx requests
239
+ * @param msg - the tx request message
240
+ * @returns the tx response message
241
+ */
242
+ const txHandler = (msg: Buffer): Promise<Uint8Array> => {
243
+ const txHash = TxHash.fromBuffer(msg);
244
+ const foundTx = txPool.getTxByHash(txHash);
245
+ const asUint8Array = Uint8Array.from(foundTx ? foundTx.toBuffer() : Buffer.alloc(0));
246
+ return Promise.resolve(asUint8Array);
247
+ };
248
+
249
+ const requestResponseHandlers = {
250
+ [PING_PROTOCOL]: pingHandler,
251
+ [STATUS_PROTOCOL]: statusHandler,
252
+ [TX_REQ_PROTOCOL]: txHandler,
253
+ };
254
+
255
+ return new LibP2PService(config, node, peerDiscoveryService, txPool, attestationPool, requestResponseHandlers);
256
+ }
257
+
258
+ /**
259
+ * Send Request via the ReqResp service
260
+ * The subprotocol defined will determine the request and response types
261
+ *
262
+ * See the subProtocolMap for the mapping of subprotocols to request/response types in `interface.ts`
263
+ *
264
+ * @param protocol The request response protocol to use
265
+ * @param request The request type to send
266
+ * @returns
267
+ */
268
+ async sendRequest<SubProtocol extends ReqRespSubProtocol>(
269
+ protocol: SubProtocol,
270
+ request: InstanceType<SubProtocolMap[SubProtocol]['request']>,
271
+ ): Promise<InstanceType<SubProtocolMap[SubProtocol]['response']> | undefined> {
272
+ const pair = subProtocolMap[protocol];
273
+
274
+ const res = await this.reqresp.sendRequest(protocol, request.toBuffer());
275
+ if (!res) {
276
+ return undefined;
277
+ }
278
+
279
+ return pair.response.fromBuffer(res!);
210
280
  }
211
281
 
282
+ /**
283
+ * Get the ENR of the node
284
+ * @returns The ENR of the node
285
+ */
212
286
  public getEnr(): ENR | undefined {
213
287
  return this.peerDiscoveryService.getEnr();
214
288
  }
@@ -1,7 +1,17 @@
1
- export function pingHandler(_msg: any) {
2
- return Uint8Array.from(Buffer.from('pong'));
1
+ /**
2
+ * Handles the ping request.
3
+ * @param _msg - The ping request message.
4
+ * @returns A resolved promise with the pong response.
5
+ */
6
+ export function pingHandler(_msg: any): Promise<Uint8Array> {
7
+ return Promise.resolve(Uint8Array.from(Buffer.from('pong')));
3
8
  }
4
9
 
5
- export function statusHandler(_msg: any) {
6
- return Uint8Array.from(Buffer.from('ok'));
10
+ /**
11
+ * Handles the status request.
12
+ * @param _msg - The status request message.
13
+ * @returns A resolved promise with the ok response.
14
+ */
15
+ export function statusHandler(_msg: any): Promise<Uint8Array> {
16
+ return Promise.resolve(Uint8Array.from(Buffer.from('ok')));
7
17
  }
@@ -1,13 +1,100 @@
1
- export enum ReqRespType {
2
- Status = 'status',
3
- Ping = 'ping',
4
- /** Ask peers for specific transactions */
5
- TxsByHash = 'txs_by_hash',
6
- }
1
+ import { Tx, TxHash } from '@aztec/circuit-types';
7
2
 
3
+ /*
4
+ * Request Response Sub Protocols
5
+ */
8
6
  export const PING_PROTOCOL = '/aztec/ping/0.1.0';
9
7
  export const STATUS_PROTOCOL = '/aztec/status/0.1.0';
8
+ export const TX_REQ_PROTOCOL = '/aztec/tx_req/0.1.0';
9
+
10
+ // Sum type for sub protocols
11
+ export type ReqRespSubProtocol = typeof PING_PROTOCOL | typeof STATUS_PROTOCOL | typeof TX_REQ_PROTOCOL;
12
+
13
+ /**
14
+ * A handler for a sub protocol
15
+ * The message will arrive as a buffer, and the handler must return a buffer
16
+ */
17
+ export type ReqRespSubProtocolHandler = (msg: Buffer) => Promise<Uint8Array>;
18
+
19
+ /**
20
+ * A type mapping from supprotocol to it's handling funciton
21
+ */
22
+ export type ReqRespSubProtocolHandlers = Record<ReqRespSubProtocol, ReqRespSubProtocolHandler>;
23
+
24
+ /**
25
+ * Sub protocol map determines the request and response types for each
26
+ * Req Resp protocol
27
+ */
28
+ export type SubProtocolMap = {
29
+ [S in ReqRespSubProtocol]: RequestResponsePair<any, any>;
30
+ };
31
+
32
+ /**
33
+ * Default handler for unimplemented sub protocols, this SHOULD be overwritten
34
+ * by the service, but is provided as a fallback
35
+ */
36
+ const defaultHandler = (_msg: any): Promise<Uint8Array> => {
37
+ return Promise.resolve(Uint8Array.from(Buffer.from('unimplemented')));
38
+ };
10
39
 
11
- export type SubProtocol = typeof PING_PROTOCOL | typeof STATUS_PROTOCOL;
40
+ /**
41
+ * Default sub protocol handlers - this SHOULD be overwritten by the service,
42
+ */
43
+ export const DEFAULT_SUB_PROTOCOL_HANDLERS: ReqRespSubProtocolHandlers = {
44
+ [PING_PROTOCOL]: defaultHandler,
45
+ [STATUS_PROTOCOL]: defaultHandler,
46
+ [TX_REQ_PROTOCOL]: defaultHandler,
47
+ };
48
+
49
+ /**
50
+ * The Request Response Pair interface defines the methods that each
51
+ * request response pair must implement
52
+ */
53
+ interface RequestResponsePair<Req, Res> {
54
+ request: new (...args: any[]) => Req;
55
+ /**
56
+ * The response must implement the static fromBuffer method (generic serialisation)
57
+ */
58
+ response: {
59
+ new (...args: any[]): Res;
60
+ fromBuffer(buffer: Buffer): Res;
61
+ };
62
+ }
63
+
64
+ /**
65
+ * RequestableBuffer is a wrapper around a buffer that allows it to be
66
+ * used in generic request response protocols
67
+ *
68
+ * An instance of the RequestResponsePair defined above
69
+ */
70
+ export class RequestableBuffer {
71
+ constructor(public buffer: Buffer) {}
72
+
73
+ toBuffer() {
74
+ return this.buffer;
75
+ }
76
+
77
+ static fromBuffer(buffer: Buffer) {
78
+ return new RequestableBuffer(buffer);
79
+ }
80
+ }
12
81
 
13
- export type SubProtocolHandler = (msg: string) => Uint8Array;
82
+ /**
83
+ * A mapping from each protocol to their request and response types
84
+ * This defines the request and response types for each sub protocol, used primarily
85
+ * as a type rather than an object
86
+ */
87
+ export const subProtocolMap: SubProtocolMap = {
88
+ [PING_PROTOCOL]: {
89
+ request: RequestableBuffer,
90
+ response: RequestableBuffer,
91
+ },
92
+ [STATUS_PROTOCOL]: {
93
+ request: RequestableBuffer,
94
+ response: RequestableBuffer,
95
+ },
96
+ [TX_REQ_PROTOCOL]: {
97
+ request: TxHash,
98
+ response: Tx,
99
+ },
100
+ };
@@ -6,22 +6,30 @@ import { pipe } from 'it-pipe';
6
6
  import { type Libp2p } from 'libp2p';
7
7
  import { type Uint8ArrayList } from 'uint8arraylist';
8
8
 
9
- import { pingHandler, statusHandler } from './handlers.js';
10
- import { PING_PROTOCOL, STATUS_PROTOCOL, type SubProtocol, type SubProtocolHandler } from './interface.js';
9
+ import {
10
+ DEFAULT_SUB_PROTOCOL_HANDLERS,
11
+ type ReqRespSubProtocol,
12
+ type ReqRespSubProtocolHandlers,
13
+ } from './interface.js';
11
14
 
12
15
  /**
13
- * A mapping from a protocol to a handler function
16
+ * The Request Response Service
17
+ *
18
+ * It allows nodes to request specific information from their peers, its use case covers recovering
19
+ * information that was missed during a syncronisation or a gossip event.
20
+ *
21
+ * This service implements the request response sub protocol, it is heavily inspired from
22
+ * ethereum implementations of the same name.
23
+ *
24
+ * see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/p2p-interface.md#the-reqresp-domain
14
25
  */
15
- const SUB_PROTOCOL_HANDLERS: Record<SubProtocol, SubProtocolHandler> = {
16
- [PING_PROTOCOL]: pingHandler,
17
- [STATUS_PROTOCOL]: statusHandler,
18
- };
19
-
20
26
  export class ReqResp {
21
27
  protected readonly logger: Logger;
22
28
 
23
29
  private abortController: AbortController = new AbortController();
24
30
 
31
+ private subProtocolHandlers: ReqRespSubProtocolHandlers = DEFAULT_SUB_PROTOCOL_HANDLERS;
32
+
25
33
  constructor(protected readonly libp2p: Libp2p) {
26
34
  this.logger = createDebugLogger('aztec:p2p:reqresp');
27
35
  }
@@ -29,10 +37,11 @@ export class ReqResp {
29
37
  /**
30
38
  * Start the reqresp service
31
39
  */
32
- async start() {
40
+ async start(subProtocolHandlers: ReqRespSubProtocolHandlers) {
41
+ this.subProtocolHandlers = subProtocolHandlers;
33
42
  // Register all protocol handlers
34
- for (const subProtocol of Object.keys(SUB_PROTOCOL_HANDLERS)) {
35
- await this.libp2p.handle(subProtocol, this.streamHandler.bind(this, subProtocol as SubProtocol));
43
+ for (const subProtocol of Object.keys(this.subProtocolHandlers)) {
44
+ await this.libp2p.handle(subProtocol, this.streamHandler.bind(this, subProtocol as ReqRespSubProtocol));
36
45
  }
37
46
  }
38
47
 
@@ -41,7 +50,7 @@ export class ReqResp {
41
50
  */
42
51
  async stop() {
43
52
  // Unregister all handlers
44
- for (const protocol of Object.keys(SUB_PROTOCOL_HANDLERS)) {
53
+ for (const protocol of Object.keys(this.subProtocolHandlers)) {
45
54
  await this.libp2p.unhandle(protocol);
46
55
  }
47
56
  await this.libp2p.stop();
@@ -55,7 +64,7 @@ export class ReqResp {
55
64
  * @param payload - The payload to send
56
65
  * @returns - The response from the peer, otherwise undefined
57
66
  */
58
- async sendRequest(subProtocol: SubProtocol, payload: Buffer): Promise<Buffer | undefined> {
67
+ async sendRequest(subProtocol: ReqRespSubProtocol, payload: Buffer): Promise<Buffer | undefined> {
59
68
  // Get active peers
60
69
  const peers = this.libp2p.getPeers();
61
70
 
@@ -64,7 +73,8 @@ export class ReqResp {
64
73
  const response = await this.sendRequestToPeer(peer, subProtocol, payload);
65
74
 
66
75
  // If we get a response, return it, otherwise we iterate onto the next peer
67
- if (response) {
76
+ // We do not consider it a success if we have an empty buffer
77
+ if (response && response.length > 0) {
68
78
  return response;
69
79
  }
70
80
  }
@@ -79,7 +89,11 @@ export class ReqResp {
79
89
  * @param payload - The payload to send
80
90
  * @returns If the request is successful, the response is returned, otherwise undefined
81
91
  */
82
- async sendRequestToPeer(peerId: PeerId, subProtocol: SubProtocol, payload: Buffer): Promise<Buffer | undefined> {
92
+ async sendRequestToPeer(
93
+ peerId: PeerId,
94
+ subProtocol: ReqRespSubProtocol,
95
+ payload: Buffer,
96
+ ): Promise<Buffer | undefined> {
83
97
  try {
84
98
  const stream = await this.libp2p.dialProtocol(peerId, subProtocol);
85
99
 
@@ -109,14 +123,17 @@ export class ReqResp {
109
123
  *
110
124
  * @param param0 - The incoming stream data
111
125
  */
112
- private async streamHandler(protocol: SubProtocol, { stream }: IncomingStreamData) {
126
+ private async streamHandler(protocol: ReqRespSubProtocol, { stream }: IncomingStreamData) {
127
+ // Store a reference to from this for the async generator
128
+ const handler = this.subProtocolHandlers[protocol];
129
+
113
130
  try {
114
131
  await pipe(
115
132
  stream,
116
- async function* (source) {
133
+ async function* (source: any) {
117
134
  for await (const chunkList of source) {
118
- const msg = Buffer.from(chunkList.subarray()).toString();
119
- yield SUB_PROTOCOL_HANDLERS[protocol](msg);
135
+ const msg = Buffer.from(chunkList.subarray());
136
+ yield handler(msg);
120
137
  }
121
138
  },
122
139
  stream,
@@ -4,6 +4,8 @@ import type { ENR } from '@chainsafe/enr';
4
4
  import type { PeerId } from '@libp2p/interface';
5
5
  import type EventEmitter from 'events';
6
6
 
7
+ import { type ReqRespSubProtocol, type SubProtocolMap } from './reqresp/interface.js';
8
+
7
9
  export enum PeerDiscoveryState {
8
10
  RUNNING = 'running',
9
11
  STOPPED = 'stopped',
@@ -31,6 +33,18 @@ export interface P2PService {
31
33
  */
32
34
  propagate<T extends Gossipable>(message: T): void;
33
35
 
36
+ /**
37
+ * Request information from peers via the request response protocol.
38
+ *
39
+ * @param protocol - The request response protocol to use
40
+ * @param request - The request type, corresponding to the protocol
41
+ * @returns The response type, corresponding to the protocol
42
+ */
43
+ sendRequest<Protocol extends ReqRespSubProtocol>(
44
+ protocol: Protocol,
45
+ request: InstanceType<SubProtocolMap[Protocol]['request']>,
46
+ ): Promise<InstanceType<SubProtocolMap[Protocol]['response']> | undefined>;
47
+
34
48
  // Leaky abstraction: fix https://github.com/AztecProtocol/aztec-packages/issues/7963
35
49
  registerBlockReceivedCallback(callback: (block: BlockProposal) => Promise<BlockAttestation>): void;
36
50