@aztec/p2p 0.23.0 → 0.24.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.
@@ -0,0 +1,216 @@
1
+ import { ExtendedContractData, Tx, TxHash, TxL2Logs } from '@aztec/circuit-types';
2
+ import {
3
+ MAX_NEW_CONTRACTS_PER_TX,
4
+ PrivateKernelTailCircuitPublicInputs,
5
+ Proof,
6
+ PublicCallRequest,
7
+ } from '@aztec/circuits.js';
8
+ import { Tuple, numToUInt32BE } from '@aztec/foundation/serialize';
9
+
10
+ /**
11
+ * Enumeration of P2P message types.
12
+ */
13
+ export enum Messages {
14
+ POOLED_TRANSACTIONS = 1,
15
+ POOLED_TRANSACTION_HASHES = 2,
16
+ GET_TRANSACTIONS = 3,
17
+ }
18
+
19
+ /**
20
+ * Create a P2P message from the message type and message data.
21
+ * @param type - The type of the message.
22
+ * @param messageData - The binary message data.
23
+ * @returns The encoded message.
24
+ */
25
+ export function createMessage(type: Messages, messageData: Buffer) {
26
+ return Buffer.concat([numToUInt32BE(type), messageData]);
27
+ }
28
+
29
+ /**
30
+ * Create a POOLED_TRANSACTIONS message from an array of transactions.
31
+ * @param txs - The transactions to encoded into a message.
32
+ * @returns The encoded message.
33
+ */
34
+ export function createTransactionsMessage(txs: Tx[]) {
35
+ const messageData = txs.map(toTxMessage);
36
+ return createMessage(Messages.POOLED_TRANSACTIONS, Buffer.concat(messageData));
37
+ }
38
+
39
+ /**
40
+ * Decode a POOLED_TRANSACTIONS message into the original transaction objects.
41
+ * @param message - The binary message to be decoded.
42
+ * @returns - The array of transactions originally encoded into the message.
43
+ */
44
+ export function decodeTransactionsMessage(message: Buffer) {
45
+ const lengthSize = 4;
46
+ let offset = 0;
47
+ const txs: Tx[] = [];
48
+ while (offset < message.length) {
49
+ const dataSize = message.readUInt32BE(offset);
50
+ const totalSizeOfMessage = lengthSize + dataSize;
51
+ txs.push(fromTxMessage(message.subarray(offset, offset + totalSizeOfMessage)));
52
+ offset += totalSizeOfMessage;
53
+ }
54
+ return txs;
55
+ }
56
+
57
+ /**
58
+ * Create a POOLED_TRANSACTION_HASHES message.
59
+ * @param hashes - The transaction hashes to be sent.
60
+ * @returns The encoded message.
61
+ */
62
+ export function createTransactionHashesMessage(hashes: TxHash[]) {
63
+ const messageData = hashes.map(x => x.buffer);
64
+ return createMessage(Messages.POOLED_TRANSACTION_HASHES, Buffer.concat(messageData));
65
+ }
66
+
67
+ /**
68
+ * Decode a POOLED_TRANSACTION_HASHESs message ito the original transaction hash objects.
69
+ * @param message - The binary message to be decoded.
70
+ * @returns - The array of transaction hashes originally encoded into the message.
71
+ */
72
+ export function decodeTransactionHashesMessage(message: Buffer) {
73
+ let offset = 0;
74
+ const txHashes: TxHash[] = [];
75
+ while (offset < message.length) {
76
+ const slice = message.subarray(offset, offset + TxHash.SIZE);
77
+ if (slice.length < TxHash.SIZE) {
78
+ throw new Error(`Invalid message size when processing transaction hashes message`);
79
+ }
80
+ txHashes.push(new TxHash(slice));
81
+ offset += TxHash.SIZE;
82
+ }
83
+ return txHashes;
84
+ }
85
+
86
+ /**
87
+ * Create a GET_TRANSACTIONS message from an array of transaction hashes.
88
+ * @param hashes - The hashes of the transactions to be requested.
89
+ * @returns The encoded message.
90
+ */
91
+ export function createGetTransactionsRequestMessage(hashes: TxHash[]) {
92
+ const messageData = hashes.map(x => x.buffer);
93
+ return createMessage(Messages.GET_TRANSACTIONS, Buffer.concat(messageData));
94
+ }
95
+
96
+ /**
97
+ * Decode a GET_TRANSACTIONS message into the original transaction hash objects.
98
+ * @param message - The binary message to be decoded.
99
+ * @returns - The array of transaction hashes originally encoded into the message.
100
+ */
101
+ export function decodeGetTransactionsRequestMessage(message: Buffer) {
102
+ // for the time being this payload is effectively the same as the POOLED_TRANSACTION_HASHES message
103
+ return decodeTransactionHashesMessage(message);
104
+ }
105
+
106
+ /**
107
+ * Decode the message type from a received message.
108
+ * @param message - The received message.
109
+ * @returns The decoded MessageType.
110
+ */
111
+ export function decodeMessageType(message: Buffer) {
112
+ return message.readUInt32BE(0);
113
+ }
114
+
115
+ /**
116
+ * Return the encoded message (minus the header) from received message buffer.
117
+ * @param message - The complete received message.
118
+ * @returns The encoded message, without the header.
119
+ */
120
+ export function getEncodedMessage(message: Buffer) {
121
+ return message.subarray(4);
122
+ }
123
+
124
+ /**
125
+ * Creates a tx 'message' for sending to a peer.
126
+ * @param tx - The transaction to convert to a message.
127
+ * @returns - The message.
128
+ */
129
+ export function toTxMessage(tx: Tx): Buffer {
130
+ // eslint-disable-next-line jsdoc/require-jsdoc
131
+ const createMessageComponent = (obj?: { toBuffer: () => Buffer }) => {
132
+ if (!obj) {
133
+ // specify a length of 0 bytes
134
+ return numToUInt32BE(0);
135
+ }
136
+ const buffer = obj.toBuffer();
137
+ return Buffer.concat([numToUInt32BE(buffer.length), buffer]);
138
+ };
139
+ // eslint-disable-next-line jsdoc/require-jsdoc
140
+ const createMessageComponents = (obj?: { toBuffer: () => Buffer }[]) => {
141
+ if (!obj || !obj.length) {
142
+ // specify a length of 0 bytes
143
+ return numToUInt32BE(0);
144
+ }
145
+ const allComponents = Buffer.concat(obj.map(createMessageComponent));
146
+ return Buffer.concat([numToUInt32BE(obj.length), allComponents]);
147
+ };
148
+ const messageBuffer = Buffer.concat([
149
+ createMessageComponent(tx.data),
150
+ createMessageComponent(tx.proof),
151
+ createMessageComponent(tx.encryptedLogs),
152
+ createMessageComponent(tx.unencryptedLogs),
153
+ createMessageComponents(tx.enqueuedPublicFunctionCalls),
154
+ createMessageComponents(tx.newContracts),
155
+ ]);
156
+ const messageLength = numToUInt32BE(messageBuffer.length);
157
+ return Buffer.concat([messageLength, messageBuffer]);
158
+ }
159
+
160
+ /**
161
+ * Reproduces a transaction from a transaction 'message'
162
+ * @param buffer - The message buffer to convert to a tx.
163
+ * @returns - The reproduced transaction.
164
+ */
165
+ export function fromTxMessage(buffer: Buffer): Tx {
166
+ // eslint-disable-next-line jsdoc/require-jsdoc
167
+ const toObject = <T>(objectBuffer: Buffer, factory: { fromBuffer: (b: Buffer) => T }) => {
168
+ const objectSize = objectBuffer.readUint32BE(0);
169
+ return {
170
+ remainingData: objectBuffer.subarray(objectSize + 4),
171
+ obj: objectSize === 0 ? undefined : factory.fromBuffer(objectBuffer.subarray(4, objectSize + 4)),
172
+ };
173
+ };
174
+
175
+ // eslint-disable-next-line jsdoc/require-jsdoc
176
+ const toObjectArray = <T>(objectBuffer: Buffer, factory: { fromBuffer: (b: Buffer) => T }) => {
177
+ const output: T[] = [];
178
+ const numItems = objectBuffer.readUint32BE(0);
179
+ let workingBuffer = objectBuffer.subarray(4);
180
+ for (let i = 0; i < numItems; i++) {
181
+ const obj = toObject<T>(workingBuffer, factory);
182
+ workingBuffer = obj.remainingData;
183
+ if (obj !== undefined) {
184
+ output.push(obj.obj!);
185
+ }
186
+ }
187
+ return {
188
+ remainingData: workingBuffer,
189
+ objects: output,
190
+ };
191
+ };
192
+ // this is the opposite of the 'toMessage' function
193
+ // so the first 4 bytes is the complete length, skip it
194
+ const publicInputs = toObject(buffer.subarray(4), PrivateKernelTailCircuitPublicInputs);
195
+ const proof = toObject(publicInputs.remainingData, Proof);
196
+
197
+ const encryptedLogs = toObject(proof.remainingData, TxL2Logs);
198
+ if (!encryptedLogs.obj) {
199
+ encryptedLogs.obj = new TxL2Logs([]);
200
+ }
201
+ const unencryptedLogs = toObject(encryptedLogs.remainingData, TxL2Logs);
202
+ if (!unencryptedLogs.obj) {
203
+ unencryptedLogs.obj = new TxL2Logs([]);
204
+ }
205
+
206
+ const publicCalls = toObjectArray(unencryptedLogs.remainingData, PublicCallRequest);
207
+ const newContracts = toObjectArray(publicCalls.remainingData, ExtendedContractData);
208
+ return new Tx(
209
+ publicInputs.obj!,
210
+ proof.obj!,
211
+ encryptedLogs.obj,
212
+ unencryptedLogs.obj,
213
+ publicCalls.objects,
214
+ newContracts.objects as Tuple<ExtendedContractData, typeof MAX_NEW_CONTRACTS_PER_TX>,
215
+ );
216
+ }
@@ -0,0 +1,99 @@
1
+ import { Tx, TxHash } from '@aztec/circuit-types';
2
+ import { TxAddedToPoolStats } from '@aztec/circuit-types/stats';
3
+ import { Logger, createDebugLogger } from '@aztec/foundation/log';
4
+ import { AztecKVStore, AztecMap } from '@aztec/kv-store';
5
+
6
+ import { TxPool } from './tx_pool.js';
7
+
8
+ /**
9
+ * In-memory implementation of the Transaction Pool.
10
+ */
11
+ export class AztecKVTxPool implements TxPool {
12
+ #store: AztecKVStore;
13
+
14
+ /**
15
+ * Our tx pool, stored as a Map in-memory, with K: tx hash and V: the transaction.
16
+ */
17
+ #txs: AztecMap<string, Buffer>;
18
+
19
+ #log: Logger;
20
+
21
+ /**
22
+ * Class constructor for in-memory TxPool. Initiates our transaction pool as a JS Map.
23
+ * @param store - A KV store.
24
+ * @param log - A logger.
25
+ */
26
+ constructor(store: AztecKVStore, log = createDebugLogger('aztec:tx_pool')) {
27
+ this.#txs = store.openMap('txs');
28
+ this.#store = store;
29
+ this.#log = log;
30
+ }
31
+
32
+ /**
33
+ * Checks if a transaction exists in the pool and returns it.
34
+ * @param txHash - The generated tx hash.
35
+ * @returns The transaction, if found, 'undefined' otherwise.
36
+ */
37
+ public getTxByHash(txHash: TxHash): Tx | undefined {
38
+ const buffer = this.#txs.get(txHash.toString());
39
+ return buffer ? Tx.fromBuffer(buffer) : undefined;
40
+ }
41
+
42
+ /**
43
+ * Adds a list of transactions to the pool. Duplicates are ignored.
44
+ * @param txs - An array of txs to be added to the pool.
45
+ * @returns Empty promise.
46
+ */
47
+ public async addTxs(txs: Tx[]): Promise<void> {
48
+ const txHashes = await Promise.all(txs.map(tx => tx.getTxHash()));
49
+ return this.#store.transaction(() => {
50
+ for (const [i, tx] of txs.entries()) {
51
+ const txHash = txHashes[i];
52
+ this.#log.info(`Adding tx with id ${txHash.toString()}`, {
53
+ eventName: 'tx-added-to-pool',
54
+ ...tx.getStats(),
55
+ } satisfies TxAddedToPoolStats);
56
+
57
+ void this.#txs.set(txHash.toString(), tx.toBuffer());
58
+ }
59
+ });
60
+ }
61
+
62
+ /**
63
+ * Deletes transactions from the pool. Tx hashes that are not present are ignored.
64
+ * @param txHashes - An array of tx hashes to be removed from the tx pool.
65
+ * @returns The number of transactions that was deleted from the pool.
66
+ */
67
+ public deleteTxs(txHashes: TxHash[]): Promise<void> {
68
+ return this.#store.transaction(() => {
69
+ for (const hash of txHashes) {
70
+ void this.#txs.delete(hash.toString());
71
+ }
72
+ });
73
+ }
74
+
75
+ /**
76
+ * Gets all the transactions stored in the pool.
77
+ * @returns Array of tx objects in the order they were added to the pool.
78
+ */
79
+ public getAllTxs(): Tx[] {
80
+ return Array.from(this.#txs.values()).map(buffer => Tx.fromBuffer(buffer));
81
+ }
82
+
83
+ /**
84
+ * Gets the hashes of all transactions currently in the tx pool.
85
+ * @returns An array of transaction hashes found in the tx pool.
86
+ */
87
+ public getAllTxHashes(): TxHash[] {
88
+ return Array.from(this.#txs.keys()).map(x => TxHash.fromString(x));
89
+ }
90
+
91
+ /**
92
+ * Returns a boolean indicating if the transaction is present in the pool.
93
+ * @param txHash - The hash of the transaction to be queried.
94
+ * @returns True if the transaction present, false otherwise.
95
+ */
96
+ public hasTx(txHash: TxHash): boolean {
97
+ return this.#txs.has(txHash.toString());
98
+ }
99
+ }
@@ -0,0 +1,3 @@
1
+ export * from './tx_pool.js';
2
+ export * from './memory_tx_pool.js';
3
+ export * from './aztec_kv_tx_pool.js';
@@ -0,0 +1,86 @@
1
+ import { Tx, TxHash } from '@aztec/circuit-types';
2
+ import { TxAddedToPoolStats } from '@aztec/circuit-types/stats';
3
+ import { createDebugLogger } from '@aztec/foundation/log';
4
+
5
+ import { TxPool } from './tx_pool.js';
6
+
7
+ /**
8
+ * In-memory implementation of the Transaction Pool.
9
+ */
10
+ export class InMemoryTxPool implements TxPool {
11
+ /**
12
+ * Our tx pool, stored as a Map in-memory, with K: tx hash and V: the transaction.
13
+ */
14
+ private txs: Map<bigint, Tx>;
15
+
16
+ /**
17
+ * Class constructor for in-memory TxPool. Initiates our transaction pool as a JS Map.
18
+ * @param log - A logger.
19
+ */
20
+ constructor(private log = createDebugLogger('aztec:tx_pool')) {
21
+ this.txs = new Map<bigint, Tx>();
22
+ }
23
+
24
+ /**
25
+ * Checks if a transaction exists in the pool and returns it.
26
+ * @param txHash - The generated tx hash.
27
+ * @returns The transaction, if found, 'undefined' otherwise.
28
+ */
29
+ public getTxByHash(txHash: TxHash): Tx | undefined {
30
+ const result = this.txs.get(txHash.toBigInt());
31
+ return result === undefined ? undefined : Tx.clone(result);
32
+ }
33
+
34
+ /**
35
+ * Adds a list of transactions to the pool. Duplicates are ignored.
36
+ * @param txs - An array of txs to be added to the pool.
37
+ * @returns Empty promise.
38
+ */
39
+ public async addTxs(txs: Tx[]): Promise<void> {
40
+ for (const tx of txs) {
41
+ const txHash = await tx.getTxHash();
42
+ this.log(`Adding tx with id ${txHash.toString()}`, {
43
+ eventName: 'tx-added-to-pool',
44
+ ...tx.getStats(),
45
+ } satisfies TxAddedToPoolStats);
46
+ this.txs.set(txHash.toBigInt(), tx);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Deletes transactions from the pool. Tx hashes that are not present are ignored.
52
+ * @param txHashes - An array of tx hashes to be removed from the tx pool.
53
+ * @returns The number of transactions that was deleted from the pool.
54
+ */
55
+ public deleteTxs(txHashes: TxHash[]): Promise<void> {
56
+ for (const txHash of txHashes) {
57
+ this.txs.delete(txHash.toBigInt());
58
+ }
59
+ return Promise.resolve();
60
+ }
61
+
62
+ /**
63
+ * Gets all the transactions stored in the pool.
64
+ * @returns Array of tx objects in the order they were added to the pool.
65
+ */
66
+ public getAllTxs(): Tx[] {
67
+ return Array.from(this.txs.values()).map(x => Tx.clone(x));
68
+ }
69
+
70
+ /**
71
+ * Gets the hashes of all transactions currently in the tx pool.
72
+ * @returns An array of transaction hashes found in the tx pool.
73
+ */
74
+ public getAllTxHashes(): TxHash[] {
75
+ return Array.from(this.txs.keys()).map(x => TxHash.fromBigInt(x));
76
+ }
77
+
78
+ /**
79
+ * Returns a boolean indicating if the transaction is present in the pool.
80
+ * @param txHash - The hash of the transaction to be queried.
81
+ * @returns True if the transaction present, false otherwise.
82
+ */
83
+ public hasTx(txHash: TxHash): boolean {
84
+ return this.txs.has(txHash.toBigInt());
85
+ }
86
+ }
@@ -0,0 +1,44 @@
1
+ import { Tx, TxHash } from '@aztec/circuit-types';
2
+
3
+ /**
4
+ * Interface of a transaction pool. The pool includes tx requests and is kept up-to-date by a P2P client.
5
+ */
6
+ export interface TxPool {
7
+ /**
8
+ * Adds a list of transactions to the pool. Duplicates are ignored.
9
+ * @param txs - An array of txs to be added to the pool.
10
+ */
11
+ addTxs(txs: Tx[]): Promise<void>;
12
+
13
+ /**
14
+ * Checks if a transaction exists in the pool and returns it.
15
+ * @param txHash - The hash of the transaction, used as an ID.
16
+ * @returns The transaction, if found, 'undefined' otherwise.
17
+ */
18
+ getTxByHash(txHash: TxHash): Tx | undefined;
19
+
20
+ /**
21
+ * Deletes transactions from the pool. Tx hashes that are not present are ignored.
22
+ * @param txHashes - An array of tx hashes to be removed from the tx pool.
23
+ */
24
+ deleteTxs(txHashes: TxHash[]): Promise<void>;
25
+
26
+ /**
27
+ * Gets all transactions currently in the tx pool.
28
+ * @returns An array of transaction objects found in the tx pool.
29
+ */
30
+ getAllTxs(): Tx[];
31
+
32
+ /**
33
+ * Gets the hashes of all transactions currently in the tx pool.
34
+ * @returns An array of transaction hashes found in the tx pool.
35
+ */
36
+ getAllTxHashes(): TxHash[];
37
+
38
+ /**
39
+ * Returns a boolean indicating if the transaction is present in the pool.
40
+ * @param txHash - The hash of the transaction to be queried.
41
+ * @returns True if the transaction present, false otherwise.
42
+ */
43
+ hasTx(txHash: TxHash): boolean;
44
+ }
@@ -0,0 +1,59 @@
1
+ import { mockTx } from '@aztec/circuit-types';
2
+
3
+ import { TxPool } from './tx_pool.js';
4
+
5
+ /**
6
+ * Tests a TxPool implementation.
7
+ * @param getTxPool - Gets a fresh TxPool
8
+ */
9
+ export function describeTxPool(getTxPool: () => TxPool) {
10
+ let pool: TxPool;
11
+
12
+ beforeEach(() => {
13
+ pool = getTxPool();
14
+ });
15
+
16
+ it('Adds txs to the pool', async () => {
17
+ const tx1 = mockTx();
18
+
19
+ await pool.addTxs([tx1]);
20
+ const poolTx = pool.getTxByHash(await tx1.getTxHash());
21
+ expect(await poolTx!.getTxHash()).toEqual(await tx1.getTxHash());
22
+ });
23
+
24
+ it('Removes txs from the pool', async () => {
25
+ const tx1 = mockTx();
26
+
27
+ await pool.addTxs([tx1]);
28
+ await pool.deleteTxs([await tx1.getTxHash()]);
29
+
30
+ const poolTx = pool.getTxByHash(await tx1.getTxHash());
31
+ expect(poolTx).toBeFalsy();
32
+ });
33
+
34
+ it('Returns all transactions in the pool', async () => {
35
+ const tx1 = mockTx(1);
36
+ const tx2 = mockTx(2);
37
+ const tx3 = mockTx(3);
38
+
39
+ await pool.addTxs([tx1, tx2, tx3]);
40
+
41
+ const poolTxs = pool.getAllTxs();
42
+ expect(poolTxs).toHaveLength(3);
43
+ expect(poolTxs).toEqual(expect.arrayContaining([tx1, tx2, tx3]));
44
+ });
45
+
46
+ it('Returns all txHashes in the pool', async () => {
47
+ const tx1 = mockTx(1);
48
+ const tx2 = mockTx(2);
49
+ const tx3 = mockTx(3);
50
+
51
+ await pool.addTxs([tx1, tx2, tx3]);
52
+
53
+ const poolTxHashes = pool.getAllTxHashes();
54
+ expect(poolTxHashes).toHaveLength(3);
55
+ expect(poolTxHashes).toEqual(
56
+ expect.arrayContaining([await tx1.getTxHash(), await tx2.getTxHash(), await tx3.getTxHash()]),
57
+ );
58
+ });
59
+ }