Skip to content

Commit

Permalink
feat: serialise L2Block to JSON (#2496)
Browse files Browse the repository at this point in the history
Serialise L2Blocks to JSON.

# Checklist:
Remove the checklist to signal you've completed it. Enable auto-merge if
the PR is ready to merge.
- [ ] If the pull request requires a cryptography review (e.g.
cryptographic algorithm implementations) I have added the 'crypto' tag.
- [x] I have reviewed my diff in github, line by line and removed
unexpected formatting changes, testing logs, or commented-out code.
- [x] Every change is related to the PR description.
- [x] I have
[linked](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
this pull request to relevant issues (if any exist).
  • Loading branch information
alexghr authored Sep 25, 2023
1 parent 89d700d commit 714c727
Show file tree
Hide file tree
Showing 6 changed files with 142 additions and 6 deletions.
18 changes: 18 additions & 0 deletions yarn-project/circuits.js/src/structs/global_variables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,29 @@ export class GlobalVariables {
return new GlobalVariables(reader.readFr(), reader.readFr(), reader.readFr(), reader.readFr());
}

static fromJSON(obj: any): GlobalVariables {
return new GlobalVariables(
Fr.fromString(obj.chainId),
Fr.fromString(obj.version),
Fr.fromString(obj.blockNumber),
Fr.fromString(obj.timestamp),
);
}

static getFields(fields: FieldsOf<GlobalVariables>) {
return [fields.chainId, fields.version, fields.blockNumber, fields.timestamp] as const;
}

toBuffer() {
return serializeToBuffer(...GlobalVariables.getFields(this));
}

toJSON() {
return {
chainId: this.chainId.toString(),
version: this.version.toString(),
blockNumber: this.blockNumber.toString(),
timestamp: this.timestamp.toString(),
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Fr } from '@aztec/foundation/fields';
import { BufferReader } from '@aztec/foundation/serialize';

import { serializeToBuffer } from '../../utils/serialize.js';
import { UInt32 } from '../shared.js';
import { STRING_ENCODING, UInt32 } from '../shared.js';

/**
* Snapshot of an append only tree.
Expand Down Expand Up @@ -31,11 +31,19 @@ export class AppendOnlyTreeSnapshot {
return serializeToBuffer(this.root, this.nextAvailableLeafIndex);
}

toString(): string {
return this.toBuffer().toString(STRING_ENCODING);
}

static fromBuffer(buffer: Buffer | BufferReader): AppendOnlyTreeSnapshot {
const reader = BufferReader.asReader(buffer);
return new AppendOnlyTreeSnapshot(reader.readFr(), reader.readNumber());
}

static fromString(str: string): AppendOnlyTreeSnapshot {
return AppendOnlyTreeSnapshot.fromBuffer(Buffer.from(str, STRING_ENCODING));
}

static empty() {
return new AppendOnlyTreeSnapshot(Fr.ZERO, 0);
}
Expand Down
5 changes: 5 additions & 0 deletions yarn-project/circuits.js/src/structs/shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,8 @@ export enum RollupTypes {
Merge = 1,
Root = 2,
}

/**
* String encoding of serialised buffer data
*/
export const STRING_ENCODING: BufferEncoding = 'hex';
8 changes: 8 additions & 0 deletions yarn-project/types/src/l2_block.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ describe('L2Block', () => {
expect(recovered).toEqual(block);
});

it('can serialise an L2 block to JSON and back', () => {
const block = L2Block.random(42);
const serialised = block.toJSON();
const recovered = L2Block.fromJSON(serialised);

expect(recovered).toEqual(block);
});

// TS equivalent of `testComputeKernelLogsIterationWithoutLogs` in `Decoder.t.sol`
it('correctly computes kernel logs hash when there are no logs', () => {
// The following 2 values are copied from `testComputeKernelLogsIterationWithoutLogs` in `Decoder.t.sol`
Expand Down
89 changes: 84 additions & 5 deletions yarn-project/types/src/l2_block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
MAX_NEW_NULLIFIERS_PER_TX,
MAX_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX,
NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
STRING_ENCODING,
} from '@aztec/circuits.js';
import { makeAppendOnlyTreeSnapshot, makeGlobalVariables } from '@aztec/circuits.js/factories';
import { BufferReader, serializeToBuffer } from '@aztec/circuits.js/utils';
Expand All @@ -19,11 +20,6 @@ import times from 'lodash.times';
import { ContractData, L2Tx, LogType, PublicDataWrite, TxL2Logs } from './index.js';
import { L2BlockL2Logs } from './logs/l2_block_l2_logs.js';

/**
* String encoding of serialised L2 block data.
*/
const STRING_ENCODING: BufferEncoding = 'hex';

/**
* The data that makes up the rollup proof, with encoder decoder functions.
* TODO: Reuse data types and serialization functions from circuits package.
Expand Down Expand Up @@ -401,6 +397,37 @@ export class L2Block {
return this.toBuffer().toString(STRING_ENCODING);
}

/**
* Encodes the block as a JSON object.
* @returns The L2 block encoded as a JSON object.
*/
toJSON() {
return {
globalVariables: this.globalVariables.toJSON(),
startPrivateDataTreeSnapshot: this.startPrivateDataTreeSnapshot.toString(),
startNullifierTreeSnapshot: this.startNullifierTreeSnapshot.toString(),
startContractTreeSnapshot: this.startContractTreeSnapshot.toString(),
startPublicDataTreeRoot: this.startPublicDataTreeRoot.toString(),
startL1ToL2MessagesTreeSnapshot: this.startL1ToL2MessagesTreeSnapshot.toString(),
startHistoricBlocksTreeSnapshot: this.startHistoricBlocksTreeSnapshot.toString(),
endPrivateDataTreeSnapshot: this.endPrivateDataTreeSnapshot.toString(),
endNullifierTreeSnapshot: this.endNullifierTreeSnapshot.toString(),
endContractTreeSnapshot: this.endContractTreeSnapshot.toString(),
endPublicDataTreeRoot: this.endPublicDataTreeRoot.toString(),
endL1ToL2MessagesTreeSnapshot: this.endL1ToL2MessagesTreeSnapshot.toString(),
endHistoricBlocksTreeSnapshot: this.endHistoricBlocksTreeSnapshot.toString(),
newCommitments: this.newCommitments.map(c => c.toString()),
newNullifiers: this.newNullifiers.map(n => n.toString()),
newPublicDataWrites: this.newPublicDataWrites.map(p => p.toString()),
newL2ToL1Msgs: this.newL2ToL1Msgs.map(m => m.toString()),
newContracts: this.newContracts.map(c => c.toString()),
newContractData: this.newContractData.map(c => c.toString()),
newL1ToL2Messages: this.newL1ToL2Messages.map(m => m.toString()),
newEncryptedLogs: this.newEncryptedLogs?.toJSON() ?? null,
newUnencryptedLogs: this.newUnencryptedLogs?.toJSON() ?? null,
};
}

/**
* Decode the L2 block data from a buffer.
* @param encoded - The encoded L2 block data.
Expand Down Expand Up @@ -469,6 +496,58 @@ export class L2Block {
return L2Block.decode(Buffer.from(str, STRING_ENCODING));
}

static fromJSON(_obj: any): L2Block {
const globalVariables = GlobalVariables.fromJSON(_obj.globalVariables);
const number = Number(globalVariables.blockNumber.value);
const startPrivateDataTreeSnapshot = AppendOnlyTreeSnapshot.fromString(_obj.startPrivateDataTreeSnapshot);
const startNullifierTreeSnapshot = AppendOnlyTreeSnapshot.fromString(_obj.startNullifierTreeSnapshot);
const startContractTreeSnapshot = AppendOnlyTreeSnapshot.fromString(_obj.startContractTreeSnapshot);
const startPublicDataTreeRoot = Fr.fromString(_obj.startPublicDataTreeRoot);
const startL1ToL2MessagesTreeSnapshot = AppendOnlyTreeSnapshot.fromString(_obj.startL1ToL2MessagesTreeSnapshot);
const startHistoricBlocksTreeSnapshot = AppendOnlyTreeSnapshot.fromString(_obj.startHistoricBlocksTreeSnapshot);
const endPrivateDataTreeSnapshot = AppendOnlyTreeSnapshot.fromString(_obj.endPrivateDataTreeSnapshot);
const endNullifierTreeSnapshot = AppendOnlyTreeSnapshot.fromString(_obj.endNullifierTreeSnapshot);
const endContractTreeSnapshot = AppendOnlyTreeSnapshot.fromString(_obj.endContractTreeSnapshot);
const endPublicDataTreeRoot = Fr.fromString(_obj.endPublicDataTreeRoot);
const endL1ToL2MessagesTreeSnapshot = AppendOnlyTreeSnapshot.fromString(_obj.endL1ToL2MessagesTreeSnapshot);
const endHistoricBlocksTreeSnapshot = AppendOnlyTreeSnapshot.fromString(_obj.endHistoricBlocksTreeSnapshot);
const newCommitments = _obj.newCommitments.map((c: string) => Fr.fromString(c));
const newNullifiers = _obj.newNullifiers.map((n: string) => Fr.fromString(n));
const newPublicDataWrites = _obj.newPublicDataWrites.map((p: any) => PublicDataWrite.fromString(p));
const newL2ToL1Msgs = _obj.newL2ToL1Msgs.map((m: string) => Fr.fromString(m));
const newContracts = _obj.newContracts.map((c: string) => Fr.fromString(c));
const newContractData = _obj.newContractData.map((c: any) => ContractData.fromString(c));
const newL1ToL2Messages = _obj.newL1ToL2Messages.map((m: string) => Fr.fromString(m));
const newEncryptedLogs = _obj.newEncryptedLogs ? L2BlockL2Logs.fromJSON(_obj.newEncryptedLogs) : undefined;
const newUnencryptedLogs = _obj.newUnencryptedLogs ? L2BlockL2Logs.fromJSON(_obj.newUnencryptedLogs) : undefined;

return L2Block.fromFields({
number,
globalVariables,
startPrivateDataTreeSnapshot,
startNullifierTreeSnapshot,
startContractTreeSnapshot,
startPublicDataTreeRoot,
startL1ToL2MessagesTreeSnapshot,
startHistoricBlocksTreeSnapshot,
endPrivateDataTreeSnapshot,
endNullifierTreeSnapshot,
endContractTreeSnapshot,
endPublicDataTreeRoot,
endL1ToL2MessagesTreeSnapshot,
endHistoricBlocksTreeSnapshot,
newCommitments,
newNullifiers,
newPublicDataWrites,
newL2ToL1Msgs,
newContracts,
newContractData,
newL1ToL2Messages,
newEncryptedLogs,
newUnencryptedLogs,
});
}

/**
* Helper function to attach logs related to a block.
* @param logs - The logs to be attached to a block.
Expand Down
18 changes: 18 additions & 0 deletions yarn-project/types/src/public_data_write.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { STRING_ENCODING } from '@aztec/circuits.js';
import { serializeToBuffer } from '@aztec/circuits.js/utils';
import { Fr } from '@aztec/foundation/fields';
import { BufferReader } from '@aztec/foundation/serialize';
Expand Down Expand Up @@ -43,6 +44,14 @@ export class PublicDataWrite {
return serializeToBuffer(this.leafIndex, this.newValue);
}

/**
* Serialises the operation to a string.
* @returns A string representation of the operation.
*/
toString(): string {
return this.toBuffer().toString(STRING_ENCODING);
}

/**
* Checks if the public data write operation is empty.
* @returns True if the public data write operation is empty, false otherwise.
Expand All @@ -61,6 +70,15 @@ export class PublicDataWrite {
return new PublicDataWrite(reader.readFr(), reader.readFr());
}

/**
* Creates a new public data write operation from the given string.
* @param str - The serialised string
* @returns A new public data write operation instance.
*/
static fromString(str: string): PublicDataWrite {
return PublicDataWrite.fromBuffer(Buffer.from(str, STRING_ENCODING));
}

/**
* Creates an empty public data write operation.
* @returns A new public data write operation instance.
Expand Down

0 comments on commit 714c727

Please sign in to comment.