Skip to content

Commit c643a54

Browse files
authored
feat(p2p): snappy compress p2p messages (#10417)
fixes: #10348
1 parent c246aba commit c643a54

File tree

9 files changed

+199
-49
lines changed

9 files changed

+199
-49
lines changed

yarn-project/p2p/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@
9191
"libp2p": "1.5.0",
9292
"semver": "^7.6.0",
9393
"sha3": "^2.1.4",
94+
"snappy": "^7.2.2",
9495
"tslib": "^2.4.0",
9596
"xxhash-wasm": "^1.1.0"
9697
},

yarn-project/p2p/src/mocks/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ export type ReqRespNode = {
148148
export const MOCK_SUB_PROTOCOL_HANDLERS: ReqRespSubProtocolHandlers = {
149149
[PING_PROTOCOL]: pingHandler,
150150
[STATUS_PROTOCOL]: statusHandler,
151-
[TX_REQ_PROTOCOL]: (_msg: any) => Promise.resolve(Uint8Array.from(Buffer.from('tx'))),
151+
[TX_REQ_PROTOCOL]: (_msg: any) => Promise.resolve(Buffer.from('tx')),
152152
};
153153

154154
// By default, all requests are valid

yarn-project/p2p/src/service/encoding.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import { sha256 } from '@aztec/foundation/crypto';
33

44
import { type RPC } from '@chainsafe/libp2p-gossipsub/message';
5+
import { type DataTransform } from '@chainsafe/libp2p-gossipsub/types';
56
import { type Message } from '@libp2p/interface';
7+
import { compressSync, uncompressSync } from 'snappy';
68
import xxhashFactory from 'xxhash-wasm';
79

810
// Load WASM
@@ -46,3 +48,14 @@ export function getMsgIdFn(message: Message) {
4648
const vec = [Buffer.from(topic), message.data];
4749
return sha256(Buffer.concat(vec)).subarray(0, 20);
4850
}
51+
52+
export class SnappyTransform implements DataTransform {
53+
inboundTransform(_topicStr: string, data: Uint8Array): Uint8Array {
54+
const uncompressed = Buffer.from(uncompressSync(Buffer.from(data), { asBuffer: true }));
55+
return new Uint8Array(uncompressed);
56+
}
57+
58+
outboundTransform(_topicStr: string, data: Uint8Array): Uint8Array {
59+
return new Uint8Array(compressSync(Buffer.from(data)));
60+
}
61+
}

yarn-project/p2p/src/service/libp2p_service.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ import {
4343
} from '../tx_validator/index.js';
4444
import { type PubSubLibp2p, convertToMultiaddr } from '../util.js';
4545
import { AztecDatastore } from './data_store.js';
46-
import { fastMsgIdFn, getMsgIdFn, msgIdToStrFn } from './encoding.js';
46+
import { SnappyTransform, fastMsgIdFn, getMsgIdFn, msgIdToStrFn } from './encoding.js';
4747
import { PeerManager } from './peer_manager.js';
4848
import { PeerErrorSeverity } from './peer_scoring.js';
4949
import { pingHandler, statusHandler } from './reqresp/handlers.js';
@@ -246,6 +246,7 @@ export class LibP2PService extends WithTracer implements P2PService {
246246
msgIdFn: getMsgIdFn,
247247
msgIdToStrFn: msgIdToStrFn,
248248
fastMsgIdFn: fastMsgIdFn,
249+
dataTransform: new SnappyTransform(),
249250
metricsRegister: otelMetricsAdapter,
250251
metricsTopicStrToLabel: metricsTopicStrToLabels(),
251252
scoreParams: createPeerScoreParams({
@@ -282,11 +283,11 @@ export class LibP2PService extends WithTracer implements P2PService {
282283
* @param msg - the tx request message
283284
* @returns the tx response message
284285
*/
285-
const txHandler = (msg: Buffer): Promise<Uint8Array> => {
286+
const txHandler = (msg: Buffer): Promise<Buffer> => {
286287
const txHash = TxHash.fromBuffer(msg);
287288
const foundTx = mempools.txPool.getTxByHash(txHash);
288-
const asUint8Array = Uint8Array.from(foundTx ? foundTx.toBuffer() : Buffer.alloc(0));
289-
return Promise.resolve(asUint8Array);
289+
const buf = foundTx ? foundTx.toBuffer() : Buffer.alloc(0);
290+
return Promise.resolve(buf);
290291
};
291292

292293
const requestResponseHandlers = {

yarn-project/p2p/src/service/reqresp/handlers.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
* @param _msg - The ping request message.
44
* @returns A resolved promise with the pong response.
55
*/
6-
export function pingHandler(_msg: any): Promise<Uint8Array> {
7-
return Promise.resolve(Uint8Array.from(Buffer.from('pong')));
6+
export function pingHandler(_msg: any): Promise<Buffer> {
7+
return Promise.resolve(Buffer.from('pong'));
88
}
99

1010
/**
1111
* Handles the status request.
1212
* @param _msg - The status request message.
1313
* @returns A resolved promise with the ok response.
1414
*/
15-
export function statusHandler(_msg: any): Promise<Uint8Array> {
16-
return Promise.resolve(Uint8Array.from(Buffer.from('ok')));
15+
export function statusHandler(_msg: any): Promise<Buffer> {
16+
return Promise.resolve(Buffer.from('ok'));
1717
}

yarn-project/p2p/src/service/reqresp/interface.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export type ReqRespSubProtocol = typeof PING_PROTOCOL | typeof STATUS_PROTOCOL |
1616
* A handler for a sub protocol
1717
* The message will arrive as a buffer, and the handler must return a buffer
1818
*/
19-
export type ReqRespSubProtocolHandler = (msg: Buffer) => Promise<Uint8Array>;
19+
export type ReqRespSubProtocolHandler = (msg: Buffer) => Promise<Buffer>;
2020

2121
/**
2222
* A type mapping from supprotocol to it's rate limits
@@ -83,8 +83,8 @@ export type SubProtocolMap = {
8383
* Default handler for unimplemented sub protocols, this SHOULD be overwritten
8484
* by the service, but is provided as a fallback
8585
*/
86-
const defaultHandler = (_msg: any): Promise<Uint8Array> => {
87-
return Promise.resolve(Uint8Array.from(Buffer.from('unimplemented')));
86+
const defaultHandler = (_msg: any): Promise<Buffer> => {
87+
return Promise.resolve(Buffer.from('unimplemented'));
8888
};
8989

9090
/**

yarn-project/p2p/src/service/reqresp/reqresp.test.ts

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { CollectiveReqRespTimeoutError, IndiviualReqRespTimeoutError } from '../
88
import {
99
MOCK_SUB_PROTOCOL_HANDLERS,
1010
MOCK_SUB_PROTOCOL_VALIDATORS,
11+
type ReqRespNode,
1112
connectToPeers,
1213
createNodes,
1314
startNodes,
@@ -23,15 +24,22 @@ const PING_REQUEST = RequestableBuffer.fromBuffer(Buffer.from('ping'));
2324
// and ask for specific data that they missed via the traditional gossip protocol.
2425
describe('ReqResp', () => {
2526
let peerManager: MockProxy<PeerManager>;
27+
let nodes: ReqRespNode[];
2628

2729
beforeEach(() => {
2830
peerManager = mock<PeerManager>();
2931
});
3032

33+
afterEach(async () => {
34+
if (nodes) {
35+
await stopNodes(nodes as ReqRespNode[]);
36+
}
37+
});
38+
3139
it('Should perform a ping request', async () => {
3240
// Create two nodes
3341
// They need to discover each other
34-
const nodes = await createNodes(peerManager, 2);
42+
nodes = await createNodes(peerManager, 2);
3543
const { req: pinger } = nodes[0];
3644

3745
await startNodes(nodes);
@@ -45,12 +53,10 @@ describe('ReqResp', () => {
4553

4654
await sleep(500);
4755
expect(res?.toBuffer().toString('utf-8')).toEqual('pong');
48-
49-
await stopNodes(nodes);
5056
});
5157

5258
it('Should handle gracefully if a peer connected peer is offline', async () => {
53-
const nodes = await createNodes(peerManager, 2);
59+
nodes = await createNodes(peerManager, 2);
5460

5561
const { req: pinger } = nodes[0];
5662
const { req: ponger } = nodes[1];
@@ -66,12 +72,10 @@ describe('ReqResp', () => {
6672
const res = await pinger.sendRequest(PING_PROTOCOL, PING_REQUEST);
6773

6874
expect(res).toBeUndefined();
69-
70-
await stopNodes(nodes);
7175
});
7276

7377
it('Should request from a later peer if other peers are offline', async () => {
74-
const nodes = await createNodes(peerManager, 4);
78+
nodes = await createNodes(peerManager, 4);
7579

7680
await startNodes(nodes);
7781
await sleep(500);
@@ -86,12 +90,10 @@ describe('ReqResp', () => {
8690
const res = await nodes[0].req.sendRequest(PING_PROTOCOL, PING_REQUEST);
8791

8892
expect(res?.toBuffer().toString('utf-8')).toEqual('pong');
89-
90-
await stopNodes(nodes);
9193
});
9294

9395
it('Should hit a rate limit if too many requests are made in quick succession', async () => {
94-
const nodes = await createNodes(peerManager, 2);
96+
nodes = await createNodes(peerManager, 2);
9597

9698
await startNodes(nodes);
9799

@@ -110,8 +112,6 @@ describe('ReqResp', () => {
110112
// Make sure the error message is logged
111113
const errorMessage = `Rate limit exceeded for ${PING_PROTOCOL} from ${nodes[0].p2p.peerId.toString()}`;
112114
expect(loggerSpy).toHaveBeenCalledWith(errorMessage);
113-
114-
await stopNodes(nodes);
115115
});
116116

117117
describe('TX REQ PROTOCOL', () => {
@@ -120,15 +120,15 @@ describe('ReqResp', () => {
120120
const txHash = tx.getTxHash();
121121

122122
const protocolHandlers = MOCK_SUB_PROTOCOL_HANDLERS;
123-
protocolHandlers[TX_REQ_PROTOCOL] = (message: Buffer): Promise<Uint8Array> => {
123+
protocolHandlers[TX_REQ_PROTOCOL] = (message: Buffer): Promise<Buffer> => {
124124
const receivedHash = TxHash.fromBuffer(message);
125125
if (txHash.equals(receivedHash)) {
126-
return Promise.resolve(Uint8Array.from(tx.toBuffer()));
126+
return Promise.resolve(tx.toBuffer());
127127
}
128-
return Promise.resolve(Uint8Array.from(Buffer.from('')));
128+
return Promise.resolve(Buffer.from(''));
129129
};
130130

131-
const nodes = await createNodes(peerManager, 2);
131+
nodes = await createNodes(peerManager, 2);
132132

133133
await startNodes(nodes, protocolHandlers);
134134
await sleep(500);
@@ -137,8 +137,6 @@ describe('ReqResp', () => {
137137

138138
const res = await nodes[0].req.sendRequest(TX_REQ_PROTOCOL, txHash);
139139
expect(res).toEqual(tx);
140-
141-
await stopNodes(nodes);
142140
});
143141

144142
it('Does not crash if tx hash returns undefined', async () => {
@@ -147,11 +145,11 @@ describe('ReqResp', () => {
147145

148146
const protocolHandlers = MOCK_SUB_PROTOCOL_HANDLERS;
149147
// Return nothing
150-
protocolHandlers[TX_REQ_PROTOCOL] = (_message: Buffer): Promise<Uint8Array> => {
151-
return Promise.resolve(Uint8Array.from(Buffer.from('')));
148+
protocolHandlers[TX_REQ_PROTOCOL] = (_message: Buffer): Promise<Buffer> => {
149+
return Promise.resolve(Buffer.from(''));
152150
};
153151

154-
const nodes = await createNodes(peerManager, 2);
152+
nodes = await createNodes(peerManager, 2);
155153

156154
await startNodes(nodes, protocolHandlers);
157155
await sleep(500);
@@ -160,12 +158,10 @@ describe('ReqResp', () => {
160158

161159
const res = await nodes[0].req.sendRequest(TX_REQ_PROTOCOL, txHash);
162160
expect(res).toBeUndefined();
163-
164-
await stopNodes(nodes);
165161
});
166162

167163
it('Should hit individual timeout if nothing is returned over the stream', async () => {
168-
const nodes = await createNodes(peerManager, 2);
164+
nodes = await createNodes(peerManager, 2);
169165

170166
await startNodes(nodes);
171167

@@ -197,12 +193,10 @@ describe('ReqResp', () => {
197193
}),
198194
PeerErrorSeverity.HighToleranceError,
199195
);
200-
201-
await stopNodes(nodes);
202196
});
203197

204198
it('Should hit collective timeout if nothing is returned over the stream from multiple peers', async () => {
205-
const nodes = await createNodes(peerManager, 4);
199+
nodes = await createNodes(peerManager, 4);
206200

207201
await startNodes(nodes);
208202

@@ -226,8 +220,6 @@ describe('ReqResp', () => {
226220
// Make sure the error message is logged
227221
const errorMessage = `${new CollectiveReqRespTimeoutError().message} | subProtocol: ${TX_REQ_PROTOCOL}`;
228222
expect(loggerSpy).toHaveBeenCalledWith(errorMessage);
229-
230-
await stopNodes(nodes);
231223
});
232224

233225
it('Should penalize peer if transaction validation fails', async () => {
@@ -236,12 +228,12 @@ describe('ReqResp', () => {
236228

237229
// Mock that the node will respond with the tx
238230
const protocolHandlers = MOCK_SUB_PROTOCOL_HANDLERS;
239-
protocolHandlers[TX_REQ_PROTOCOL] = (message: Buffer): Promise<Uint8Array> => {
231+
protocolHandlers[TX_REQ_PROTOCOL] = (message: Buffer): Promise<Buffer> => {
240232
const receivedHash = TxHash.fromBuffer(message);
241233
if (txHash.equals(receivedHash)) {
242-
return Promise.resolve(Uint8Array.from(tx.toBuffer()));
234+
return Promise.resolve(tx.toBuffer());
243235
}
244-
return Promise.resolve(Uint8Array.from(Buffer.from('')));
236+
return Promise.resolve(Buffer.from(''));
245237
};
246238

247239
// Mock that the receiving node will find that the transaction is invalid
@@ -251,7 +243,7 @@ describe('ReqResp', () => {
251243
return Promise.resolve(false);
252244
};
253245

254-
const nodes = await createNodes(peerManager, 2);
246+
nodes = await createNodes(peerManager, 2);
255247

256248
await startNodes(nodes, protocolHandlers, protocolValidators);
257249
await sleep(500);
@@ -268,8 +260,6 @@ describe('ReqResp', () => {
268260
}),
269261
PeerErrorSeverity.LowToleranceError,
270262
);
271-
272-
await stopNodes(nodes);
273263
});
274264
});
275265
});

yarn-project/p2p/src/service/reqresp/reqresp.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { executeTimeoutWithCustomError } from '@aztec/foundation/timer';
55
import { type IncomingStreamData, type PeerId, type Stream } from '@libp2p/interface';
66
import { pipe } from 'it-pipe';
77
import { type Libp2p } from 'libp2p';
8+
import { compressSync, uncompressSync } from 'snappy';
89
import { type Uint8ArrayList } from 'uint8arraylist';
910

1011
import { CollectiveReqRespTimeoutError, IndiviualReqRespTimeoutError } from '../../errors/reqresp.error.js';
@@ -31,6 +32,9 @@ import { RequestResponseRateLimiter } from './rate_limiter/rate_limiter.js';
3132
* This service implements the request response sub protocol, it is heavily inspired from
3233
* ethereum implementations of the same name.
3334
*
35+
* Note, responses get compressed in streamHandler
36+
* so they get decompressed in readMessage
37+
*
3438
* see: https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/p2p-interface.md#the-reqresp-domain
3539
*/
3640
export class ReqResp {
@@ -232,7 +236,7 @@ export class ReqResp {
232236
chunks.push(chunk.subarray());
233237
}
234238
const messageData = chunks.concat();
235-
return Buffer.concat(messageData);
239+
return uncompressSync(Buffer.concat(messageData), { asBuffer: true }) as Buffer;
236240
}
237241

238242
/**
@@ -269,7 +273,8 @@ export class ReqResp {
269273
async function* (source: any) {
270274
for await (const chunkList of source) {
271275
const msg = Buffer.from(chunkList.subarray());
272-
yield handler(msg);
276+
const response = await handler(msg);
277+
yield new Uint8Array(compressSync(response));
273278
}
274279
},
275280
stream,

0 commit comments

Comments
 (0)