diff --git a/barretenberg/cpp/src/barretenberg/vm2/common/avm_inputs.hpp b/barretenberg/cpp/src/barretenberg/vm2/common/avm_inputs.hpp index 546f7eede956..0d4c30c57499 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/common/avm_inputs.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/common/avm_inputs.hpp @@ -193,6 +193,20 @@ template struct SequentialInsertHint { MSGPACK_FIELDS(hintKey, treeId, leaf, lowLeavesWitnessData, insertionWitnessData, stateAfter); }; +// Hint for MerkleTreeDB.appendLeaves. +// Note: only supported for NOTE_HASH_TREE and L1_TO_L2_MESSAGE_TREE. +struct AppendLeavesHint { + AppendOnlyTreeSnapshot hintKey; + AppendOnlyTreeSnapshot stateAfter; + // params + world_state::MerkleTreeId treeId; + std::vector leaves; + + bool operator==(const AppendLeavesHint& other) const = default; + + MSGPACK_FIELDS(hintKey, stateAfter, treeId, leaves); +}; + struct CheckpointActionNoStateChangeHint { // key uint32_t actionCounter; @@ -286,6 +300,7 @@ struct ExecutionHints { std::vector getLeafValueHints; std::vector> sequentialInsertHintsPublicDataTree; std::vector> sequentialInsertHintsNullifierTree; + std::vector appendLeavesHints; std::vector createCheckpointHints; std::vector commitCheckpointHints; std::vector revertCheckpointHints; @@ -303,6 +318,7 @@ struct ExecutionHints { getLeafValueHints, sequentialInsertHintsPublicDataTree, sequentialInsertHintsNullifierTree, + appendLeavesHints, createCheckpointHints, commitCheckpointHints, revertCheckpointHints); diff --git a/barretenberg/cpp/src/barretenberg/vm2/common/avm_inputs.testdata.bin b/barretenberg/cpp/src/barretenberg/vm2/common/avm_inputs.testdata.bin index f868b34c446b..b39137336c36 100644 Binary files a/barretenberg/cpp/src/barretenberg/vm2/common/avm_inputs.testdata.bin and b/barretenberg/cpp/src/barretenberg/vm2/common/avm_inputs.testdata.bin differ diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.cpp index 38872764e3e7..9867295acb77 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/execution.cpp @@ -119,7 +119,7 @@ ExecutionResult Execution::execute_internal(ContextInterface& context) // Go from a wire instruction to an execution opcode. const WireInstructionSpec& wire_spec = instruction_info_db.get(instruction.opcode); context.set_next_pc(pc + wire_spec.size_in_bytes); - info("@", pc, " ", instruction.to_string()); + debug("@", pc, " ", instruction.to_string()); ExecutionOpCode opcode = wire_spec.exec_opcode; ex_event.opcode = opcode; diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/db_interfaces.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/db_interfaces.hpp index f111de945288..3f82c1b63eba 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/db_interfaces.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/db_interfaces.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include "barretenberg/crypto/merkle_tree/hash_path.hpp" #include "barretenberg/crypto/merkle_tree/indexed_tree/indexed_leaf.hpp" @@ -44,6 +45,7 @@ class LowLevelMerkleDBInterface { insert_indexed_leaves_public_data_tree(const crypto::merkle_tree::PublicDataLeafValue& leaf_value) = 0; virtual world_state::SequentialInsertionResult insert_indexed_leaves_nullifier_tree(const crypto::merkle_tree::NullifierLeafValue& leaf_value) = 0; + virtual void append_leaves(world_state::MerkleTreeId tree_id, std::span leaves) = 0; virtual void create_checkpoint() = 0; virtual void commit_checkpoint() = 0; diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/raw_data_dbs.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/raw_data_dbs.cpp index 5fc1761a8004..127ff06dd608 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/raw_data_dbs.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/raw_data_dbs.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include @@ -129,6 +130,8 @@ HintedRawMerkleDB::HintedRawMerkleDB(const ExecutionHints& hints, const TreeSnap hints.sequentialInsertHintsPublicDataTree.size(), "\n * sequential_insert_hints_nullifier_tree: ", hints.sequentialInsertHintsNullifierTree.size(), + "\n * append_leaves_hints: ", + hints.appendLeavesHints.size(), "\n * create_checkpoint_hints: ", hints.createCheckpointHints.size(), "\n * commit_checkpoint_hints: ", @@ -183,6 +186,12 @@ HintedRawMerkleDB::HintedRawMerkleDB(const ExecutionHints& hints, const TreeSnap sequential_insert_hints_nullifier_tree[key] = sequential_insert_hint; } + for (const auto& append_leaves_hint : hints.appendLeavesHints) { + // Convert the span from the hint to a vector for the key + AppendLeavesHintKey key = { append_leaves_hint.hintKey, append_leaves_hint.treeId, append_leaves_hint.leaves }; + append_leaves_hints[key] = append_leaves_hint.stateAfter; + } + for (const auto& create_checkpoint_hint : hints.createCheckpointHints) { create_checkpoint_hints[create_checkpoint_hint.actionCounter] = create_checkpoint_hint; } @@ -324,8 +333,7 @@ world_state::SequentialInsertionResult hint.insertionWitnessData.leaf, hint.insertionWitnessData.index, hint.insertionWitnessData.path); // Evolve state. - tree_roots.publicDataTree.root = hint.stateAfter.root; - tree_roots.publicDataTree.nextAvailableLeafIndex = hint.stateAfter.nextAvailableLeafIndex; + tree_roots.publicDataTree = hint.stateAfter; debug("Evolved state of PUBLIC_DATA_TREE: ", tree_roots.publicDataTree.root, @@ -364,8 +372,7 @@ world_state::SequentialInsertionResult hint.insertionWitnessData.leaf, hint.insertionWitnessData.index, hint.insertionWitnessData.path); // Evolve state. - tree_roots.nullifierTree.root = hint.stateAfter.root; - tree_roots.nullifierTree.nextAvailableLeafIndex = hint.stateAfter.nextAvailableLeafIndex; + tree_roots.nullifierTree = hint.stateAfter; debug("Evolved state of NULLIFIER_TREE: ", tree_roots.nullifierTree.root, @@ -500,4 +507,51 @@ void HintedRawMerkleDB::revert_checkpoint() checkpoint_action_counter++; } +void HintedRawMerkleDB::append_leaves(world_state::MerkleTreeId tree_id, std::span leaves) +{ + if (leaves.empty()) { + return; + } + + auto tree_info = get_tree_info(tree_id); + AppendLeavesHintKey key = { tree_info, tree_id, std::vector(leaves.begin(), leaves.end()) }; + auto it = append_leaves_hints.find(key); + if (it == append_leaves_hints.end()) { + throw std::runtime_error(format("Append leaves hint not found for key (root: ", + tree_info.root, + ", size: ", + tree_info.nextAvailableLeafIndex, + ", tree_id: ", + static_cast(tree_id), + ", leaves: ", + leaves[0], + "..., leaves count: ", + leaves.size(), + ")")); + } + const auto& stateAfter = it->second; + + // Update the tree state based on the hint + switch (tree_id) { + case world_state::MerkleTreeId::NOTE_HASH_TREE: + tree_roots.noteHashTree = stateAfter; + debug("Evolved state of NOTE_HASH_TREE: ", + tree_roots.noteHashTree.root, + " (size: ", + tree_roots.noteHashTree.nextAvailableLeafIndex, + ")"); + break; + case world_state::MerkleTreeId::L1_TO_L2_MESSAGE_TREE: + tree_roots.l1ToL2MessageTree = stateAfter; + debug("Evolved state of L1_TO_L2_MESSAGE_TREE: ", + tree_roots.l1ToL2MessageTree.root, + " (size: ", + tree_roots.l1ToL2MessageTree.nextAvailableLeafIndex, + ")"); + break; + default: + throw std::runtime_error("append_leaves is only supported for NOTE_HASH_TREE and L1_TO_L2_MESSAGE_TREE"); + } +} + } // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/raw_data_dbs.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/raw_data_dbs.hpp index 068fdb42c3ca..210d3baeaec5 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/raw_data_dbs.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/raw_data_dbs.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include @@ -57,6 +58,7 @@ class HintedRawMerkleDB final : public LowLevelMerkleDBInterface { insert_indexed_leaves_public_data_tree(const crypto::merkle_tree::PublicDataLeafValue& leaf_value) override; world_state::SequentialInsertionResult insert_indexed_leaves_nullifier_tree(const crypto::merkle_tree::NullifierLeafValue& leaf_value) override; + void append_leaves(world_state::MerkleTreeId tree_id, std::span leaves) override; void create_checkpoint() override; void commit_checkpoint() override; @@ -94,6 +96,8 @@ class HintedRawMerkleDB final : public LowLevelMerkleDBInterface { unordered_flat_map> sequential_insert_hints_nullifier_tree; + using AppendLeavesHintKey = std::tuple>; + unordered_flat_map append_leaves_hints; unordered_flat_map create_checkpoint_hints; unordered_flat_map commit_checkpoint_hints; unordered_flat_map revert_checkpoint_hints; @@ -102,3 +106,17 @@ class HintedRawMerkleDB final : public LowLevelMerkleDBInterface { }; } // namespace bb::avm2::simulation + +// Specialization of std::hash for std::vector to be used as a key in unordered_flat_map. +namespace std { +template <> struct hash> { + size_t operator()(const std::vector& vec) const + { + size_t seed = vec.size(); + for (const auto& item : vec) { + seed ^= std::hash{}(item) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + } + return seed; + } +}; +} // namespace std diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/memory.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/memory.cpp index 16a34cd746b3..b6e08a9a22bf 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/memory.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/memory.cpp @@ -25,7 +25,7 @@ void Memory::set(MemoryAddress index, MemoryValue value, MemoryTag tag) { // TODO: validate tag-value makes sense. memory[index] = { value, tag }; - vinfo("Memory write: ", index, " <- ", value, " (tag: ", static_cast(tag), ")"); + debug("Memory write: ", index, " <- ", value, " (tag: ", static_cast(tag), ")"); events.emit({ .mode = MemoryMode::WRITE, .addr = index, .value = value, .tag = tag, .space_id = space_id }); } @@ -37,7 +37,7 @@ ValueRefAndTag Memory::get(MemoryAddress index) const const auto& vt = it != memory.end() ? it->second : default_value; events.emit({ .mode = MemoryMode::READ, .addr = index, .value = vt.value, .tag = vt.tag, .space_id = space_id }); - vinfo("Memory read: ", index, " -> ", vt.value, " (tag: ", static_cast(vt.tag), ")"); + debug("Memory read: ", index, " -> ", vt.value, " (tag: ", static_cast(vt.tag), ")"); return { vt.value, vt.tag }; } @@ -53,4 +53,4 @@ std::pair, std::vector> Memory::get_slice(Me return { std::move(values), std::move(tags) }; } -} // namespace bb::avm2::simulation \ No newline at end of file +} // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_dbs.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_dbs.hpp index 6ff31bd2b906..571f60df04d7 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_dbs.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_dbs.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace bb::avm2::simulation { @@ -54,6 +55,7 @@ class MockLowLevelMerkleDB : public LowLevelMerkleDBInterface { insert_indexed_leaves_nullifier_tree, (const crypto::merkle_tree::NullifierLeafValue& leaf_value), (override)); + MOCK_METHOD(void, append_leaves, (world_state::MerkleTreeId tree_id, std::span leaves), (override)); MOCK_METHOD(void, create_checkpoint, (), (override)); MOCK_METHOD(void, commit_checkpoint, (), (override)); MOCK_METHOD(void, revert_checkpoint, (), (override)); diff --git a/yarn-project/simulator/src/public/hinting_db_sources.ts b/yarn-project/simulator/src/public/hinting_db_sources.ts index 8740e0b43c8d..d3732889ffd1 100644 --- a/yarn-project/simulator/src/public/hinting_db_sources.ts +++ b/yarn-project/simulator/src/public/hinting_db_sources.ts @@ -4,6 +4,7 @@ import { type Logger, createLogger } from '@aztec/foundation/log'; import type { IndexedTreeLeafPreimage, SiblingPath } from '@aztec/foundation/trees'; import type { FunctionSelector } from '@aztec/stdlib/abi'; import { + AvmAppendLeavesHint, AvmBytecodeCommitmentHint, AvmCommitCheckpointHint, AvmContractClassHint, @@ -264,6 +265,29 @@ export class HintingPublicTreesDB extends PublicTreesDB { return result; } + public override async appendLeaves( + treeId: ID, + leaves: MerkleTreeLeafType[], + ): Promise { + // Use sequentialInsert for PublicDataTree and NullifierTree. + assert(treeId == MerkleTreeId.NOTE_HASH_TREE || treeId == MerkleTreeId.L1_TO_L2_MESSAGE_TREE); + + if (leaves.length === 0) { + return; + } + + const beforeState = await this.getHintKey(treeId); + + await super.appendLeaves(treeId, leaves); + + const afterState = await this.getHintKey(treeId); + + HintingPublicTreesDB.log.debug('[appendLeaves] Evolved tree state.'); + HintingPublicTreesDB.logTreeChange(beforeState, afterState, treeId); + + this.hints.appendLeavesHints.push(new AvmAppendLeavesHint(beforeState, afterState, treeId, leaves as Fr[])); + } + public override async createCheckpoint(): Promise { const actionCounter = this.checkpointActionCounter++; const oldCheckpointId = this.getCurrentCheckpointId(); diff --git a/yarn-project/stdlib/src/avm/avm.ts b/yarn-project/stdlib/src/avm/avm.ts index cd49411bef77..b1df4fd4b331 100644 --- a/yarn-project/stdlib/src/avm/avm.ts +++ b/yarn-project/stdlib/src/avm/avm.ts @@ -262,6 +262,31 @@ function AvmSequentialInsertHintFactory(klass: IndexedTreeLeafPreimagesClasses) export class AvmSequentialInsertHintPublicDataTree extends AvmSequentialInsertHintFactory(PublicDataTreeLeafPreimage) {} export class AvmSequentialInsertHintNullifierTree extends AvmSequentialInsertHintFactory(NullifierLeafPreimage) {} +// Hint for MerkleTreeDB.appendLeaves. +// Note: only supported for NOTE_HASH_TREE and L1_TO_L2_MESSAGE_TREE. +export class AvmAppendLeavesHint { + constructor( + public readonly hintKey: AppendOnlyTreeSnapshot, + public readonly stateAfter: AppendOnlyTreeSnapshot, + // params + public readonly treeId: MerkleTreeId, + public readonly leaves: Fr[], + ) {} + + static get schema() { + return z + .object({ + hintKey: AppendOnlyTreeSnapshot.schema, + stateAfter: AppendOnlyTreeSnapshot.schema, + treeId: z.number().int().nonnegative(), + leaves: schemas.Fr.array(), + }) + .transform( + ({ hintKey, stateAfter, treeId, leaves }) => new AvmAppendLeavesHint(hintKey, stateAfter, treeId, leaves), + ); + } +} + // Hint for checkpoint actions that don't change the state. class AvmCheckpointActionNoStateChangeHint { constructor( @@ -497,6 +522,7 @@ export class AvmExecutionHints { public readonly getLeafValueHints: AvmGetLeafValueHint[] = [], public readonly sequentialInsertHintsPublicDataTree: AvmSequentialInsertHintPublicDataTree[] = [], public readonly sequentialInsertHintsNullifierTree: AvmSequentialInsertHintNullifierTree[] = [], + public readonly appendLeavesHints: AvmAppendLeavesHint[] = [], public readonly createCheckpointHints: AvmCreateCheckpointHint[] = [], public readonly commitCheckpointHints: AvmCommitCheckpointHint[] = [], public readonly revertCheckpointHints: AvmRevertCheckpointHint[] = [], @@ -520,6 +546,7 @@ export class AvmExecutionHints { getLeafValueHints: AvmGetLeafValueHint.schema.array(), sequentialInsertHintsPublicDataTree: AvmSequentialInsertHintPublicDataTree.schema.array(), sequentialInsertHintsNullifierTree: AvmSequentialInsertHintNullifierTree.schema.array(), + appendLeavesHints: AvmAppendLeavesHint.schema.array(), createCheckpointHints: AvmCreateCheckpointHint.schema.array(), commitCheckpointHints: AvmCommitCheckpointHint.schema.array(), revertCheckpointHints: AvmRevertCheckpointHint.schema.array(), @@ -537,6 +564,7 @@ export class AvmExecutionHints { getLeafValueHints, sequentialInsertHintsPublicDataTree, sequentialInsertHintsNullifierTree, + appendLeavesHints, createCheckpointHints, commitCheckpointHints, revertCheckpointHints, @@ -553,6 +581,7 @@ export class AvmExecutionHints { getLeafValueHints, sequentialInsertHintsPublicDataTree, sequentialInsertHintsNullifierTree, + appendLeavesHints, createCheckpointHints, commitCheckpointHints, revertCheckpointHints, diff --git a/yarn-project/stdlib/src/tests/factories.ts b/yarn-project/stdlib/src/tests/factories.ts index 249695cc1e3c..b5df34a9b512 100644 --- a/yarn-project/stdlib/src/tests/factories.ts +++ b/yarn-project/stdlib/src/tests/factories.ts @@ -55,6 +55,7 @@ import { ContractStorageRead } from '../avm/contract_storage_read.js'; import { ContractStorageUpdateRequest } from '../avm/contract_storage_update_request.js'; import { AvmAccumulatedData, + AvmAppendLeavesHint, AvmBytecodeCommitmentHint, AvmCircuitInputs, AvmCircuitPublicInputs, @@ -1385,6 +1386,16 @@ export function makeAvmSequentialInsertHintNullifierTree(seed = 0): AvmSequentia ); } +export function makeAvmAppendLeavesHint(seed = 0): AvmAppendLeavesHint { + return new AvmAppendLeavesHint( + makeAppendOnlyTreeSnapshot(seed), + makeAppendOnlyTreeSnapshot(seed + 1), + // Use NOTE_HASH_TREE or L1_TO_L2_MESSAGE_TREE as mentioned in the comment on AvmAppendLeavesHint + seed % 2 === 0 ? MerkleTreeId.NOTE_HASH_TREE : MerkleTreeId.L1_TO_L2_MESSAGE_TREE, + makeArray((seed % 5) + 1, i => new Fr(seed + i + 2), 0), + ); +} + export function makeAvmCheckpointActionCreateCheckpointHint(seed = 0): AvmCreateCheckpointHint { return new AvmCreateCheckpointHint( /*actionCounter=*/ seed, @@ -1511,6 +1522,7 @@ export async function makeAvmExecutionHints( makeAvmSequentialInsertHintNullifierTree, seed + 0x5700, ), + appendLeavesHints: makeArray(baseLength + 5, makeAvmAppendLeavesHint, seed + 0x5800), createCheckpointHints: makeArray(baseLength + 5, makeAvmCheckpointActionCreateCheckpointHint, seed + 0x5900), commitCheckpointHints: makeArray(baseLength + 5, makeAvmCheckpointActionCommitCheckpointHint, seed + 0x5b00), revertCheckpointHints: makeArray(baseLength + 5, makeAvmCheckpointActionRevertCheckpointHint, seed + 0x5d00), @@ -1529,6 +1541,7 @@ export async function makeAvmExecutionHints( fields.getLeafValueHints, fields.sequentialInsertHintsPublicDataTree, fields.sequentialInsertHintsNullifierTree, + fields.appendLeavesHints, fields.createCheckpointHints, fields.commitCheckpointHints, fields.revertCheckpointHints,