From e65a361e1ac0c8c087c5f153330d9557639c2ac1 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Mon, 25 Sep 2023 10:32:06 +0100 Subject: [PATCH] feat: serialise L2Block to JSON --- .../src/structs/global_variables.ts | 18 ++++ .../rollup/append_only_tree_snapshot.ts | 10 ++- .../circuits.js/src/structs/shared.ts | 5 ++ yarn-project/types/src/l2_block.test.ts | 8 ++ yarn-project/types/src/l2_block.ts | 89 +++++++++++++++++-- yarn-project/types/src/public_data_write.ts | 18 ++++ 6 files changed, 142 insertions(+), 6 deletions(-) diff --git a/yarn-project/circuits.js/src/structs/global_variables.ts b/yarn-project/circuits.js/src/structs/global_variables.ts index 10509a5d015..c9bd2cb7e1e 100644 --- a/yarn-project/circuits.js/src/structs/global_variables.ts +++ b/yarn-project/circuits.js/src/structs/global_variables.ts @@ -40,6 +40,15 @@ 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) { return [fields.chainId, fields.version, fields.blockNumber, fields.timestamp] as const; } @@ -47,4 +56,13 @@ export class GlobalVariables { toBuffer() { return serializeToBuffer(...GlobalVariables.getFields(this)); } + + toJSON() { + return { + chainId: this.chainId.toString(), + version: this.version.toString(), + blockNumber: this.blockNumber.toString(), + timestamp: this.timestamp.toString(), + }; + } } diff --git a/yarn-project/circuits.js/src/structs/rollup/append_only_tree_snapshot.ts b/yarn-project/circuits.js/src/structs/rollup/append_only_tree_snapshot.ts index 90dec1eebdf..3c418ef986e 100644 --- a/yarn-project/circuits.js/src/structs/rollup/append_only_tree_snapshot.ts +++ b/yarn-project/circuits.js/src/structs/rollup/append_only_tree_snapshot.ts @@ -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. @@ -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); } diff --git a/yarn-project/circuits.js/src/structs/shared.ts b/yarn-project/circuits.js/src/structs/shared.ts index 4f33319e80d..473db51ee7b 100644 --- a/yarn-project/circuits.js/src/structs/shared.ts +++ b/yarn-project/circuits.js/src/structs/shared.ts @@ -44,3 +44,8 @@ export enum RollupTypes { Merge = 1, Root = 2, } + +/** + * String encoding of serialised buffer data + */ +export const STRING_ENCODING: BufferEncoding = 'hex'; diff --git a/yarn-project/types/src/l2_block.test.ts b/yarn-project/types/src/l2_block.test.ts index 99d20880517..959849fed67 100644 --- a/yarn-project/types/src/l2_block.test.ts +++ b/yarn-project/types/src/l2_block.test.ts @@ -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` diff --git a/yarn-project/types/src/l2_block.ts b/yarn-project/types/src/l2_block.ts index a0a3950bed6..d2ea30a99fd 100644 --- a/yarn-project/types/src/l2_block.ts +++ b/yarn-project/types/src/l2_block.ts @@ -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'; @@ -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. @@ -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. @@ -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. diff --git a/yarn-project/types/src/public_data_write.ts b/yarn-project/types/src/public_data_write.ts index 71999319e03..844a7ce1805 100644 --- a/yarn-project/types/src/public_data_write.ts +++ b/yarn-project/types/src/public_data_write.ts @@ -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'; @@ -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. @@ -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.