From fa52b3d5dd9ba3a5a637c05454e4c8533316e34e Mon Sep 17 00:00:00 2001 From: MirandaWood Date: Fri, 31 Jan 2025 12:59:24 +0000 Subject: [PATCH 1/8] fix: support multiple blobs p block --- yarn-project/archiver/src/archiver/data_retrieval.ts | 2 +- yarn-project/foundation/src/blob/blob.ts | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/yarn-project/archiver/src/archiver/data_retrieval.ts b/yarn-project/archiver/src/archiver/data_retrieval.ts index bde78daa4071..cc1e43b651ec 100644 --- a/yarn-project/archiver/src/archiver/data_retrieval.ts +++ b/yarn-project/archiver/src/archiver/data_retrieval.ts @@ -243,7 +243,7 @@ async function getBlockFromRollupTx( // Body.fromBlobFields to accept blob buffers directly let blockFields: Fr[]; try { - blockFields = blobBodies.flatMap(b => b.toEncodedFields()); + blockFields = Blob.toEncodedFields(blobBodies); } catch (err: any) { if (err instanceof BlobDeserializationError) { logger.fatal(err.message); diff --git a/yarn-project/foundation/src/blob/blob.ts b/yarn-project/foundation/src/blob/blob.ts index e1504f392534..df5349083970 100644 --- a/yarn-project/foundation/src/blob/blob.ts +++ b/yarn-project/foundation/src/blob/blob.ts @@ -163,6 +163,17 @@ export class Blob { } } + /** + * Get the encoded fields from multiple blobs. + * + * @dev This method takes into account trailing zeros + * + * @returns The encoded fields from the blobs. + */ + static toEncodedFields(blobs: Blob[]): Fr[] { + return deserializeEncodedBlobToFields(Buffer.concat(blobs.map(b => b.data))); + } + /** * Get the commitment fields from the blob. * From 28ca0bd35295c7f57224c93c0bb88727bb9c04e8 Mon Sep 17 00:00:00 2001 From: MirandaWood Date: Mon, 3 Feb 2025 13:17:23 +0000 Subject: [PATCH 2/8] feat: remove some range checks, ensure archiver.test handles multiple blobs --- .../crates/rollup-lib/src/components.nr | 32 ++++++--- .../archiver/src/archiver/archiver.test.ts | 72 +++++++++---------- 2 files changed, 58 insertions(+), 46 deletions(-) diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr index 776ee6e8851e..a5347bdc5d91 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr @@ -5,7 +5,7 @@ use crate::abis::{ use super::abis::tx_effect::TxEffect; use dep::types::{ abis::{ - log::Log, log_hash::ScopedLogHash, public_data_write::PublicDataWrite, + log_hash::ScopedLogHash, private_log::PrivateLog, public_data_write::PublicDataWrite, public_log::PublicLog, sponge_blob::SpongeBlob, }, constants::{ @@ -225,6 +225,7 @@ fn get_tx_effects_hash_input( let mut offset = 0; let mut array_len = 0; + let mut check_elt = true; // NB: for publishing fields of blob data we use the first element of the blob to encode: // TX_START_PREFIX | 0 | txlen[0] txlen[1] | 0 | REVERT_CODE_PREFIX | 0 | revert_code @@ -251,7 +252,8 @@ fn get_tx_effects_hash_input( offset += 1; for j in 0..MAX_NOTE_HASHES_PER_TX { - if j < array_len { + check_elt &= j != array_len; + if check_elt { assert_eq(tx_effects_hash_input[offset + j], note_hashes[j]); } } @@ -261,12 +263,14 @@ fn get_tx_effects_hash_input( // NULLIFIERS array_len = array_length(nullifiers); if array_len != 0 { + check_elt = true; let nullifiers_prefix = encode_blob_prefix(NULLIFIERS_PREFIX, array_len); assert_eq(tx_effects_hash_input[offset], nullifiers_prefix); offset += 1; for j in 0..MAX_NULLIFIERS_PER_TX { - if j < array_len { + check_elt &= j != array_len; + if check_elt { assert_eq(tx_effects_hash_input[offset + j], nullifiers[j]); } } @@ -276,12 +280,14 @@ fn get_tx_effects_hash_input( // L2 TO L1 MESSAGES array_len = array_length(tx_effect.l2_to_l1_msgs); if array_len != 0 { + check_elt = true; let l2_to_l1_msgs_prefix = encode_blob_prefix(L2_L1_MSGS_PREFIX, array_len); assert_eq(tx_effects_hash_input[offset], l2_to_l1_msgs_prefix); offset += 1; for j in 0..MAX_L2_TO_L1_MSGS_PER_TX { - if j < array_len { + check_elt &= j != array_len; + if check_elt { assert_eq(tx_effects_hash_input[offset + j], tx_effect.l2_to_l1_msgs[j]); } } @@ -291,12 +297,14 @@ fn get_tx_effects_hash_input( // PUBLIC DATA UPDATE REQUESTS array_len = array_length(public_data_update_requests); if array_len != 0 { + check_elt = true; let public_data_update_requests_prefix = encode_blob_prefix(PUBLIC_DATA_UPDATE_REQUESTS_PREFIX, array_len * 2); assert_eq(tx_effects_hash_input[offset], public_data_update_requests_prefix); offset += 1; for j in 0..MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX { - if j < array_len { + check_elt &= j != array_len; + if check_elt { assert_eq( tx_effects_hash_input[offset + j * 2], public_data_update_requests[j].leaf_slot, @@ -314,6 +322,7 @@ fn get_tx_effects_hash_input( // PRIVATE_LOGS array_len = array_length(private_logs) * PRIVATE_LOG_SIZE_IN_FIELDS; if array_len != 0 { + check_elt = true; let private_logs_prefix = encode_blob_prefix(PRIVATE_LOGS_PREFIX, array_len); assert_eq(tx_effects_hash_input[offset], private_logs_prefix); offset += 1; @@ -321,7 +330,8 @@ fn get_tx_effects_hash_input( for j in 0..MAX_PRIVATE_LOGS_PER_TX { for k in 0..PRIVATE_LOG_SIZE_IN_FIELDS { let index = offset + j * PRIVATE_LOG_SIZE_IN_FIELDS + k; - if index < array_len { + check_elt &= j * PRIVATE_LOG_SIZE_IN_FIELDS + k != array_len; + if check_elt { assert_eq(tx_effects_hash_input[index], private_logs[j].fields[k]); } } @@ -332,6 +342,7 @@ fn get_tx_effects_hash_input( // PUBLIC LOGS array_len = array_length(public_logs) * PUBLIC_LOG_SIZE_IN_FIELDS; if array_len != 0 { + check_elt = true; let public_logs_prefix = encode_blob_prefix(PUBLIC_LOGS_PREFIX, array_len); assert_eq(tx_effects_hash_input[offset], public_logs_prefix); offset += 1; @@ -339,7 +350,8 @@ fn get_tx_effects_hash_input( let log = public_logs[j].serialize(); for k in 0..PUBLIC_LOG_SIZE_IN_FIELDS { let index = offset + j * PUBLIC_LOG_SIZE_IN_FIELDS + k; - if index < array_len { + check_elt &= j * PUBLIC_LOG_SIZE_IN_FIELDS + k != array_len; + if check_elt { assert_eq(tx_effects_hash_input[index], log[k]); } } @@ -352,12 +364,14 @@ fn get_tx_effects_hash_input( // CONTRACT CLASS LOGS array_len = array_length(contract_class_logs); if array_len != 0 { + check_elt = true; let contract_class_logs_prefix = encode_blob_prefix(CONTRACT_CLASS_LOGS_PREFIX, array_len); assert_eq(tx_effects_hash_input[offset], contract_class_logs_prefix); offset += 1; for j in 0..MAX_CONTRACT_CLASS_LOGS_PER_TX { - if j < array_len { + check_elt &= j != array_len; + if check_elt { assert_eq(tx_effects_hash_input[offset + j], contract_class_logs[j]); } } @@ -390,7 +404,7 @@ unconstrained fn get_tx_effects_hash_input_helper( nullifiers: [Field; MAX_NULLIFIERS_PER_TX], l2_to_l1_msgs: [Field; MAX_L2_TO_L1_MSGS_PER_TX], public_data_update_requests: [PublicDataWrite; MAX_TOTAL_PUBLIC_DATA_UPDATE_REQUESTS_PER_TX], - private_logs: [Log; MAX_PRIVATE_LOGS_PER_TX], + private_logs: [PrivateLog; MAX_PRIVATE_LOGS_PER_TX], public_logs: [PublicLog; MAX_PUBLIC_LOGS_PER_TX], contract_class_logs: [Field; MAX_CONTRACT_CLASS_LOGS_PER_TX], revert_code: Field, diff --git a/yarn-project/archiver/src/archiver/archiver.test.ts b/yarn-project/archiver/src/archiver/archiver.test.ts index 6d31d0dac9e9..578bf9aac2fa 100644 --- a/yarn-project/archiver/src/archiver/archiver.test.ts +++ b/yarn-project/archiver/src/archiver/archiver.test.ts @@ -183,7 +183,7 @@ describe('Archiver', () => { (b.header.globalVariables.timestamp = new Fr(now + DefaultL1ContractsConfig.ethereumSlotDuration * (i + 1))), ); const rollupTxs = await Promise.all(blocks.map(makeRollupTx)); - const blobHashes = await Promise.all(blocks.map(makeVersionedBlobHash)); + const blobHashes = await Promise.all(blocks.map(makeVersionedBlobHashes)); publicClient.getBlockNumber.mockResolvedValueOnce(2500n).mockResolvedValueOnce(2600n).mockResolvedValueOnce(2700n); @@ -199,19 +199,19 @@ describe('Archiver', () => { mockInbox.read.totalMessagesInserted.mockResolvedValueOnce(2n).mockResolvedValueOnce(6n); - const blobsFromBlocks = await Promise.all(blocks.map(b => makeBlobFromBlock(b))); - blobsFromBlocks.forEach(blob => blobSinkClient.getBlobSidecar.mockResolvedValueOnce([blob])); + const blobsFromBlocks = await Promise.all(blocks.map(b => makeBlobsFromBlock(b))); + blobsFromBlocks.forEach(blobs => blobSinkClient.getBlobSidecar.mockResolvedValueOnce(blobs)); makeMessageSentEvent(98n, 1n, 0n); makeMessageSentEvent(99n, 1n, 1n); - makeL2BlockProposedEvent(101n, 1n, blocks[0].archive.root.toString(), [blobHashes[0]]); + makeL2BlockProposedEvent(101n, 1n, blocks[0].archive.root.toString(), blobHashes[0]); makeMessageSentEvent(2504n, 2n, 0n); makeMessageSentEvent(2505n, 2n, 1n); makeMessageSentEvent(2505n, 2n, 2n); makeMessageSentEvent(2506n, 3n, 1n); - makeL2BlockProposedEvent(2510n, 2n, blocks[1].archive.root.toString(), [blobHashes[1]]); - makeL2BlockProposedEvent(2520n, 3n, blocks[2].archive.root.toString(), [blobHashes[2]]); + makeL2BlockProposedEvent(2510n, 2n, blocks[1].archive.root.toString(), blobHashes[1]); + makeL2BlockProposedEvent(2520n, 3n, blocks[2].archive.root.toString(), blobHashes[2]); publicClient.getTransaction.mockResolvedValueOnce(rollupTxs[0]); rollupTxs.slice(1).forEach(tx => publicClient.getTransaction.mockResolvedValueOnce(tx)); @@ -281,7 +281,7 @@ describe('Archiver', () => { const numL2BlocksInTest = 2; const rollupTxs = await Promise.all(blocks.map(makeRollupTx)); - const blobHashes = await Promise.all(blocks.map(makeVersionedBlobHash)); + const blobHashes = await Promise.all(blocks.map(makeVersionedBlobHashes)); // Here we set the current L1 block number to 102. L1 to L2 messages after this should not be read. publicClient.getBlockNumber.mockResolvedValue(102n); @@ -295,13 +295,13 @@ describe('Archiver', () => { makeMessageSentEvent(66n, 1n, 0n); makeMessageSentEvent(68n, 1n, 1n); - makeL2BlockProposedEvent(70n, 1n, blocks[0].archive.root.toString(), [blobHashes[0]]); - makeL2BlockProposedEvent(80n, 2n, blocks[1].archive.root.toString(), [blobHashes[1]]); + makeL2BlockProposedEvent(70n, 1n, blocks[0].archive.root.toString(), blobHashes[0]); + makeL2BlockProposedEvent(80n, 2n, blocks[1].archive.root.toString(), blobHashes[1]); makeL2BlockProposedEvent(90n, 3n, badArchive, [badBlobHash]); rollupTxs.forEach(tx => publicClient.getTransaction.mockResolvedValueOnce(tx)); - const blobsFromBlocks = await Promise.all(blocks.map(b => makeBlobFromBlock(b))); - blobsFromBlocks.forEach(blob => blobSinkClient.getBlobSidecar.mockResolvedValueOnce([blob])); + const blobsFromBlocks = await Promise.all(blocks.map(b => makeBlobsFromBlock(b))); + blobsFromBlocks.forEach(blobs => blobSinkClient.getBlobSidecar.mockResolvedValueOnce(blobs)); await archiver.start(false); @@ -326,7 +326,7 @@ describe('Archiver', () => { const numL2BlocksInTest = 2; const rollupTxs = await Promise.all(blocks.map(makeRollupTx)); - const blobHashes = await Promise.all(blocks.map(makeVersionedBlobHash)); + const blobHashes = await Promise.all(blocks.map(makeVersionedBlobHashes)); publicClient.getBlockNumber.mockResolvedValueOnce(50n).mockResolvedValueOnce(100n); mockRollup.read.status @@ -337,12 +337,12 @@ describe('Archiver', () => { makeMessageSentEvent(66n, 1n, 0n); makeMessageSentEvent(68n, 1n, 1n); - makeL2BlockProposedEvent(70n, 1n, blocks[0].archive.root.toString(), [blobHashes[0]]); - makeL2BlockProposedEvent(80n, 2n, blocks[1].archive.root.toString(), [blobHashes[1]]); + makeL2BlockProposedEvent(70n, 1n, blocks[0].archive.root.toString(), blobHashes[0]); + makeL2BlockProposedEvent(80n, 2n, blocks[1].archive.root.toString(), blobHashes[1]); rollupTxs.forEach(tx => publicClient.getTransaction.mockResolvedValueOnce(tx)); - const blobsFromBlocks = await Promise.all(blocks.map(b => makeBlobFromBlock(b))); - blobsFromBlocks.forEach(blob => blobSinkClient.getBlobSidecar.mockResolvedValueOnce([blob])); + const blobsFromBlocks = await Promise.all(blocks.map(b => makeBlobsFromBlock(b))); + blobsFromBlocks.forEach(blobs => blobSinkClient.getBlobSidecar.mockResolvedValueOnce(blobs)); await archiver.start(false); @@ -364,7 +364,7 @@ describe('Archiver', () => { const numL2BlocksInTest = 2; const rollupTxs = await Promise.all(blocks.map(makeRollupTx)); - const blobHashes = await Promise.all(blocks.map(makeVersionedBlobHash)); + const blobHashes = await Promise.all(blocks.map(makeVersionedBlobHashes)); publicClient.getBlockNumber.mockResolvedValueOnce(50n).mockResolvedValueOnce(100n).mockResolvedValueOnce(150n); @@ -388,12 +388,12 @@ describe('Archiver', () => { makeMessageSentEvent(66n, 1n, 0n); makeMessageSentEvent(68n, 1n, 1n); - makeL2BlockProposedEvent(70n, 1n, blocks[0].archive.root.toString(), [blobHashes[0]]); - makeL2BlockProposedEvent(80n, 2n, blocks[1].archive.root.toString(), [blobHashes[1]]); + makeL2BlockProposedEvent(70n, 1n, blocks[0].archive.root.toString(), blobHashes[0]); + makeL2BlockProposedEvent(80n, 2n, blocks[1].archive.root.toString(), blobHashes[1]); rollupTxs.forEach(tx => publicClient.getTransaction.mockResolvedValueOnce(tx)); - const blobsFromBlocks = await Promise.all(blocks.map(b => makeBlobFromBlock(b))); - blobsFromBlocks.forEach(blob => blobSinkClient.getBlobSidecar.mockResolvedValueOnce([blob])); + const blobsFromBlocks = await Promise.all(blocks.map(b => makeBlobsFromBlock(b))); + blobsFromBlocks.forEach(blobs => blobSinkClient.getBlobSidecar.mockResolvedValueOnce(blobs)); await archiver.start(false); @@ -434,15 +434,15 @@ describe('Archiver', () => { const l2Block = blocks[0]; l2Block.header.globalVariables.slotNumber = new Fr(notLastL2SlotInEpoch); blocks = [l2Block]; - const blobHashes = [await makeVersionedBlobHash(l2Block)]; + const blobHashes = await makeVersionedBlobHashes(l2Block); const rollupTxs = await Promise.all(blocks.map(makeRollupTx)); publicClient.getBlockNumber.mockResolvedValueOnce(l1BlockForL2Block); mockRollup.read.status.mockResolvedValueOnce([0n, GENESIS_ROOT, 1n, l2Block.archive.root.toString(), GENESIS_ROOT]); makeL2BlockProposedEvent(l1BlockForL2Block, 1n, l2Block.archive.root.toString(), blobHashes); rollupTxs.forEach(tx => publicClient.getTransaction.mockResolvedValueOnce(tx)); - const blobsFromBlocks = await Promise.all(blocks.map(b => makeBlobFromBlock(b))); - blobsFromBlocks.forEach(blob => blobSinkClient.getBlobSidecar.mockResolvedValueOnce([blob])); + const blobsFromBlocks = await Promise.all(blocks.map(b => makeBlobsFromBlock(b))); + blobsFromBlocks.forEach(blobs => blobSinkClient.getBlobSidecar.mockResolvedValueOnce(blobs)); await archiver.start(false); @@ -468,7 +468,7 @@ describe('Archiver', () => { const l2Block = blocks[0]; l2Block.header.globalVariables.slotNumber = new Fr(lastL2SlotInEpoch); blocks = [l2Block]; - const blobHashes = [await makeVersionedBlobHash(l2Block)]; + const blobHashes = await makeVersionedBlobHashes(l2Block); const rollupTxs = await Promise.all(blocks.map(makeRollupTx)); publicClient.getBlockNumber.mockResolvedValueOnce(l1BlockForL2Block); @@ -476,8 +476,8 @@ describe('Archiver', () => { makeL2BlockProposedEvent(l1BlockForL2Block, 1n, l2Block.archive.root.toString(), blobHashes); rollupTxs.forEach(tx => publicClient.getTransaction.mockResolvedValueOnce(tx)); - const blobsFromBlocks = await Promise.all(blocks.map(b => makeBlobFromBlock(b))); - blobsFromBlocks.forEach(blob => blobSinkClient.getBlobSidecar.mockResolvedValueOnce([blob])); + const blobsFromBlocks = await Promise.all(blocks.map(b => makeBlobsFromBlock(b))); + blobsFromBlocks.forEach(blobs => blobSinkClient.getBlobSidecar.mockResolvedValueOnce(blobs)); await archiver.start(false); @@ -594,22 +594,20 @@ async function makeRollupTx(l2Block: L2Block) { } /** - * Makes a versioned blob hash for testing purposes. + * Makes versioned blob hashes for testing purposes. * @param l2Block - The L2 block. - * @returns A versioned blob hash. + * @returns Versioned blob hashes. */ -async function makeVersionedBlobHash(l2Block: L2Block): Promise<`0x${string}`> { - return `0x${(await Blob.fromFields(l2Block.body.toBlobFields())) - .getEthVersionedBlobHash() - .toString('hex')}` as `0x${string}`; +async function makeVersionedBlobHashes(l2Block: L2Block): Promise<`0x${string}`[]> { + const blobHashes = (await Blob.getBlobs(l2Block.body.toBlobFields())).map(b => b.getEthVersionedBlobHash()); + return blobHashes.map(h => `0x${h.toString('hex')}` as `0x${string})`); } /** * Blob response to be returned from the blob sink based on the expected block. * @param block - The block. - * @returns The blob. + * @returns The blobs. */ -function makeBlobFromBlock(block: L2Block) { - const blob = block.body.toBlobFields(); - return Blob.fromFields(blob); +async function makeBlobsFromBlock(block: L2Block) { + return await Blob.getBlobs(block.body.toBlobFields()); } From 679497f3366ceb3d95528d88a01fc59d8d05bf85 Mon Sep 17 00:00:00 2001 From: MirandaWood Date: Tue, 4 Feb 2025 11:57:37 +0000 Subject: [PATCH 3/8] chore: declare inside statements --- .../crates/rollup-lib/src/components.nr | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr index a5347bdc5d91..ee2f75b3953d 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr @@ -225,7 +225,6 @@ fn get_tx_effects_hash_input( let mut offset = 0; let mut array_len = 0; - let mut check_elt = true; // NB: for publishing fields of blob data we use the first element of the blob to encode: // TX_START_PREFIX | 0 | txlen[0] txlen[1] | 0 | REVERT_CODE_PREFIX | 0 | revert_code @@ -247,6 +246,7 @@ fn get_tx_effects_hash_input( // NOTE HASHES array_len = array_length(note_hashes); if array_len != 0 { + let mut check_elt = true; let notes_prefix = encode_blob_prefix(NOTES_PREFIX, array_len); assert_eq(tx_effects_hash_input[offset], notes_prefix); offset += 1; @@ -263,7 +263,7 @@ fn get_tx_effects_hash_input( // NULLIFIERS array_len = array_length(nullifiers); if array_len != 0 { - check_elt = true; + let mut check_elt = true; let nullifiers_prefix = encode_blob_prefix(NULLIFIERS_PREFIX, array_len); assert_eq(tx_effects_hash_input[offset], nullifiers_prefix); offset += 1; @@ -280,7 +280,7 @@ fn get_tx_effects_hash_input( // L2 TO L1 MESSAGES array_len = array_length(tx_effect.l2_to_l1_msgs); if array_len != 0 { - check_elt = true; + let mut check_elt = true; let l2_to_l1_msgs_prefix = encode_blob_prefix(L2_L1_MSGS_PREFIX, array_len); assert_eq(tx_effects_hash_input[offset], l2_to_l1_msgs_prefix); offset += 1; @@ -297,7 +297,7 @@ fn get_tx_effects_hash_input( // PUBLIC DATA UPDATE REQUESTS array_len = array_length(public_data_update_requests); if array_len != 0 { - check_elt = true; + let mut check_elt = true; let public_data_update_requests_prefix = encode_blob_prefix(PUBLIC_DATA_UPDATE_REQUESTS_PREFIX, array_len * 2); assert_eq(tx_effects_hash_input[offset], public_data_update_requests_prefix); @@ -322,7 +322,7 @@ fn get_tx_effects_hash_input( // PRIVATE_LOGS array_len = array_length(private_logs) * PRIVATE_LOG_SIZE_IN_FIELDS; if array_len != 0 { - check_elt = true; + let mut check_elt = true; let private_logs_prefix = encode_blob_prefix(PRIVATE_LOGS_PREFIX, array_len); assert_eq(tx_effects_hash_input[offset], private_logs_prefix); offset += 1; @@ -342,7 +342,7 @@ fn get_tx_effects_hash_input( // PUBLIC LOGS array_len = array_length(public_logs) * PUBLIC_LOG_SIZE_IN_FIELDS; if array_len != 0 { - check_elt = true; + let mut check_elt = true; let public_logs_prefix = encode_blob_prefix(PUBLIC_LOGS_PREFIX, array_len); assert_eq(tx_effects_hash_input[offset], public_logs_prefix); offset += 1; @@ -364,7 +364,7 @@ fn get_tx_effects_hash_input( // CONTRACT CLASS LOGS array_len = array_length(contract_class_logs); if array_len != 0 { - check_elt = true; + let mut check_elt = true; let contract_class_logs_prefix = encode_blob_prefix(CONTRACT_CLASS_LOGS_PREFIX, array_len); assert_eq(tx_effects_hash_input[offset], contract_class_logs_prefix); offset += 1; From 89ab63fc883d297e04bade38101d718e6ec0a4cd Mon Sep 17 00:00:00 2001 From: MirandaWood Date: Tue, 4 Feb 2025 11:30:44 +0000 Subject: [PATCH 4/8] feat: improve array_merge and remove a warning --- .../crates/types/src/hash.nr | 2 +- .../crates/types/src/utils/arrays.nr | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr index 78f1b38318c7..6c4c0be2c6fb 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr @@ -16,7 +16,7 @@ use crate::{ merkle_tree::root::root_from_sibling_path, messaging::l2_to_l1_message::{L2ToL1Message, ScopedL2ToL1Message}, poseidon2::Poseidon2Sponge, - traits::{FromField, Hash, is_empty, ToField}, + traits::{FromField, Hash, ToField}, utils::field::field_from_bytes_32_trunc, }; use super::{constants::TWO_POW_64, utils::{arrays::array_concat, field::field_from_bytes}}; diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr index 44a76b077121..1008724d8499 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr @@ -137,6 +137,23 @@ pub fn array_concat(array1: [T; N], array2: [T; M]) - } pub fn array_merge(array1: [T; N], array2: [T; N]) -> [T; N] +where + T: Empty + Eq, +{ + /// Safety: we constrain this array below + let result = unsafe { array_merge_helper(array1, array2) }; + let array1_len = array_length(array1); + for i in 0..N { + if !is_empty(array1[i]) { + assert_eq(result[i], array1[i]); + } else { + assert_eq(result[i], array2[i - array1_len]); + } + } + result +} + +unconstrained fn array_merge_helper(array1: [T; N], array2: [T; N]) -> [T; N] where T: Empty + Eq, { From c4ad2e29e7069778ecf03a5244f929563cb6b4cf Mon Sep 17 00:00:00 2001 From: MirandaWood Date: Tue, 4 Feb 2025 11:52:21 +0000 Subject: [PATCH 5/8] feat: oops more array_merge improvement/comments --- .../crates/rollup-lib/src/components.nr | 3 +-- .../noir-protocol-circuits/crates/types/src/utils/arrays.nr | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr index ee2f75b3953d..5a7aaaf6e17c 100644 --- a/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr +++ b/noir-projects/noir-protocol-circuits/crates/rollup-lib/src/components.nr @@ -84,7 +84,6 @@ pub fn accumulate_blocks_fees( // TODO(Miranda): combine fees with same recipient depending on rollup structure // Assuming that the final rollup tree (block root -> block merge -> root) has max 32 leaves (TODO: constrain in root), then // in the worst case, we would be checking the left 16 values (left_len = 16) against the right 16 (right_len = 16). - // Either way, construct arr in unconstrained and make use of hints to point to merged fee array. array_merge(left.fees, right.fees) } @@ -101,7 +100,7 @@ pub fn accumulate_blob_public_inputs( left_len + right_len <= AZTEC_MAX_EPOCH_DURATION, "too many blob public input structs accumulated in rollup", ); - // NB: For some reason, the below is around 150k gates cheaper than array_merge + // NB: The below is cheaper than array_merge because assigning BlockBlobPublicInputs is cheaper than calling .equals let mut add_from_left = true; let mut result = [BlockBlobPublicInputs::empty(); AZTEC_MAX_EPOCH_DURATION]; for i in 0..result.len() { diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr index 1008724d8499..96eacd9ce965 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr @@ -142,9 +142,12 @@ where { /// Safety: we constrain this array below let result = unsafe { array_merge_helper(array1, array2) }; + // We assume arrays have been validated. The only use cases so far are with previously validated arrays. let array1_len = array_length(array1); + let mut add_from_left = true; for i in 0..N { - if !is_empty(array1[i]) { + add_from_left &= i != array1_len; + if add_from_left { assert_eq(result[i], array1[i]); } else { assert_eq(result[i], array2[i - array1_len]); From 7dc9065815be4b912c26b93fd429de38e5e3bca7 Mon Sep 17 00:00:00 2001 From: MirandaWood Date: Tue, 4 Feb 2025 12:12:50 +0000 Subject: [PATCH 6/8] chore: add back import (the nr warning lied to me) --- noir-projects/noir-protocol-circuits/crates/types/src/hash.nr | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr index 6c4c0be2c6fb..78f1b38318c7 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/hash.nr @@ -16,7 +16,7 @@ use crate::{ merkle_tree::root::root_from_sibling_path, messaging::l2_to_l1_message::{L2ToL1Message, ScopedL2ToL1Message}, poseidon2::Poseidon2Sponge, - traits::{FromField, Hash, ToField}, + traits::{FromField, Hash, is_empty, ToField}, utils::field::field_from_bytes_32_trunc, }; use super::{constants::TWO_POW_64, utils::{arrays::array_concat, field::field_from_bytes}}; From e3430181a0bc79ef184348355a7212d55f82be90 Mon Sep 17 00:00:00 2001 From: MirandaWood Date: Fri, 7 Feb 2025 09:34:12 +0000 Subject: [PATCH 7/8] chore: cleanup after merge --- yarn-project/blob-lib/src/blob.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/yarn-project/blob-lib/src/blob.ts b/yarn-project/blob-lib/src/blob.ts index cacac62730ef..4f0810b26da5 100644 --- a/yarn-project/blob-lib/src/blob.ts +++ b/yarn-project/blob-lib/src/blob.ts @@ -1,8 +1,8 @@ -// Importing directly from 'c-kzg' does not work, ignoring import/no-named-as-default-member err: import { poseidon2Hash, sha256 } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize'; +// Importing directly from 'c-kzg' does not work, ignoring import/no-named-as-default-member err: import cKzg from 'c-kzg'; import type { Blob as BlobBuffer } from 'c-kzg'; @@ -172,7 +172,13 @@ export class Blob { * @returns The encoded fields from the blobs. */ static toEncodedFields(blobs: Blob[]): Fr[] { - return deserializeEncodedBlobToFields(Buffer.concat(blobs.map(b => b.data))); + try { + return deserializeEncodedBlobToFields(Buffer.concat(blobs.map(b => b.data))); + } catch (err) { + throw new BlobDeserializationError( + `Failed to deserialize encoded blob fields, this blob was likely not created by us`, + ); + } } /** From 55949bbd6d66dac446bc4e4875f35d317dcabea8 Mon Sep 17 00:00:00 2001 From: Miranda Wood Date: Wed, 12 Feb 2025 18:34:22 +0000 Subject: [PATCH 8/8] docs: add comment (ty Tom) Co-authored-by: Tom French <15848336+TomAFrench@users.noreply.github.com> --- .../noir-protocol-circuits/crates/types/src/utils/arrays.nr | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr index 96eacd9ce965..67b27f858c09 100644 --- a/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr +++ b/noir-projects/noir-protocol-circuits/crates/types/src/utils/arrays.nr @@ -135,7 +135,8 @@ pub fn array_concat(array1: [T; N], array2: [T; M]) - } result } - +/// This function assumes that `array1` and `array2` contain no more than N non-empty elements between them, +/// if this is not the case then elements from the end of `array2` will be dropped. pub fn array_merge(array1: [T; N], array2: [T; N]) -> [T; N] where T: Empty + Eq,