Skip to content

Commit

Permalink
fix: block encoding (#2719)
Browse files Browse the repository at this point in the history
When `AztecNode.getBlocks` was called over JSON-RPC an error was thrown
in cases when the logs were not yet attached to a block. This PR fixes
it.

In this PR I also remove unused `toJSON` and `fromJSON` functionality
from `L2Block`.
  • Loading branch information
benesjan authored Oct 6, 2023
1 parent 891c136 commit c4796ac
Show file tree
Hide file tree
Showing 10 changed files with 69 additions and 139 deletions.
2 changes: 1 addition & 1 deletion yarn-project/archiver/src/archiver/archiver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ function makeL1ToL2MessageCancelledEvents(l1BlockNum: bigint, entryKeys: string[
*/
function makeRollupTx(l2Block: L2Block) {
const proof = `0x`;
const block = toHex(l2Block.encode());
const block = toHex(l2Block.toBufferWithLogs());
const input = encodeFunctionData({ abi: RollupAbi, functionName: 'process', args: [proof, block] });
return { input } as Transaction<bigint, number>;
}
2 changes: 1 addition & 1 deletion yarn-project/archiver/src/archiver/eth_log_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ async function getBlockFromCallData(
});
if (functionName !== 'process') throw new Error(`Unexpected method called ${functionName}`);
const [, l2BlockHex] = args! as [Hex, Hex];
const block = L2Block.decode(Buffer.from(hexToBytes(l2BlockHex)));
const block = L2Block.fromBufferWithLogs(Buffer.from(hexToBytes(l2BlockHex)));
if (BigInt(block.number) !== l2BlockNum) {
throw new Error(`Block number mismatch: expected ${l2BlockNum} but got ${block.number}`);
}
Expand Down
8 changes: 4 additions & 4 deletions yarn-project/aztec-sandbox/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ services:
image: otterscan/otterscan:develop
# platform: linux/amd64
ports:
- "5100:80"
- '5100:80'
container_name: otterscan
environment:
# otterscan env var is hardcoded to support erigon client
# but it also works for anvil
- ERIGON_URL=http://127.0.0.1:${SANDBOX_ANVIL_PORT:-8545}
# otterscan env var is hardcoded to support erigon client
# but it also works for anvil
- ERIGON_URL=http://127.0.0.1:${SANDBOX_ANVIL_PORT:-8545}
2 changes: 1 addition & 1 deletion yarn-project/end-to-end/src/fixtures/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ export const setupL1Contracts = async (
/**
* Sets up Private eXecution Environment (PXE).
* @param numberOfAccounts - The number of new accounts to be created once the PXE is initiated.
* @param aztecNode - The instance of an aztec node, if one is required
* @param aztecNode - An instance of Aztec Node.
* @param firstPrivKey - The private key of the first account to be created.
* @param logger - The logger to be used.
* @param useLogSuffix - Whether to add a randomly generated suffix to the PXE debug logs.
Expand Down
8 changes: 4 additions & 4 deletions yarn-project/end-to-end/src/integration_l1_publisher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,11 +315,11 @@ describe('L1Publisher integration', () => {
const expectedData = encodeFunctionData({
abi: RollupAbi,
functionName: 'process',
args: [`0x${l2Proof.toString('hex')}`, `0x${block.encode().toString('hex')}`],
args: [`0x${l2Proof.toString('hex')}`, `0x${block.toBufferWithLogs().toString('hex')}`],
});
expect(ethTx.input).toEqual(expectedData);

const decoderArgs = [`0x${block.encode().toString('hex')}`] as const;
const decoderArgs = [`0x${block.toBufferWithLogs().toString('hex')}`] as const;
const decodedHashes = await decoderHelper.read.computeDiffRootAndMessagesHash(decoderArgs);
const decodedRes = await decoderHelper.read.decode(decoderArgs);
const stateInRollup = await rollup.read.rollupStateHash();
Expand Down Expand Up @@ -387,11 +387,11 @@ describe('L1Publisher integration', () => {
const expectedData = encodeFunctionData({
abi: RollupAbi,
functionName: 'process',
args: [`0x${l2Proof.toString('hex')}`, `0x${block.encode().toString('hex')}`],
args: [`0x${l2Proof.toString('hex')}`, `0x${block.toBufferWithLogs().toString('hex')}`],
});
expect(ethTx.input).toEqual(expectedData);

const decoderArgs = [`0x${block.encode().toString('hex')}`] as const;
const decoderArgs = [`0x${block.toBufferWithLogs().toString('hex')}`] as const;
const decodedHashes = await decoderHelper.read.computeDiffRootAndMessagesHash(decoderArgs);
const decodedRes = await decoderHelper.read.decode(decoderArgs);
const stateInRollup = await rollup.read.rollupStateHash();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ export async function defaultFetch(
}
if (!resp.ok) {
if (noRetry) {
throw new NoRetryError(responseJson.error.message);
throw new NoRetryError('(JSON-RPC PROPAGATED) ' + responseJson.error.message);
} else {
throw new Error(responseJson.error.message);
throw new Error('(JSON-RPC PROPAGATED) ' + responseJson.error.message);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ describe('L1Publisher', () => {

beforeEach(() => {
l2Block = L2Block.random(42);
l2Inputs = l2Block.encode();
l2Inputs = l2Block.toBufferWithLogs();
l2Proof = Buffer.alloc(0);

txSender = mock<L1PublisherTxSender>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export class L1Publisher implements L2BlockReceiver {
*/
public async processL2Block(l2BlockData: L2Block): Promise<boolean> {
const proof = Buffer.alloc(0);
const txData = { proof, inputs: l2BlockData.encode() };
const txData = { proof, inputs: l2BlockData.toBufferWithLogs() };

while (!this.interrupted) {
if (!(await this.checkFeeDistributorBalance())) {
Expand Down
19 changes: 7 additions & 12 deletions yarn-project/types/src/l2_block.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,26 @@ import { TxL2Logs } from './index.js';
import { L2Block } from './l2_block.js';

describe('L2Block', () => {
it('can encode a L2 block data object to buffer and back', () => {
it('can serialize an L2 block with logs to a buffer and back', () => {
const block = L2Block.random(42);

const buffer = block.encode();
const recovered = L2Block.decode(buffer);
const buffer = block.toBufferWithLogs();
const recovered = L2Block.fromBufferWithLogs(buffer);

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

it('can encode a L2 block to string and back', () => {
it('can serialize an L2 block without logs to a buffer and back', () => {
const block = L2Block.random(42);
block.newEncryptedLogs = undefined;
block.newUnencryptedLogs = undefined;

const serialised = block.toString();
const recovered = L2Block.fromString(serialised);

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
159 changes: 47 additions & 112 deletions yarn-project/types/src/l2_block.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,14 +341,12 @@ export class L2Block {
}

/**
* Encode the L2 block data into a buffer that can be pushed to the rollup contract.
* @returns The encoded L2 block data.
* Serializes a block without logs to a buffer.
* @remarks This is used when the block is being served via JSON-RPC because the logs are expected to be served
* separately.
* @returns A serialized L2 block without logs.
*/
encode(): Buffer {
if (this.newEncryptedLogs === undefined || this.newUnencryptedLogs === undefined) {
throw new Error('newEncryptedLogs and newUnencryptedLogs must be defined when encoding L2BlockData');
}

toBuffer() {
return serializeToBuffer(
this.globalVariables,
this.startPrivateDataTreeSnapshot,
Expand Down Expand Up @@ -376,65 +374,41 @@ export class L2Block {
this.newContractData,
this.newL1ToL2Messages.length,
this.newL1ToL2Messages,
this.newEncryptedLogs,
this.newUnencryptedLogs,
);
}

/**
* Alias for encode.
* @returns The encoded L2 block data.
* Serializes a block with logs to a buffer.
* @remarks This is used when the block is being submitted on L1.
* @returns A serialized L2 block with logs.
*/
toBuffer() {
return this.encode();
}
toBufferWithLogs(): Buffer {
if (this.newEncryptedLogs === undefined || this.newUnencryptedLogs === undefined) {
throw new Error(
`newEncryptedLogs and newUnencryptedLogs must be defined when encoding L2BlockData (block ${this.number})`,
);
}

/**
* Encodes the block as a hex string
* @returns The encoded L2 block data as a hex string.
*/
toString() {
return this.toBuffer().toString(STRING_ENCODING);
return serializeToBuffer(this.toBuffer(), this.newEncryptedLogs, this.newUnencryptedLogs);
}

/**
* Encodes the block as a JSON object.
* @returns The L2 block encoded as a JSON object.
* Serializes a block without logs to a string.
* @remarks This is used when the block is being served via JSON-RPC because the logs are expected to be served
* separately.
* @returns A serialized L2 block without logs.
*/
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,
};
toString(): string {
return this.toBuffer().toString(STRING_ENCODING);
}

/**
* Decode the L2 block data from a buffer.
* @param encoded - The encoded L2 block data.
* @returns The decoded L2 block data.
* Deserializes L2 block without logs from a buffer.
* @param buf - A serialized L2 block.
* @returns Deserialized L2 block.
*/
static decode(encoded: Buffer | BufferReader) {
const reader = BufferReader.asReader(encoded);
static fromBuffer(buf: Buffer | BufferReader) {
const reader = BufferReader.asReader(buf);
const globalVariables = reader.readObject(GlobalVariables);
const number = Number(globalVariables.blockNumber.value);
const startPrivateDataTreeSnapshot = reader.readObject(AppendOnlyTreeSnapshot);
Expand All @@ -457,8 +431,6 @@ export class L2Block {
const newContractData = reader.readArray(newContracts.length, ContractData);
// TODO(sean): could an optimisation of this be that it is encoded such that zeros are assumed
const newL1ToL2Messages = reader.readVector(Fr);
const newEncryptedLogs = reader.readObject(L2BlockL2Logs);
const newUnencryptedLogs = reader.readObject(L2BlockL2Logs);

return L2Block.fromFields({
number,
Expand All @@ -482,70 +454,33 @@ export class L2Block {
newContracts,
newContractData,
newL1ToL2Messages,
newEncryptedLogs,
newUnencryptedLogs,
});
}

/**
* Decode the L2 block from a string
* @param str - The serialised L2 block
* @returns An L2 block
* Deserializes L2 block with logs from a buffer.
* @param buf - A serialized L2 block.
* @returns Deserialized L2 block.
*/
static fromString(str: string): L2Block {
return L2Block.decode(Buffer.from(str, STRING_ENCODING));
}
static fromBufferWithLogs(buf: Buffer | BufferReader) {
const reader = BufferReader.asReader(buf);
const block = L2Block.fromBuffer(reader);
const newEncryptedLogs = reader.readObject(L2BlockL2Logs);
const newUnencryptedLogs = reader.readObject(L2BlockL2Logs);

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;
block.attachLogs(newEncryptedLogs, LogType.ENCRYPTED);
block.attachLogs(newUnencryptedLogs, LogType.UNENCRYPTED);

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,
});
return block;
}

/**
* Deserializes L2 block without logs from a buffer.
* @param str - A serialized L2 block.
* @returns Deserialized L2 block.
*/
static fromString(str: string): L2Block {
return L2Block.fromBuffer(Buffer.from(str, STRING_ENCODING));
}

/**
Expand Down Expand Up @@ -584,7 +519,7 @@ export class L2Block {
*/
public getBlockHash(): Buffer {
if (!this.blockHash) {
this.blockHash = keccak(this.encode());
this.blockHash = keccak(this.toBufferWithLogs());
}
return this.blockHash;
}
Expand Down

0 comments on commit c4796ac

Please sign in to comment.