Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(p2p): tx serialization cleanup #7620

Merged
merged 1 commit into from
Jul 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions yarn-project/p2p/src/service/libp2p_service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { type Tx } from '@aztec/circuit-types';
import { Tx } from '@aztec/circuit-types';
import { SerialQueue } from '@aztec/foundation/fifo';
import { createDebugLogger } from '@aztec/foundation/log';
import { RunningPromise } from '@aztec/foundation/running-promise';
Expand All @@ -21,7 +21,7 @@ import { convertToMultiaddr } from '../util.js';
import { AztecDatastore } from './data_store.js';
import { PeerManager } from './peer_manager.js';
import type { P2PService, PeerDiscoveryService } from './service.js';
import { AztecTxMessageCreator, fromTxMessage } from './tx_messages.js';
import { AztecTxMessageCreator } from './tx_messages.js';

export interface PubSubLibp2p extends Libp2p {
services: {
Expand Down Expand Up @@ -239,7 +239,7 @@ export class LibP2PService implements P2PService {
return;
}

const tx = fromTxMessage(Buffer.from(data));
const tx = Tx.fromBuffer(Buffer.from(data));
await this.processTxFromPeer(tx);
}

Expand Down
8 changes: 3 additions & 5 deletions yarn-project/p2p/src/service/tx_messages.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import { type Tx, mockTx } from '@aztec/circuit-types';
import { Tx, mockTx } from '@aztec/circuit-types';

import { expect } from '@jest/globals';

import { fromTxMessage, toTxMessage } from './tx_messages.js';

const verifyTx = (actual: Tx, expected: Tx) => {
expect(actual.data.toBuffer()).toEqual(expected.data.toBuffer());
expect(actual.clientIvcProof.toBuffer()).toEqual(expected.clientIvcProof.toBuffer());
Expand All @@ -13,8 +11,8 @@ const verifyTx = (actual: Tx, expected: Tx) => {
describe('Messages', () => {
it('Correctly serializes and deserializes a single private transaction', () => {
const transaction = mockTx();
const message = toTxMessage(transaction);
const decodedTransaction = fromTxMessage(message);
const message = transaction.toBuffer();
const decodedTransaction = Tx.fromBuffer(message);
verifyTx(decodedTransaction, transaction);
});
});
131 changes: 2 additions & 129 deletions yarn-project/p2p/src/service/tx_messages.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,4 @@
import {
EncryptedNoteTxL2Logs,
EncryptedTxL2Logs,
PublicExecutionRequest,
Tx,
UnencryptedTxL2Logs,
} from '@aztec/circuit-types';
import { ClientIvcProof, PrivateKernelTailCircuitPublicInputs } from '@aztec/circuits.js';
import { numToUInt32BE } from '@aztec/foundation/serialize';
import { type Tx } from '@aztec/circuit-types';

import { type SemVer } from 'semver';

Expand All @@ -19,7 +11,7 @@ export class AztecTxMessageCreator {
}

createTxMessage(tx: Tx) {
const messageData = toTxMessage(tx);
const messageData = tx.toBuffer();

return { topic: this.topic, data: messageData };
}
Expand All @@ -28,122 +20,3 @@ export class AztecTxMessageCreator {
return this.topic;
}
}

/**
* Decode a POOLED_TRANSACTIONS message into the original transaction objects.
* @param message - The binary message to be decoded.
* @returns - The array of transactions originally encoded into the message.
*/
export function decodeTransactionsMessage(message: Buffer) {
const lengthSize = 4;
let offset = 0;
const txs: Tx[] = [];
while (offset < message.length) {
const dataSize = message.readUInt32BE(offset);
const totalSizeOfMessage = lengthSize + dataSize;
txs.push(fromTxMessage(message.subarray(offset, offset + totalSizeOfMessage)));
offset += totalSizeOfMessage;
}
return txs;
}

/**
* Creates a tx 'message' for sending to a peer.
* @param tx - The transaction to convert to a message.
* @returns - The message.
*/
export function toTxMessage(tx: Tx): Buffer {
// eslint-disable-next-line jsdoc/require-jsdoc
const createMessageComponent = (obj?: { toBuffer: () => Buffer }) => {
if (!obj) {
// specify a length of 0 bytes
return numToUInt32BE(0);
}
const buffer = obj.toBuffer();
return Buffer.concat([numToUInt32BE(buffer.length), buffer]);
};
// eslint-disable-next-line jsdoc/require-jsdoc
const createMessageComponents = (obj?: { toBuffer: () => Buffer }[]) => {
if (!obj || !obj.length) {
// specify a length of 0 bytes
return numToUInt32BE(0);
}
const allComponents = Buffer.concat(obj.map(createMessageComponent));
return Buffer.concat([numToUInt32BE(obj.length), allComponents]);
};
const messageBuffer = Buffer.concat([
createMessageComponent(tx.data),
createMessageComponent(tx.clientIvcProof),
createMessageComponent(tx.noteEncryptedLogs),
createMessageComponent(tx.encryptedLogs),
createMessageComponent(tx.unencryptedLogs),
createMessageComponents(tx.enqueuedPublicFunctionCalls),
createMessageComponent(tx.publicTeardownFunctionCall),
]);
const messageLength = numToUInt32BE(messageBuffer.length);
return Buffer.concat([messageLength, messageBuffer]);
}

/**
* Reproduces a transaction from a transaction 'message'
* @param buffer - The message buffer to convert to a tx.
* @returns - The reproduced transaction.
*/
export function fromTxMessage(buffer: Buffer): Tx {
// eslint-disable-next-line jsdoc/require-jsdoc
const toObject = <T>(objectBuffer: Buffer, factory: { fromBuffer: (b: Buffer) => T }) => {
const objectSize = objectBuffer.readUint32BE(0);
return {
remainingData: objectBuffer.subarray(objectSize + 4),
obj: objectSize === 0 ? undefined : factory.fromBuffer(objectBuffer.subarray(4, objectSize + 4)),
};
};

// eslint-disable-next-line jsdoc/require-jsdoc
const toObjectArray = <T>(objectBuffer: Buffer, factory: { fromBuffer: (b: Buffer) => T }) => {
const output: T[] = [];
const numItems = objectBuffer.readUint32BE(0);
let workingBuffer = objectBuffer.subarray(4);
for (let i = 0; i < numItems; i++) {
const obj = toObject<T>(workingBuffer, factory);
workingBuffer = obj.remainingData;
if (obj !== undefined) {
output.push(obj.obj!);
}
}
return {
remainingData: workingBuffer,
objects: output,
};
};
// this is the opposite of the 'toMessage' function
// so the first 4 bytes is the complete length, skip it
const publicInputs = toObject(buffer.subarray(4), PrivateKernelTailCircuitPublicInputs);
const clientIvcProof = toObject(publicInputs.remainingData, ClientIvcProof);

const noteEncryptedLogs = toObject(clientIvcProof.remainingData, EncryptedNoteTxL2Logs);
if (!noteEncryptedLogs.obj) {
noteEncryptedLogs.obj = new EncryptedNoteTxL2Logs([]);
}
const encryptedLogs = toObject(noteEncryptedLogs.remainingData, EncryptedTxL2Logs);
if (!encryptedLogs.obj) {
encryptedLogs.obj = new EncryptedTxL2Logs([]);
}
const unencryptedLogs = toObject(encryptedLogs.remainingData, UnencryptedTxL2Logs);
if (!unencryptedLogs.obj) {
unencryptedLogs.obj = new UnencryptedTxL2Logs([]);
}

const publicCalls = toObjectArray(unencryptedLogs.remainingData, PublicExecutionRequest);

const publicTeardownCall = toObject(publicCalls.remainingData, PublicExecutionRequest);
return new Tx(
publicInputs.obj!,
clientIvcProof.obj!,
noteEncryptedLogs.obj,
encryptedLogs.obj,
unencryptedLogs.obj,
publicCalls.objects,
publicTeardownCall.obj!,
);
}
Loading