@aztec/p2p 0.16.4 → 0.16.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/bootstrap/bootstrap.ts +0 -116
- package/src/client/index.ts +0 -14
- package/src/client/mocks.ts +0 -87
- package/src/client/p2p_client.ts +0 -306
- package/src/config.ts +0 -113
- package/src/index.ts +0 -5
- package/src/service/dummy_service.ts +0 -36
- package/src/service/index.ts +0 -2
- package/src/service/known_txs.ts +0 -56
- package/src/service/libp2p_service.ts +0 -404
- package/src/service/service.ts +0 -30
- package/src/service/tx_messages.ts +0 -211
- package/src/tx_pool/index.ts +0 -2
- package/src/tx_pool/memory_tx_pool.ts +0 -86
- package/src/tx_pool/tx_pool.ts +0 -44
package/src/service/known_txs.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { PeerId } from '@libp2p/interface-peer-id';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Keeps a record of which Peers have 'seen' which transactions.
|
|
5
|
-
*/
|
|
6
|
-
export class KnownTxLookup {
|
|
7
|
-
private lookup: { [key: string]: { [key: string]: boolean } } = {};
|
|
8
|
-
|
|
9
|
-
constructor() {}
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Inform this lookup that a peer has 'seen' a transaction.
|
|
13
|
-
* @param peerId - The peerId of the peer that has 'seen' the transaction.
|
|
14
|
-
* @param txHash - The thHash of the 'seen' transaction.
|
|
15
|
-
*/
|
|
16
|
-
public addPeerForTx(peerId: PeerId, txHash: string) {
|
|
17
|
-
const peerIdAsString = peerId.toString();
|
|
18
|
-
const existingLookup = this.lookup[txHash];
|
|
19
|
-
if (existingLookup === undefined) {
|
|
20
|
-
const newLookup: { [key: string]: boolean } = {};
|
|
21
|
-
newLookup[peerIdAsString] = true;
|
|
22
|
-
this.lookup[txHash] = newLookup;
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
existingLookup[peerIdAsString] = true;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
* Determine if a peer has 'seen' a transaction.
|
|
30
|
-
* @param peerId - The peerId of the peer.
|
|
31
|
-
* @param txHash - The thHash of the transaction.
|
|
32
|
-
* @returns A boolean indicating if the transaction has been 'seen' by the peer.
|
|
33
|
-
*/
|
|
34
|
-
public hasPeerSeenTx(peerId: PeerId, txHash: string) {
|
|
35
|
-
const existingLookup = this.lookup[txHash];
|
|
36
|
-
if (existingLookup === undefined) {
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
const peerIdAsString = peerId.toString();
|
|
40
|
-
return !!existingLookup[peerIdAsString];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Updates the lookup from the result of settled txs
|
|
45
|
-
* These txs will be cleared out of the lookup.
|
|
46
|
-
* It is possible that some txs could still be gossiped for a
|
|
47
|
-
* short period of time meaning they come back into this lookup
|
|
48
|
-
* but this should be infrequent and cause no undesirable effects
|
|
49
|
-
* @param txHashes - The hashes of the newly settled transactions
|
|
50
|
-
*/
|
|
51
|
-
public handleSettledTxs(txHashes: string[]) {
|
|
52
|
-
for (const txHash of txHashes) {
|
|
53
|
-
delete this.lookup[txHash];
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
@@ -1,404 +0,0 @@
|
|
|
1
|
-
import { SerialQueue } from '@aztec/foundation/fifo';
|
|
2
|
-
import { createDebugLogger } from '@aztec/foundation/log';
|
|
3
|
-
import { Tx, TxHash } from '@aztec/types';
|
|
4
|
-
|
|
5
|
-
import { noise } from '@chainsafe/libp2p-noise';
|
|
6
|
-
import { yamux } from '@chainsafe/libp2p-yamux';
|
|
7
|
-
import { bootstrap } from '@libp2p/bootstrap';
|
|
8
|
-
import type { ServiceMap } from '@libp2p/interface-libp2p';
|
|
9
|
-
import { PeerId } from '@libp2p/interface-peer-id';
|
|
10
|
-
import { IncomingStreamData } from '@libp2p/interface/stream-handler';
|
|
11
|
-
import { DualKadDHT, kadDHT } from '@libp2p/kad-dht';
|
|
12
|
-
import { mplex } from '@libp2p/mplex';
|
|
13
|
-
import { createFromJSON, createSecp256k1PeerId, exportToProtobuf } from '@libp2p/peer-id-factory';
|
|
14
|
-
import { tcp } from '@libp2p/tcp';
|
|
15
|
-
import { pipe } from 'it-pipe';
|
|
16
|
-
import { Libp2p, Libp2pOptions, ServiceFactoryMap, createLibp2p } from 'libp2p';
|
|
17
|
-
import { identifyService } from 'libp2p/identify';
|
|
18
|
-
|
|
19
|
-
import { P2PConfig } from '../config.js';
|
|
20
|
-
import { TxPool } from '../tx_pool/index.js';
|
|
21
|
-
import { KnownTxLookup } from './known_txs.js';
|
|
22
|
-
import { P2PService } from './service.js';
|
|
23
|
-
import {
|
|
24
|
-
Messages,
|
|
25
|
-
createGetTransactionsRequestMessage,
|
|
26
|
-
createTransactionHashesMessage,
|
|
27
|
-
createTransactionsMessage,
|
|
28
|
-
decodeGetTransactionsRequestMessage,
|
|
29
|
-
decodeTransactionHashesMessage,
|
|
30
|
-
decodeTransactionsMessage,
|
|
31
|
-
getEncodedMessage,
|
|
32
|
-
} from './tx_messages.js';
|
|
33
|
-
|
|
34
|
-
const INITIAL_PEER_REFRESH_INTERVAL = 20000;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Create a libp2p peer ID from the private key if provided, otherwise creates a new random ID.
|
|
38
|
-
* @param privateKey - Optional peer ID private key as hex string
|
|
39
|
-
* @returns The peer ID.
|
|
40
|
-
*/
|
|
41
|
-
export async function createLibP2PPeerId(privateKey?: string) {
|
|
42
|
-
if (!privateKey?.length) {
|
|
43
|
-
return await createSecp256k1PeerId();
|
|
44
|
-
}
|
|
45
|
-
const base64 = Buffer.from(privateKey, 'hex').toString('base64');
|
|
46
|
-
return await createFromJSON({
|
|
47
|
-
id: '',
|
|
48
|
-
privKey: base64,
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Exports a given peer id to a string representation.
|
|
54
|
-
* @param peerId - The peerId instance to be converted.
|
|
55
|
-
* @returns The peer id as a string.
|
|
56
|
-
*/
|
|
57
|
-
export function exportLibP2PPeerIdToString(peerId: PeerId) {
|
|
58
|
-
return Buffer.from(exportToProtobuf(peerId)).toString('hex');
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Lib P2P implementation of the P2PService interface.
|
|
63
|
-
*/
|
|
64
|
-
export class LibP2PService implements P2PService {
|
|
65
|
-
private jobQueue: SerialQueue = new SerialQueue();
|
|
66
|
-
private timeout: NodeJS.Timer | undefined = undefined;
|
|
67
|
-
private knownTxLookup: KnownTxLookup = new KnownTxLookup();
|
|
68
|
-
constructor(
|
|
69
|
-
private config: P2PConfig,
|
|
70
|
-
private node: Libp2p,
|
|
71
|
-
private protocolId: string,
|
|
72
|
-
private txPool: TxPool,
|
|
73
|
-
private logger = createDebugLogger('aztec:libp2p_service'),
|
|
74
|
-
) {}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Starts the LibP2P service.
|
|
78
|
-
* @returns An empty promise.
|
|
79
|
-
*/
|
|
80
|
-
public async start() {
|
|
81
|
-
if (this.node.isStarted()) {
|
|
82
|
-
throw new Error('P2P service already started');
|
|
83
|
-
}
|
|
84
|
-
const { enableNat, tcpListenIp, tcpListenPort, announceHostname, announcePort } = this.config;
|
|
85
|
-
this.logger(`Starting P2P node on ${tcpListenIp}:${tcpListenPort}`);
|
|
86
|
-
if (announceHostname) {
|
|
87
|
-
this.logger(`Announcing at ${announceHostname}/tcp/${announcePort ?? tcpListenPort}`);
|
|
88
|
-
}
|
|
89
|
-
if (enableNat) {
|
|
90
|
-
this.logger(`Enabling NAT in libp2p module`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
this.node.addEventListener('peer:discovery', evt => {
|
|
94
|
-
const peerId = evt.detail.id;
|
|
95
|
-
if (this.isBootstrapPeer(peerId)) {
|
|
96
|
-
this.logger(`Discovered bootstrap peer ${peerId.toString()}`);
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
this.node.addEventListener('peer:connect', evt => {
|
|
101
|
-
const peerId = evt.detail;
|
|
102
|
-
this.handleNewConnection(peerId);
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
this.node.addEventListener('peer:disconnect', evt => {
|
|
106
|
-
const peerId = evt.detail;
|
|
107
|
-
if (this.isBootstrapPeer(peerId)) {
|
|
108
|
-
this.logger(`Disconnect from bootstrap peer ${peerId.toString()}`);
|
|
109
|
-
} else {
|
|
110
|
-
this.logger(`Disconnected from transaction peer ${peerId.toString()}`);
|
|
111
|
-
}
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
this.jobQueue.start();
|
|
115
|
-
await this.node.start();
|
|
116
|
-
await this.node.handle(this.protocolId, (incoming: IncomingStreamData) =>
|
|
117
|
-
this.jobQueue.put(() => Promise.resolve(this.handleProtocolDial(incoming))),
|
|
118
|
-
);
|
|
119
|
-
const dht = this.node.services['kadDHT'] as DualKadDHT;
|
|
120
|
-
this.logger(`Started P2P client as ${await dht.getMode()} with Peer ID ${this.node.peerId.toString()}`);
|
|
121
|
-
this.timeout = setTimeout(async () => {
|
|
122
|
-
this.logger(`Refreshing routing table...`);
|
|
123
|
-
await dht.refreshRoutingTable();
|
|
124
|
-
}, INITIAL_PEER_REFRESH_INTERVAL);
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Stops the LibP2P service.
|
|
129
|
-
* @returns An empty promise.
|
|
130
|
-
*/
|
|
131
|
-
public async stop() {
|
|
132
|
-
if (this.timeout) {
|
|
133
|
-
clearTimeout(this.timeout);
|
|
134
|
-
}
|
|
135
|
-
await this.jobQueue.end();
|
|
136
|
-
await this.node.stop();
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Creates an instance of the LibP2P service.
|
|
141
|
-
* @param config - The configuration to use when creating the service.
|
|
142
|
-
* @param txPool - The transaction pool to be accessed by the service.
|
|
143
|
-
* @returns The new service.
|
|
144
|
-
*/
|
|
145
|
-
public static async new(config: P2PConfig, txPool: TxPool) {
|
|
146
|
-
const {
|
|
147
|
-
tcpListenIp,
|
|
148
|
-
tcpListenPort,
|
|
149
|
-
announceHostname,
|
|
150
|
-
announcePort,
|
|
151
|
-
clientKADRouting,
|
|
152
|
-
minPeerCount,
|
|
153
|
-
maxPeerCount,
|
|
154
|
-
peerIdPrivateKey,
|
|
155
|
-
} = config;
|
|
156
|
-
const peerId = await createLibP2PPeerId(peerIdPrivateKey);
|
|
157
|
-
|
|
158
|
-
const opts: Libp2pOptions<ServiceMap> = {
|
|
159
|
-
start: false,
|
|
160
|
-
peerId,
|
|
161
|
-
addresses: {
|
|
162
|
-
listen: [`/ip4/${tcpListenIp}/tcp/${tcpListenPort}`],
|
|
163
|
-
announce: announceHostname ? [`${announceHostname}/tcp/${announcePort ?? tcpListenPort}`] : [],
|
|
164
|
-
},
|
|
165
|
-
transports: [tcp()],
|
|
166
|
-
streamMuxers: [yamux(), mplex()],
|
|
167
|
-
connectionEncryption: [noise()],
|
|
168
|
-
connectionManager: {
|
|
169
|
-
minConnections: minPeerCount,
|
|
170
|
-
maxConnections: maxPeerCount,
|
|
171
|
-
},
|
|
172
|
-
peerDiscovery: [
|
|
173
|
-
bootstrap({
|
|
174
|
-
list: config.bootstrapNodes,
|
|
175
|
-
}),
|
|
176
|
-
],
|
|
177
|
-
};
|
|
178
|
-
|
|
179
|
-
const services: ServiceFactoryMap = {
|
|
180
|
-
identify: identifyService({
|
|
181
|
-
protocolPrefix: 'aztec',
|
|
182
|
-
}),
|
|
183
|
-
kadDHT: kadDHT({
|
|
184
|
-
protocolPrefix: 'aztec',
|
|
185
|
-
clientMode: clientKADRouting,
|
|
186
|
-
}),
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// The autonat service seems quite problematic in that using it seems to cause a lot of attempts
|
|
190
|
-
// to dial ephemeral ports. I suspect that it works better if you can get the uPNPnat service to
|
|
191
|
-
// work as then you would have a permanent port to be dialled.
|
|
192
|
-
// Alas, I struggled to get this to work reliably either. I find there is a race between the
|
|
193
|
-
// service that reads our listener addresses and the uPnP service.
|
|
194
|
-
// The result being the uPnP service can't find an address to use for the port forward.
|
|
195
|
-
// Need to investigate further.
|
|
196
|
-
// if (enableNat) {
|
|
197
|
-
// services.autoNAT = autoNATService({
|
|
198
|
-
// protocolPrefix: 'aztec',
|
|
199
|
-
// });
|
|
200
|
-
// services.uPnPNAT = uPnPNATService();
|
|
201
|
-
// }
|
|
202
|
-
|
|
203
|
-
const node = await createLibp2p({
|
|
204
|
-
...opts,
|
|
205
|
-
services,
|
|
206
|
-
});
|
|
207
|
-
const protocolId = config.transactionProtocol;
|
|
208
|
-
return new LibP2PService(config, node, protocolId, txPool);
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Propagates the provided transaction to peers.
|
|
213
|
-
* @param tx - The transaction to propagate.
|
|
214
|
-
*/
|
|
215
|
-
public propagateTx(tx: Tx): void {
|
|
216
|
-
void this.jobQueue.put(() => Promise.resolve(this.sendTxToPeers(tx)));
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Handles the settling of a new batch of transactions.
|
|
221
|
-
* @param txHashes - The hashes of the newly settled transactions.
|
|
222
|
-
*/
|
|
223
|
-
public settledTxs(txHashes: TxHash[]): void {
|
|
224
|
-
this.knownTxLookup.handleSettledTxs(txHashes.map(x => x.toString()));
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
private async handleProtocolDial(incomingStreamData: IncomingStreamData) {
|
|
228
|
-
try {
|
|
229
|
-
const { message, peer } = await this.consumeInboundStream(incomingStreamData);
|
|
230
|
-
if (!message.length) {
|
|
231
|
-
this.logger(`Ignoring 0 byte message from peer${peer.toString()}`);
|
|
232
|
-
}
|
|
233
|
-
await this.processMessage(message, peer);
|
|
234
|
-
} catch (err) {
|
|
235
|
-
this.logger.error(
|
|
236
|
-
`Failed to handle received message from peer ${incomingStreamData.connection.remotePeer.toString()}`,
|
|
237
|
-
err,
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
private async consumeInboundStream(incomingStreamData: IncomingStreamData) {
|
|
243
|
-
let buffer = Buffer.alloc(0);
|
|
244
|
-
await pipe(incomingStreamData.stream, async source => {
|
|
245
|
-
for await (const msg of source) {
|
|
246
|
-
const payload = msg.subarray();
|
|
247
|
-
buffer = Buffer.concat([buffer, Buffer.from(payload)]);
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
await incomingStreamData.stream.close();
|
|
251
|
-
return { message: buffer, peer: incomingStreamData.connection.remotePeer };
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private handleNewConnection(peerId: PeerId) {
|
|
255
|
-
if (this.isBootstrapPeer(peerId)) {
|
|
256
|
-
this.logger(`Connected to bootstrap peer ${peerId.toString()}`);
|
|
257
|
-
} else {
|
|
258
|
-
this.logger(`Connected to transaction peer ${peerId.toString()}`);
|
|
259
|
-
// send the peer our current pooled transaction hashes
|
|
260
|
-
void this.jobQueue.put(async () => {
|
|
261
|
-
await this.sendTxHashesMessageToPeer(peerId);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
private async processMessage(message: Buffer, peerId: PeerId) {
|
|
267
|
-
const type = message.readUInt32BE(0);
|
|
268
|
-
const encodedMessage = getEncodedMessage(message);
|
|
269
|
-
switch (type) {
|
|
270
|
-
case Messages.POOLED_TRANSACTIONS:
|
|
271
|
-
await this.processReceivedTxs(encodedMessage, peerId);
|
|
272
|
-
return;
|
|
273
|
-
case Messages.POOLED_TRANSACTION_HASHES:
|
|
274
|
-
await this.processReceivedTxHashes(encodedMessage, peerId);
|
|
275
|
-
return;
|
|
276
|
-
case Messages.GET_TRANSACTIONS:
|
|
277
|
-
await this.processReceivedGetTransactionsRequest(encodedMessage, peerId);
|
|
278
|
-
return;
|
|
279
|
-
}
|
|
280
|
-
throw new Error(`Unknown message type ${type}`);
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
private async processReceivedTxHashes(encodedMessage: Buffer, peerId: PeerId) {
|
|
284
|
-
try {
|
|
285
|
-
const txHashes = decodeTransactionHashesMessage(encodedMessage);
|
|
286
|
-
this.logger(`Received tx hash messages from ${peerId.toString()}`);
|
|
287
|
-
// we send a message requesting the transactions that we don't have from the set of received hashes
|
|
288
|
-
const requiredHashes = txHashes.filter(hash => !this.txPool.hasTx(hash));
|
|
289
|
-
if (!requiredHashes.length) {
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
await this.sendGetTransactionsMessageToPeer(txHashes, peerId);
|
|
293
|
-
} catch (err) {
|
|
294
|
-
this.logger.error(`Failed to process received tx hashes`, err);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
private async processReceivedGetTransactionsRequest(encodedMessage: Buffer, peerId: PeerId) {
|
|
299
|
-
try {
|
|
300
|
-
this.logger(`Received get txs messages from ${peerId.toString()}`);
|
|
301
|
-
// get the transactions in the list that we have and return them
|
|
302
|
-
const removeUndefined = <S>(value: S | undefined): value is S => value != undefined;
|
|
303
|
-
const txHashes = decodeGetTransactionsRequestMessage(encodedMessage);
|
|
304
|
-
const txs = txHashes.map(x => this.txPool.getTxByHash(x)).filter(removeUndefined);
|
|
305
|
-
if (!txs.length) {
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
await this.sendTransactionsMessageToPeer(txs, peerId);
|
|
309
|
-
} catch (err) {
|
|
310
|
-
this.logger.error(`Failed to process get txs request`, err);
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
private async processReceivedTxs(encodedMessage: Buffer, peerId: PeerId) {
|
|
315
|
-
try {
|
|
316
|
-
const txs = decodeTransactionsMessage(encodedMessage);
|
|
317
|
-
// Could optimize here and process all txs at once
|
|
318
|
-
// Propagation would need to filter and send custom tx set per peer
|
|
319
|
-
for (const tx of txs) {
|
|
320
|
-
await this.processTxFromPeer(tx, peerId);
|
|
321
|
-
}
|
|
322
|
-
} catch (err) {
|
|
323
|
-
this.logger.error(`Failed to process pooled transactions message`, err);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
private async processTxFromPeer(tx: Tx, peerId: PeerId): Promise<void> {
|
|
328
|
-
const txHash = await tx.getTxHash();
|
|
329
|
-
const txHashString = txHash.toString();
|
|
330
|
-
this.knownTxLookup.addPeerForTx(peerId, txHashString);
|
|
331
|
-
this.logger(`Received tx ${txHashString} from peer ${peerId.toString()}`);
|
|
332
|
-
await this.txPool.addTxs([tx]);
|
|
333
|
-
this.propagateTx(tx);
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
private async sendTxToPeers(tx: Tx) {
|
|
337
|
-
const txs = createTransactionsMessage([tx]);
|
|
338
|
-
const payload = new Uint8Array(txs);
|
|
339
|
-
const peers = this.getTxPeers();
|
|
340
|
-
const txHash = await tx.getTxHash();
|
|
341
|
-
const txHashString = txHash.toString();
|
|
342
|
-
for (const peer of peers) {
|
|
343
|
-
try {
|
|
344
|
-
if (this.knownTxLookup.hasPeerSeenTx(peer, txHashString)) {
|
|
345
|
-
this.logger(`Not sending tx ${txHashString} to peer ${peer.toString()} as they have already seen it`);
|
|
346
|
-
continue;
|
|
347
|
-
}
|
|
348
|
-
this.logger(`Sending tx ${txHashString} to peer ${peer.toString()}`);
|
|
349
|
-
await this.sendRawMessageToPeer(payload, peer);
|
|
350
|
-
this.knownTxLookup.addPeerForTx(peer, txHashString);
|
|
351
|
-
} catch (err) {
|
|
352
|
-
this.logger.error(`Failed to send txs to peer ${peer.toString()}`, err);
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
private async sendTxHashesMessageToPeer(peer: PeerId) {
|
|
359
|
-
try {
|
|
360
|
-
const hashes = this.txPool.getAllTxHashes();
|
|
361
|
-
if (!hashes.length) {
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
const message = createTransactionHashesMessage(hashes);
|
|
365
|
-
await this.sendRawMessageToPeer(new Uint8Array(message), peer);
|
|
366
|
-
} catch (err) {
|
|
367
|
-
this.logger.error(`Failed to send tx hashes to peer ${peer.toString()}`, err);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
private async sendGetTransactionsMessageToPeer(hashes: TxHash[], peer: PeerId) {
|
|
372
|
-
try {
|
|
373
|
-
const message = createGetTransactionsRequestMessage(hashes);
|
|
374
|
-
await this.sendRawMessageToPeer(new Uint8Array(message), peer);
|
|
375
|
-
} catch (err) {
|
|
376
|
-
this.logger.error(`Failed to send tx request to peer ${peer.toString()}`, err);
|
|
377
|
-
}
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
private async sendTransactionsMessageToPeer(txs: Tx[], peer: PeerId) {
|
|
381
|
-
// don't filter out any transactions based on what we think the peer has seen,
|
|
382
|
-
// we have been explicitly asked for these transactions
|
|
383
|
-
const message = createTransactionsMessage(txs);
|
|
384
|
-
await this.sendRawMessageToPeer(message, peer);
|
|
385
|
-
for (const tx of txs) {
|
|
386
|
-
const hash = await tx.getTxHash();
|
|
387
|
-
this.knownTxLookup.addPeerForTx(peer, hash.toString());
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
private async sendRawMessageToPeer(message: Uint8Array, peer: PeerId) {
|
|
392
|
-
const stream = await this.node.dialProtocol(peer, this.protocolId);
|
|
393
|
-
await pipe([message], stream);
|
|
394
|
-
await stream.close();
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
private getTxPeers() {
|
|
398
|
-
return this.node.getPeers().filter(peer => !this.isBootstrapPeer(peer));
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
private isBootstrapPeer(peer: PeerId) {
|
|
402
|
-
return this.config.bootstrapNodes.findIndex(bootstrap => bootstrap.includes(peer.toString())) != -1;
|
|
403
|
-
}
|
|
404
|
-
}
|
package/src/service/service.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { Tx, TxHash } from '@aztec/types';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The interface for a P2P service implementation.
|
|
5
|
-
*/
|
|
6
|
-
export interface P2PService {
|
|
7
|
-
/**
|
|
8
|
-
* Starts the service.
|
|
9
|
-
* @returns An empty promise.
|
|
10
|
-
*/
|
|
11
|
-
start(): Promise<void>;
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Stops the service.
|
|
15
|
-
* @returns An empty promise.
|
|
16
|
-
*/
|
|
17
|
-
stop(): Promise<void>;
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Called to have the given transaction propagated through the P2P network.
|
|
21
|
-
* @param tx - The transaction to be propagated.
|
|
22
|
-
*/
|
|
23
|
-
propagateTx(tx: Tx): void;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Called upon receipt of settled transactions.
|
|
27
|
-
* @param txHashes - The hashes of the settled transactions.
|
|
28
|
-
*/
|
|
29
|
-
settledTxs(txHashes: TxHash[]): void;
|
|
30
|
-
}
|
|
@@ -1,211 +0,0 @@
|
|
|
1
|
-
import { KernelCircuitPublicInputsFinal, MAX_NEW_CONTRACTS_PER_TX, Proof, PublicCallRequest } from '@aztec/circuits.js';
|
|
2
|
-
import { Tuple, numToUInt32BE } from '@aztec/foundation/serialize';
|
|
3
|
-
import { ExtendedContractData, Tx, TxHash, TxL2Logs } from '@aztec/types';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Enumeration of P2P message types.
|
|
7
|
-
*/
|
|
8
|
-
export enum Messages {
|
|
9
|
-
POOLED_TRANSACTIONS = 1,
|
|
10
|
-
POOLED_TRANSACTION_HASHES = 2,
|
|
11
|
-
GET_TRANSACTIONS = 3,
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Create a P2P message from the message type and message data.
|
|
16
|
-
* @param type - The type of the message.
|
|
17
|
-
* @param messageData - The binary message data.
|
|
18
|
-
* @returns The encoded message.
|
|
19
|
-
*/
|
|
20
|
-
export function createMessage(type: Messages, messageData: Buffer) {
|
|
21
|
-
return Buffer.concat([numToUInt32BE(type), messageData]);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Create a POOLED_TRANSACTIONS message from an array of transactions.
|
|
26
|
-
* @param txs - The transactions to encoded into a message.
|
|
27
|
-
* @returns The encoded message.
|
|
28
|
-
*/
|
|
29
|
-
export function createTransactionsMessage(txs: Tx[]) {
|
|
30
|
-
const messageData = txs.map(toTxMessage);
|
|
31
|
-
return createMessage(Messages.POOLED_TRANSACTIONS, Buffer.concat(messageData));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Decode a POOLED_TRANSACTIONS message into the original transaction objects.
|
|
36
|
-
* @param message - The binary message to be decoded.
|
|
37
|
-
* @returns - The array of transactions originally encoded into the message.
|
|
38
|
-
*/
|
|
39
|
-
export function decodeTransactionsMessage(message: Buffer) {
|
|
40
|
-
const lengthSize = 4;
|
|
41
|
-
let offset = 0;
|
|
42
|
-
const txs: Tx[] = [];
|
|
43
|
-
while (offset < message.length) {
|
|
44
|
-
const dataSize = message.readUInt32BE(offset);
|
|
45
|
-
const totalSizeOfMessage = lengthSize + dataSize;
|
|
46
|
-
txs.push(fromTxMessage(message.subarray(offset, offset + totalSizeOfMessage)));
|
|
47
|
-
offset += totalSizeOfMessage;
|
|
48
|
-
}
|
|
49
|
-
return txs;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Create a POOLED_TRANSACTION_HASHES message.
|
|
54
|
-
* @param hashes - The transaction hashes to be sent.
|
|
55
|
-
* @returns The encoded message.
|
|
56
|
-
*/
|
|
57
|
-
export function createTransactionHashesMessage(hashes: TxHash[]) {
|
|
58
|
-
const messageData = hashes.map(x => x.buffer);
|
|
59
|
-
return createMessage(Messages.POOLED_TRANSACTION_HASHES, Buffer.concat(messageData));
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Decode a POOLED_TRANSACTION_HASHESs message ito the original transaction hash objects.
|
|
64
|
-
* @param message - The binary message to be decoded.
|
|
65
|
-
* @returns - The array of transaction hashes originally encoded into the message.
|
|
66
|
-
*/
|
|
67
|
-
export function decodeTransactionHashesMessage(message: Buffer) {
|
|
68
|
-
let offset = 0;
|
|
69
|
-
const txHashes: TxHash[] = [];
|
|
70
|
-
while (offset < message.length) {
|
|
71
|
-
const slice = message.subarray(offset, offset + TxHash.SIZE);
|
|
72
|
-
if (slice.length < TxHash.SIZE) {
|
|
73
|
-
throw new Error(`Invalid message size when processing transaction hashes message`);
|
|
74
|
-
}
|
|
75
|
-
txHashes.push(new TxHash(slice));
|
|
76
|
-
offset += TxHash.SIZE;
|
|
77
|
-
}
|
|
78
|
-
return txHashes;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Create a GET_TRANSACTIONS message from an array of transaction hashes.
|
|
83
|
-
* @param hashes - The hashes of the transactions to be requested.
|
|
84
|
-
* @returns The encoded message.
|
|
85
|
-
*/
|
|
86
|
-
export function createGetTransactionsRequestMessage(hashes: TxHash[]) {
|
|
87
|
-
const messageData = hashes.map(x => x.buffer);
|
|
88
|
-
return createMessage(Messages.GET_TRANSACTIONS, Buffer.concat(messageData));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Decode a GET_TRANSACTIONS message into the original transaction hash objects.
|
|
93
|
-
* @param message - The binary message to be decoded.
|
|
94
|
-
* @returns - The array of transaction hashes originally encoded into the message.
|
|
95
|
-
*/
|
|
96
|
-
export function decodeGetTransactionsRequestMessage(message: Buffer) {
|
|
97
|
-
// for the time being this payload is effectively the same as the POOLED_TRANSACTION_HASHES message
|
|
98
|
-
return decodeTransactionHashesMessage(message);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Decode the message type from a received message.
|
|
103
|
-
* @param message - The received message.
|
|
104
|
-
* @returns The decoded MessageType.
|
|
105
|
-
*/
|
|
106
|
-
export function decodeMessageType(message: Buffer) {
|
|
107
|
-
return message.readUInt32BE(0);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Return the encoded message (minus the header) from received message buffer.
|
|
112
|
-
* @param message - The complete received message.
|
|
113
|
-
* @returns The encoded message, without the header.
|
|
114
|
-
*/
|
|
115
|
-
export function getEncodedMessage(message: Buffer) {
|
|
116
|
-
return message.subarray(4);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Creates a tx 'message' for sending to a peer.
|
|
121
|
-
* @param tx - The transaction to convert to a message.
|
|
122
|
-
* @returns - The message.
|
|
123
|
-
*/
|
|
124
|
-
export function toTxMessage(tx: Tx): Buffer {
|
|
125
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
126
|
-
const createMessageComponent = (obj?: { toBuffer: () => Buffer }) => {
|
|
127
|
-
if (!obj) {
|
|
128
|
-
// specify a length of 0 bytes
|
|
129
|
-
return numToUInt32BE(0);
|
|
130
|
-
}
|
|
131
|
-
const buffer = obj.toBuffer();
|
|
132
|
-
return Buffer.concat([numToUInt32BE(buffer.length), buffer]);
|
|
133
|
-
};
|
|
134
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
135
|
-
const createMessageComponents = (obj?: { toBuffer: () => Buffer }[]) => {
|
|
136
|
-
if (!obj || !obj.length) {
|
|
137
|
-
// specify a length of 0 bytes
|
|
138
|
-
return numToUInt32BE(0);
|
|
139
|
-
}
|
|
140
|
-
const allComponents = Buffer.concat(obj.map(createMessageComponent));
|
|
141
|
-
return Buffer.concat([numToUInt32BE(obj.length), allComponents]);
|
|
142
|
-
};
|
|
143
|
-
const messageBuffer = Buffer.concat([
|
|
144
|
-
createMessageComponent(tx.data),
|
|
145
|
-
createMessageComponent(tx.proof),
|
|
146
|
-
createMessageComponent(tx.encryptedLogs),
|
|
147
|
-
createMessageComponent(tx.unencryptedLogs),
|
|
148
|
-
createMessageComponents(tx.enqueuedPublicFunctionCalls),
|
|
149
|
-
createMessageComponents(tx.newContracts),
|
|
150
|
-
]);
|
|
151
|
-
const messageLength = numToUInt32BE(messageBuffer.length);
|
|
152
|
-
return Buffer.concat([messageLength, messageBuffer]);
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Reproduces a transaction from a transaction 'message'
|
|
157
|
-
* @param buffer - The message buffer to convert to a tx.
|
|
158
|
-
* @returns - The reproduced transaction.
|
|
159
|
-
*/
|
|
160
|
-
export function fromTxMessage(buffer: Buffer): Tx {
|
|
161
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
162
|
-
const toObject = <T>(objectBuffer: Buffer, factory: { fromBuffer: (b: Buffer) => T }) => {
|
|
163
|
-
const objectSize = objectBuffer.readUint32BE(0);
|
|
164
|
-
return {
|
|
165
|
-
remainingData: objectBuffer.subarray(objectSize + 4),
|
|
166
|
-
obj: objectSize === 0 ? undefined : factory.fromBuffer(objectBuffer.subarray(4, objectSize + 4)),
|
|
167
|
-
};
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
171
|
-
const toObjectArray = <T>(objectBuffer: Buffer, factory: { fromBuffer: (b: Buffer) => T }) => {
|
|
172
|
-
const output: T[] = [];
|
|
173
|
-
const numItems = objectBuffer.readUint32BE(0);
|
|
174
|
-
let workingBuffer = objectBuffer.subarray(4);
|
|
175
|
-
for (let i = 0; i < numItems; i++) {
|
|
176
|
-
const obj = toObject<T>(workingBuffer, factory);
|
|
177
|
-
workingBuffer = obj.remainingData;
|
|
178
|
-
if (obj !== undefined) {
|
|
179
|
-
output.push(obj.obj!);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return {
|
|
183
|
-
remainingData: workingBuffer,
|
|
184
|
-
objects: output,
|
|
185
|
-
};
|
|
186
|
-
};
|
|
187
|
-
// this is the opposite of the 'toMessage' function
|
|
188
|
-
// so the first 4 bytes is the complete length, skip it
|
|
189
|
-
const publicInputs = toObject(buffer.subarray(4), KernelCircuitPublicInputsFinal);
|
|
190
|
-
const proof = toObject(publicInputs.remainingData, Proof);
|
|
191
|
-
|
|
192
|
-
const encryptedLogs = toObject(proof.remainingData, TxL2Logs);
|
|
193
|
-
if (!encryptedLogs.obj) {
|
|
194
|
-
encryptedLogs.obj = new TxL2Logs([]);
|
|
195
|
-
}
|
|
196
|
-
const unencryptedLogs = toObject(encryptedLogs.remainingData, TxL2Logs);
|
|
197
|
-
if (!unencryptedLogs.obj) {
|
|
198
|
-
unencryptedLogs.obj = new TxL2Logs([]);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const publicCalls = toObjectArray(unencryptedLogs.remainingData, PublicCallRequest);
|
|
202
|
-
const newContracts = toObjectArray(publicCalls.remainingData, ExtendedContractData);
|
|
203
|
-
return new Tx(
|
|
204
|
-
publicInputs.obj!,
|
|
205
|
-
proof.obj!,
|
|
206
|
-
encryptedLogs.obj,
|
|
207
|
-
unencryptedLogs.obj,
|
|
208
|
-
publicCalls.objects,
|
|
209
|
-
newContracts.objects as Tuple<ExtendedContractData, typeof MAX_NEW_CONTRACTS_PER_TX>,
|
|
210
|
-
);
|
|
211
|
-
}
|
package/src/tx_pool/index.ts
DELETED