diff --git a/barretenberg/cpp/src/barretenberg/vm2/common/avm_inputs.hpp b/barretenberg/cpp/src/barretenberg/vm2/common/avm_inputs.hpp index 934876bfbf6f..096a44fa8c9f 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/common/avm_inputs.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/common/avm_inputs.hpp @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include "barretenberg/common/utils.hpp" @@ -36,6 +37,11 @@ struct AppendOnlyTreeSnapshot { std::size_t hash() const noexcept { return utils::hash_as_tuple(root, nextAvailableLeafIndex); } bool operator==(const AppendOnlyTreeSnapshot& other) const = default; + friend std::ostream& operator<<(std::ostream& os, const AppendOnlyTreeSnapshot& obj) + { + os << "root: " << obj.root << ", nextAvailableLeafIndex: " << obj.nextAvailableLeafIndex; + return os; + } MSGPACK_FIELDS(root, nextAvailableLeafIndex); }; @@ -187,6 +193,36 @@ template struct SequentialInsertHint { MSGPACK_FIELDS(hintKey, treeId, leaf, lowLeavesWitnessData, insertionWitnessData, stateAfter); }; +struct CheckpointActionNoStateChangeHint { + // key + uint32_t actionCounter; + // current checkpoint evolution + uint32_t oldCheckpointId; + uint32_t newCheckpointId; + + bool operator==(const CheckpointActionNoStateChangeHint& other) const = default; + + MSGPACK_FIELDS(actionCounter, oldCheckpointId, newCheckpointId); +}; + +using CreateCheckpointHint = CheckpointActionNoStateChangeHint; +using CommitCheckpointHint = CheckpointActionNoStateChangeHint; + +struct RevertCheckpointHint { + // key + uint32_t actionCounter; + // current checkpoint evolution + uint32_t oldCheckpointId; + uint32_t newCheckpointId; + // state evolution + TreeSnapshots stateBefore; + TreeSnapshots stateAfter; + + bool operator==(const RevertCheckpointHint& other) const = default; + + MSGPACK_FIELDS(actionCounter, oldCheckpointId, newCheckpointId, stateBefore, stateAfter); +}; + //////////////////////////////////////////////////////////////////////////// // Hints (other) //////////////////////////////////////////////////////////////////////////// @@ -248,6 +284,9 @@ struct ExecutionHints { std::vector getLeafValueHints; std::vector> sequentialInsertHintsPublicDataTree; std::vector> sequentialInsertHintsNullifierTree; + std::vector createCheckpointHints; + std::vector commitCheckpointHints; + std::vector revertCheckpointHints; bool operator==(const ExecutionHints& other) const = default; @@ -261,7 +300,10 @@ struct ExecutionHints { getLeafPreimageHintsNullifierTree, getLeafValueHints, sequentialInsertHintsPublicDataTree, - sequentialInsertHintsNullifierTree); + sequentialInsertHintsNullifierTree, + createCheckpointHints, + commitCheckpointHints, + revertCheckpointHints); }; //////////////////////////////////////////////////////////////////////////// @@ -278,9 +320,3 @@ struct AvmProvingInputs { }; } // namespace bb::avm2 - -// Define hash function so that they can be used as keys in maps. -// See https://en.cppreference.com/w/cpp/utility/hash. -template <> struct std::hash { - std::size_t operator()(const bb::avm2::AppendOnlyTreeSnapshot& st) const noexcept { return st.hash(); } -}; 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 dd78ef3625fc..4c79d4131070 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/concrete_dbs.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/concrete_dbs.cpp index 07ee695eee17..4b874ca426bf 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/concrete_dbs.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/concrete_dbs.cpp @@ -49,4 +49,19 @@ FF MerkleDB::storage_read(const FF& leaf_slot) const return value; } +void MerkleDB::create_checkpoint() +{ + raw_merkle_db.create_checkpoint(); +} + +void MerkleDB::commit_checkpoint() +{ + raw_merkle_db.commit_checkpoint(); +} + +void MerkleDB::revert_checkpoint() +{ + raw_merkle_db.revert_checkpoint(); +} + } // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/concrete_dbs.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/concrete_dbs.hpp index aa1609345d21..d91afc8d9ab9 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/concrete_dbs.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/concrete_dbs.hpp @@ -46,6 +46,9 @@ class MerkleDB final : public HighLevelMerkleDBInterface { // Unconstrained. const TreeSnapshots& get_tree_roots() const override; + void create_checkpoint() override; + void commit_checkpoint() override; + void revert_checkpoint() override; // Constrained. // TODO: When actually using this, consider siloing inside (and taking a silo gadget in the constructor). 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 ea1a0b8726fc..f111de945288 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/db_interfaces.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/lib/db_interfaces.hpp @@ -40,15 +40,14 @@ class LowLevelMerkleDBInterface { virtual crypto::merkle_tree::IndexedLeaf get_leaf_preimage_nullifier_tree( crypto::merkle_tree::index_t leaf_index) const = 0; - // Inserts a leaf into the public data tree sequentially, getting witnesses at every step. - // Note: This method doesn't support inserting empty leaves. virtual world_state::SequentialInsertionResult insert_indexed_leaves_public_data_tree(const crypto::merkle_tree::PublicDataLeafValue& leaf_value) = 0; - - // Inserts a leaf into the nullifier tree sequentially, getting witnesses at every step. - // Note: This method doesn't support inserting empty leaves. virtual world_state::SequentialInsertionResult insert_indexed_leaves_nullifier_tree(const crypto::merkle_tree::NullifierLeafValue& leaf_value) = 0; + + virtual void create_checkpoint() = 0; + virtual void commit_checkpoint() = 0; + virtual void revert_checkpoint() = 0; }; // High level access to a merkle db. In general these will be constrained. @@ -58,6 +57,11 @@ class HighLevelMerkleDBInterface { virtual const TreeSnapshots& get_tree_roots() const = 0; virtual FF storage_read(const FF& key) const = 0; + + virtual void create_checkpoint() = 0; + virtual void commit_checkpoint() = 0; + virtual void revert_checkpoint() = 0; + virtual LowLevelMerkleDBInterface& as_unconstrained() const = 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 d8fb21700aa0..5fc1761a8004 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 @@ -3,6 +3,7 @@ #include #include #include +#include #include "barretenberg/common/log.hpp" #include "barretenberg/crypto/merkle_tree/indexed_tree/indexed_leaf.hpp" @@ -10,6 +11,22 @@ namespace bb::avm2::simulation { +namespace { + +std::string to_string(const TreeSnapshots& snapshots) +{ + return format("PUBLIC_DATA_TREE: ", + snapshots.publicDataTree, + "\nNULLIFIER_TREE: ", + snapshots.nullifierTree, + "\nNOTE_HASH_TREE: ", + snapshots.noteHashTree, + "\nL1_TO_L2_MESSAGE_TREE: ", + snapshots.l1ToL2MessageTree); +} + +} // namespace + // HintedRawContractDB starts. HintedRawContractDB::HintedRawContractDB(const ExecutionHints& hints) { @@ -98,41 +115,27 @@ HintedRawMerkleDB::HintedRawMerkleDB(const ExecutionHints& hints, const TreeSnap : tree_roots(tree_roots) { vinfo("Initializing HintedRawMerkleDB with...", - "\n * get_sibling_path hints: ", + "\n * get_sibling_path_hints: ", hints.getSiblingPathHints.size(), - "\n * get_previous_value_index hints: ", + "\n * get_previous_value_index_hints: ", hints.getPreviousValueIndexHints.size(), - "\n * get_leaf_preimage hints_public_data_tree: ", + "\n * get_leaf_preimage_hints_public_data_tree: ", hints.getLeafPreimageHintsPublicDataTree.size(), - "\n * get_leaf_preimage hints_nullifier_tree: ", + "\n * get_leaf_preimage_hints_nullifier_tree: ", hints.getLeafPreimageHintsNullifierTree.size(), "\n * get_leaf_value_hints: ", hints.getLeafValueHints.size(), "\n * sequential_insert_hints_public_data_tree: ", hints.sequentialInsertHintsPublicDataTree.size(), "\n * sequential_insert_hints_nullifier_tree: ", - hints.sequentialInsertHintsNullifierTree.size()); - debug("Initializing HintedRawMerkleDB with snapshots...", - "\n * nullifierTree: ", - tree_roots.nullifierTree.root, - " (size: ", - tree_roots.nullifierTree.nextAvailableLeafIndex, - ")", - "\n * publicDataTree: ", - tree_roots.publicDataTree.root, - " (size: ", - tree_roots.publicDataTree.nextAvailableLeafIndex, - ")", - "\n * noteHashTree: ", - tree_roots.noteHashTree.root, - " (size: ", - tree_roots.noteHashTree.nextAvailableLeafIndex, - ")", - "\n * l1ToL2MessageTree: ", - tree_roots.l1ToL2MessageTree.root, - " (size: ", - tree_roots.l1ToL2MessageTree.nextAvailableLeafIndex, - ")"); + hints.sequentialInsertHintsNullifierTree.size(), + "\n * create_checkpoint_hints: ", + hints.createCheckpointHints.size(), + "\n * commit_checkpoint_hints: ", + hints.commitCheckpointHints.size(), + "\n * revert_checkpoint_hints: ", + hints.revertCheckpointHints.size()); + debug("Initializing HintedRawMerkleDB with snapshots...\n", to_string(tree_roots)); for (const auto& get_sibling_path_hint : hints.getSiblingPathHints) { GetSiblingPathKey key = { get_sibling_path_hint.hintKey, @@ -179,6 +182,18 @@ HintedRawMerkleDB::HintedRawMerkleDB(const ExecutionHints& hints, const TreeSnap sequential_insert_hint.leaf }; sequential_insert_hints_nullifier_tree[key] = sequential_insert_hint; } + + for (const auto& create_checkpoint_hint : hints.createCheckpointHints) { + create_checkpoint_hints[create_checkpoint_hint.actionCounter] = create_checkpoint_hint; + } + + for (const auto& commit_checkpoint_hint : hints.commitCheckpointHints) { + commit_checkpoint_hints[commit_checkpoint_hint.actionCounter] = commit_checkpoint_hint; + } + + for (const auto& revert_checkpoint_hint : hints.revertCheckpointHints) { + revert_checkpoint_hints[revert_checkpoint_hint.actionCounter] = revert_checkpoint_hint; + } } const AppendOnlyTreeSnapshot& HintedRawMerkleDB::get_tree_info(world_state::MerkleTreeId tree_id) const @@ -361,4 +376,128 @@ world_state::SequentialInsertionResult return result; } +void HintedRawMerkleDB::create_checkpoint() +{ + auto it = create_checkpoint_hints.find(checkpoint_action_counter); + if (it == create_checkpoint_hints.end()) { + throw std::runtime_error( + format("[create_checkpoint@", checkpoint_action_counter, "] Hint not found for action counter!")); + } + const auto& hint = it->second; + + // Sanity check. + if (hint.oldCheckpointId != checkpoint_stack.top()) { + throw std::runtime_error(format("[create_checkpoint@", + checkpoint_action_counter, + "] Old checkpoint id does not match the current checkpoint id: ", + hint.oldCheckpointId, + " != ", + checkpoint_stack.top())); + } + + debug("[create_checkpoint@", + checkpoint_action_counter, + "] Checkpoint evolved ", + hint.oldCheckpointId, + " -> ", + hint.newCheckpointId); + + checkpoint_stack.push(hint.newCheckpointId); + checkpoint_action_counter++; +} + +void HintedRawMerkleDB::commit_checkpoint() +{ + auto it = commit_checkpoint_hints.find(checkpoint_action_counter); + if (it == commit_checkpoint_hints.end()) { + throw std::runtime_error( + format("[commit_checkpoint@", checkpoint_action_counter, "] Hint not found for action counter!")); + } + const auto& hint = it->second; + + // Sanity check. + if (hint.oldCheckpointId != checkpoint_stack.top()) { + throw std::runtime_error(format("[commit_checkpoint@", + checkpoint_action_counter, + "] Old checkpoint id does not match the current checkpoint id: ", + hint.oldCheckpointId, + " != ", + checkpoint_stack.top())); + } + + checkpoint_stack.pop(); + + // Sanity check. + if (hint.newCheckpointId != checkpoint_stack.top()) { + throw std::runtime_error(format("[commit_checkpoint@", + checkpoint_action_counter, + "] New checkpoint id does not match the current checkpoint id: ", + hint.newCheckpointId, + " != ", + checkpoint_stack.top())); + } + + debug("[commit_checkpoint@", + checkpoint_action_counter, + "] Checkpoint evolved ", + hint.oldCheckpointId, + " -> ", + hint.newCheckpointId); + + checkpoint_action_counter++; +} + +void HintedRawMerkleDB::revert_checkpoint() +{ + auto it = revert_checkpoint_hints.find(checkpoint_action_counter); + if (it == revert_checkpoint_hints.end()) { + throw std::runtime_error( + format("[revert_checkpoint@", checkpoint_action_counter, "] Hint not found for action counter!")); + } + const auto& hint = it->second; + + // Sanity check of checkpoint stack. + if (hint.oldCheckpointId != checkpoint_stack.top()) { + throw std::runtime_error(format("[revert_checkpoint@", + checkpoint_action_counter, + "] Old checkpoint id does not match the current checkpoint id: ", + hint.oldCheckpointId, + " != ", + checkpoint_stack.top())); + } + + // Sanity check of tree snapshots. + if (hint.stateBefore != tree_roots) { + vinfo("Hint tree snapshots: ", to_string(hint.stateBefore)); + vinfo("Current tree roots: ", to_string(tree_roots)); + throw std::runtime_error(format("[revert_checkpoint@", + checkpoint_action_counter, + "] Hint tree snapshots do not match the current tree roots.")); + } + + checkpoint_stack.pop(); + + // Sanity check. + if (hint.newCheckpointId != checkpoint_stack.top()) { + throw std::runtime_error(format("[revert_checkpoint@", + checkpoint_action_counter, + "] New checkpoint id does not match the current checkpoint id: ", + hint.newCheckpointId, + " != ", + checkpoint_stack.top())); + } + + // Evolve trees. + tree_roots = hint.stateAfter; + + debug("[revert_checkpoint@", + checkpoint_action_counter, + "] Checkpoint evolved ", + hint.oldCheckpointId, + " -> ", + hint.newCheckpointId); + + checkpoint_action_counter++; +} + } // 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 74d3e02e8fb9..068fdb42c3ca 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 "barretenberg/crypto/merkle_tree/hash_path.hpp" @@ -57,8 +58,16 @@ class HintedRawMerkleDB final : public LowLevelMerkleDBInterface { world_state::SequentialInsertionResult insert_indexed_leaves_nullifier_tree(const crypto::merkle_tree::NullifierLeafValue& leaf_value) override; + void create_checkpoint() override; + void commit_checkpoint() override; + void revert_checkpoint() override; + private: TreeSnapshots tree_roots; + uint32_t checkpoint_action_counter = 0; + // We start with a checkpoint id of 0, which is the assumed initial state checkpoint. + // This stack is for debugging purposes only. + std::stack checkpoint_stack{ { 0 } }; // Query hints. using GetSiblingPathKey = @@ -85,6 +94,9 @@ class HintedRawMerkleDB final : public LowLevelMerkleDBInterface { unordered_flat_map> sequential_insert_hints_nullifier_tree; + unordered_flat_map create_checkpoint_hints; + unordered_flat_map commit_checkpoint_hints; + unordered_flat_map revert_checkpoint_hints; const AppendOnlyTreeSnapshot& get_tree_info(world_state::MerkleTreeId tree_id) const; }; 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 08c6873a9b1e..6ff31bd2b906 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_dbs.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/testing/mock_dbs.hpp @@ -54,6 +54,9 @@ class MockLowLevelMerkleDB : public LowLevelMerkleDBInterface { insert_indexed_leaves_nullifier_tree, (const crypto::merkle_tree::NullifierLeafValue& leaf_value), (override)); + MOCK_METHOD(void, create_checkpoint, (), (override)); + MOCK_METHOD(void, commit_checkpoint, (), (override)); + MOCK_METHOD(void, revert_checkpoint, (), (override)); }; class MockHighLevelMerkleDB : public HighLevelMerkleDBInterface { @@ -62,8 +65,13 @@ class MockHighLevelMerkleDB : public HighLevelMerkleDBInterface { MockHighLevelMerkleDB(); ~MockHighLevelMerkleDB() override; - MOCK_METHOD(FF, storage_read, (const FF& key), (const, override)); MOCK_METHOD(const TreeSnapshots&, get_tree_roots, (), (const, override)); + MOCK_METHOD(FF, storage_read, (const FF& key), (const, override)); + + MOCK_METHOD(void, create_checkpoint, (), (override)); + MOCK_METHOD(void, commit_checkpoint, (), (override)); + MOCK_METHOD(void, revert_checkpoint, (), (override)); + MOCK_METHOD(LowLevelMerkleDBInterface&, as_unconstrained, (), (const, override)); }; diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/tx_execution.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/tx_execution.cpp index d20d77e36cde..dbf61b8ca100 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/tx_execution.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/tx_execution.cpp @@ -13,9 +13,10 @@ void TxExecution::simulate(const Tx& tx) tx.teardownEnqueuedCall ? "1 teardown enqueued call" : "no teardown enqueued call"); // TODO: This method is currently wrong. We need to lift the context to this level. + // TODO: Checkpointing is not yet correctly implemented. // Insert non-revertibles. - // TODO: We need a context at this level to be able to do the insertions. + insert_non_revertibles(tx); // Setup. for (const auto& call : tx.setupEnqueuedCalls) { @@ -25,7 +26,7 @@ void TxExecution::simulate(const Tx& tx) } // Insert revertibles. - // TODO: We need a context at this level to be able to do the insertions. + insert_revertibles(tx); // App logic. for (const auto& call : tx.appLogicEnqueuedCalls) { @@ -55,4 +56,19 @@ std::unique_ptr TxExecution::make_enqueued_context(AztecAddres return execution_provider.make_enqueued_context(address, msg_sender, calldata, is_static); } +void TxExecution::insert_non_revertibles(const Tx&) +{ + // 1. Write the already siloed nullifiers. + // 2. Write the note hashes. + // 3. Write the new contracts. +} + +void TxExecution::insert_revertibles(const Tx&) +{ + merkle_db.create_checkpoint(); + // 1. Write the nullifiers. + // 2. Write the note hashes. + // 3. Write the new contracts. +} + } // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation/tx_execution.hpp b/barretenberg/cpp/src/barretenberg/vm2/simulation/tx_execution.hpp index 7a3c8b9541a3..cac962ed6e3d 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation/tx_execution.hpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation/tx_execution.hpp @@ -2,14 +2,18 @@ #include "barretenberg/vm2/common/avm_inputs.hpp" #include "barretenberg/vm2/simulation/execution.hpp" +#include "barretenberg/vm2/simulation/lib/db_interfaces.hpp" namespace bb::avm2::simulation { // In charge of executing a transaction. class TxExecution final { public: - TxExecution(ExecutionInterface& call_execution) - : call_execution(call_execution){}; + // FIXME: mekle db here should only be temporary, we should access via context. + TxExecution(ExecutionInterface& call_execution, HighLevelMerkleDBInterface& merkle_db) + : call_execution(call_execution) + , merkle_db(merkle_db) + {} void simulate(const Tx& tx); @@ -21,7 +25,10 @@ class TxExecution final { private: ExecutionInterface& call_execution; // More things need to be lifted into the tx execution?? - // MerkleDB + HighLevelMerkleDBInterface& merkle_db; + + void insert_non_revertibles(const Tx& tx); + void insert_revertibles(const Tx& tx); }; } // namespace bb::avm2::simulation diff --git a/barretenberg/cpp/src/barretenberg/vm2/simulation_helper.cpp b/barretenberg/cpp/src/barretenberg/vm2/simulation_helper.cpp index ea40d29726a5..c5b6e4436c3a 100644 --- a/barretenberg/cpp/src/barretenberg/vm2/simulation_helper.cpp +++ b/barretenberg/cpp/src/barretenberg/vm2/simulation_helper.cpp @@ -125,7 +125,7 @@ template EventsContainer AvmSimulationHelper::simulate_with_setting Alu alu(alu_emitter); Execution execution(alu, execution_components, instruction_info_db, execution_emitter, context_stack_emitter); - TxExecution tx_execution(execution); + TxExecution tx_execution(execution, merkle_db); Sha256 sha256(sha256_compression_emitter); tx_execution.simulate(inputs.hints.tx); diff --git a/yarn-project/simulator/src/public/hinting_db_sources.ts b/yarn-project/simulator/src/public/hinting_db_sources.ts index 8274fa6f6ea9..8740e0b43c8d 100644 --- a/yarn-project/simulator/src/public/hinting_db_sources.ts +++ b/yarn-project/simulator/src/public/hinting_db_sources.ts @@ -1,17 +1,21 @@ +import { sha256Trunc } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; import { type Logger, createLogger } from '@aztec/foundation/log'; import type { IndexedTreeLeafPreimage, SiblingPath } from '@aztec/foundation/trees'; import type { FunctionSelector } from '@aztec/stdlib/abi'; import { AvmBytecodeCommitmentHint, + AvmCommitCheckpointHint, AvmContractClassHint, AvmContractInstanceHint, + AvmCreateCheckpointHint, type AvmExecutionHints, AvmGetLeafPreimageHintNullifierTree, AvmGetLeafPreimageHintPublicDataTree, AvmGetLeafValueHint, AvmGetPreviousValueIndexHint, AvmGetSiblingPathHint, + AvmRevertCheckpointHint, AvmSequentialInsertHintNullifierTree, AvmSequentialInsertHintPublicDataTree, } from '@aztec/stdlib/avm'; @@ -28,6 +32,7 @@ import { PublicDataTreeLeafPreimage, type SequentialInsertionResult, getTreeName, + merkleTreeIds, } from '@aztec/stdlib/trees'; import { strict as assert } from 'assert'; @@ -99,6 +104,13 @@ export class HintingPublicContractsDB implements PublicContractsDBInterface { */ export class HintingPublicTreesDB extends PublicTreesDB { private static readonly log: Logger = createLogger('HintingPublicTreesDB'); + // This stack is only for debugging purposes. + // The top of the stack is the current checkpoint id. + // We need the stack to be non-empty and use 0 as an arbitrary initial checkpoint id. + // This is not necessarily a checkpoint that happened, but whatever tree state we start with. + private checkpointStack: number[] = [0]; + private nextCheckpointId: number = 1; + private checkpointActionCounter: number = 0; // yes, a side-effect counter. constructor(db: PublicTreesDB, private hints: AvmExecutionHints) { super(db); @@ -200,11 +212,8 @@ export class HintingPublicTreesDB extends PublicTreesDB { const result = await super.sequentialInsert(treeId, leaves); const afterState = await this.getHintKey(treeId); - HintingPublicTreesDB.log.debug( - `Evolved tree state (${getTreeName(treeId)}): ${beforeState.root}, ${beforeState.nextAvailableLeafIndex} -> ${ - afterState.root - }, ${afterState.nextAvailableLeafIndex}.`, - ); + HintingPublicTreesDB.log.debug('[sequentialInsert] Evolved tree state.'); + HintingPublicTreesDB.logTreeChange(beforeState, afterState, treeId); switch (treeId) { case MerkleTreeId.PUBLIC_DATA_TREE: @@ -255,12 +264,44 @@ export class HintingPublicTreesDB extends PublicTreesDB { return result; } + public override async createCheckpoint(): Promise { + const actionCounter = this.checkpointActionCounter++; + const oldCheckpointId = this.getCurrentCheckpointId(); + const treesStateHash = await this.getTreesStateHash(); + + await super.createCheckpoint(); + this.checkpointStack.push(this.nextCheckpointId++); + const newCheckpointId = this.getCurrentCheckpointId(); + + this.hints.createCheckpointHints.push(new AvmCreateCheckpointHint(actionCounter, oldCheckpointId, newCheckpointId)); + + HintingPublicTreesDB.log.debug( + `[createCheckpoint:${actionCounter}] Checkpoint evolved ${oldCheckpointId} -> ${newCheckpointId} at trees state ${treesStateHash}.`, + ); + } + + public override async commitCheckpoint(): Promise { + const actionCounter = this.checkpointActionCounter++; + const oldCheckpointId = this.getCurrentCheckpointId(); + const treesStateHash = await this.getTreesStateHash(); + + await super.commitCheckpoint(); + this.checkpointStack.pop(); + const newCheckpointId = this.getCurrentCheckpointId(); + + this.hints.commitCheckpointHints.push(new AvmCommitCheckpointHint(actionCounter, oldCheckpointId, newCheckpointId)); + + HintingPublicTreesDB.log.debug( + `[commitCheckpoint:${actionCounter}] Checkpoint evolved ${oldCheckpointId} -> ${newCheckpointId} at trees state ${treesStateHash}.`, + ); + } + public override async revertCheckpoint(): Promise { - HintingPublicTreesDB.log.debug('revertCheckpoint not hinted yet!'); - // TODO(fcarreiro): we probably want to hint on StateReference hash. - // WARNING: is this enough? we might actually need the number of the checkpoint or similar... - // We will need to keep a stack of checkpoints on the C++ side. - const beforeState = { + const actionCounter = this.checkpointActionCounter++; + const oldCheckpointId = this.getCurrentCheckpointId(); + const treesStateHash = await this.getTreesStateHash(); + + const beforeState: Record = { [MerkleTreeId.PUBLIC_DATA_TREE]: await this.getHintKey(MerkleTreeId.PUBLIC_DATA_TREE), [MerkleTreeId.NULLIFIER_TREE]: await this.getHintKey(MerkleTreeId.NULLIFIER_TREE), [MerkleTreeId.NOTE_HASH_TREE]: await this.getHintKey(MerkleTreeId.NOTE_HASH_TREE), @@ -269,8 +310,10 @@ export class HintingPublicTreesDB extends PublicTreesDB { }; await super.revertCheckpoint(); + this.checkpointStack.pop(); + const newCheckpointId = this.getCurrentCheckpointId(); - const afterState = { + const afterState: Record = { [MerkleTreeId.PUBLIC_DATA_TREE]: await this.getHintKey(MerkleTreeId.PUBLIC_DATA_TREE), [MerkleTreeId.NULLIFIER_TREE]: await this.getHintKey(MerkleTreeId.NULLIFIER_TREE), [MerkleTreeId.NOTE_HASH_TREE]: await this.getHintKey(MerkleTreeId.NOTE_HASH_TREE), @@ -278,13 +321,15 @@ export class HintingPublicTreesDB extends PublicTreesDB { [MerkleTreeId.ARCHIVE]: await this.getHintKey(MerkleTreeId.ARCHIVE), }; - HintingPublicTreesDB.log.debug('Evolved tree state:'); - for (const treeId of Object.keys(beforeState)) { - const id: MerkleTreeId = treeId as unknown as MerkleTreeId; - const treeName = getTreeName(id); - HintingPublicTreesDB.log.debug( - `${treeName}: ${beforeState[id].root}, ${beforeState[id].nextAvailableLeafIndex} -> ${afterState[id].root}, ${afterState[id].nextAvailableLeafIndex}.`, - ); + this.hints.revertCheckpointHints.push( + AvmRevertCheckpointHint.create(actionCounter, oldCheckpointId, newCheckpointId, beforeState, afterState), + ); + + HintingPublicTreesDB.log.debug( + `[revertCheckpoint:${actionCounter}] Checkpoint evolved ${oldCheckpointId} -> ${newCheckpointId} at trees state ${treesStateHash}.`, + ); + for (const treeId of merkleTreeIds()) { + HintingPublicTreesDB.logTreeChange(beforeState[treeId], afterState[treeId], treeId); } } @@ -293,4 +338,25 @@ export class HintingPublicTreesDB extends PublicTreesDB { const treeInfo = await super.getTreeInfo(treeId); return new AppendOnlyTreeSnapshot(Fr.fromBuffer(treeInfo.root), Number(treeInfo.size)); } + + private getCurrentCheckpointId(): number { + return this.checkpointStack[this.checkpointStack.length - 1]; + } + + // For logging/debugging purposes. + private async getTreesStateHash(): Promise { + const stateReferenceFields = (await super.getStateReference()).toFields(); + return Fr.fromBuffer(sha256Trunc(Buffer.concat(stateReferenceFields.map(field => field.toBuffer())))); + } + + private static logTreeChange( + beforeState: AppendOnlyTreeSnapshot, + afterState: AppendOnlyTreeSnapshot, + treeId: MerkleTreeId, + ) { + const treeName = getTreeName(treeId); + HintingPublicTreesDB.log.debug( + `[${treeName}] Evolved tree state: ${beforeState.root}, ${beforeState.nextAvailableLeafIndex} -> ${afterState.root}, ${afterState.nextAvailableLeafIndex}.`, + ); + } } diff --git a/yarn-project/stdlib/src/avm/avm.ts b/yarn-project/stdlib/src/avm/avm.ts index 6bcf0c808c41..cf25670366b3 100644 --- a/yarn-project/stdlib/src/avm/avm.ts +++ b/yarn-project/stdlib/src/avm/avm.ts @@ -10,7 +10,7 @@ import { AppendOnlyTreeSnapshot } from '../trees/append_only_tree_snapshot.js'; import { MerkleTreeId } from '../trees/merkle_tree_id.js'; import { NullifierLeafPreimage } from '../trees/nullifier_leaf.js'; import { PublicDataTreeLeafPreimage } from '../trees/public_data_leaf.js'; -import type { Tx } from '../tx/index.js'; +import { TreeSnapshots, type Tx } from '../tx/index.js'; import { AvmCircuitPublicInputs } from './avm_circuit_public_inputs.js'; import { serializeWithMessagePack } from './message_pack.js'; @@ -262,6 +262,92 @@ function AvmSequentialInsertHintFactory(klass: IndexedTreeLeafPreimagesClasses) export class AvmSequentialInsertHintPublicDataTree extends AvmSequentialInsertHintFactory(PublicDataTreeLeafPreimage) {} export class AvmSequentialInsertHintNullifierTree extends AvmSequentialInsertHintFactory(NullifierLeafPreimage) {} +// Hint for checkpoint actions that don't change the state. +class AvmCheckpointActionNoStateChangeHint { + constructor( + // key + public readonly actionCounter: number, + // current checkpoint evolution + public readonly oldCheckpointId: number, + public readonly newCheckpointId: number, + ) {} + + static get schema() { + return z + .object({ + actionCounter: z.number().int().nonnegative(), + oldCheckpointId: z.number().int().nonnegative(), + newCheckpointId: z.number().int().nonnegative(), + }) + .transform( + ({ actionCounter, oldCheckpointId, newCheckpointId }) => + new AvmCheckpointActionNoStateChangeHint(actionCounter, oldCheckpointId, newCheckpointId), + ); + } +} + +// Hint for MerkleTreeDB.createCheckpoint. +export class AvmCreateCheckpointHint extends AvmCheckpointActionNoStateChangeHint {} + +// Hint for MerkleTreeDB.commitCheckpoint. +export class AvmCommitCheckpointHint extends AvmCheckpointActionNoStateChangeHint {} + +// Hint for MerkleTreeDB.revertCheckpoint. +export class AvmRevertCheckpointHint { + // We use explicit fields for MessagePack. + constructor( + // key + public readonly actionCounter: number, + // current checkpoint evolution + public readonly oldCheckpointId: number, + public readonly newCheckpointId: number, + // state evolution + public readonly stateBefore: TreeSnapshots, + public readonly stateAfter: TreeSnapshots, + ) {} + + static create( + actionCounter: number, + oldCheckpointId: number, + newCheckpointId: number, + stateBefore: Record, + stateAfter: Record, + ): AvmRevertCheckpointHint { + return new AvmRevertCheckpointHint( + actionCounter, + oldCheckpointId, + newCheckpointId, + new TreeSnapshots( + stateBefore[MerkleTreeId.L1_TO_L2_MESSAGE_TREE], + stateBefore[MerkleTreeId.NOTE_HASH_TREE], + stateBefore[MerkleTreeId.NULLIFIER_TREE], + stateBefore[MerkleTreeId.PUBLIC_DATA_TREE], + ), + new TreeSnapshots( + stateAfter[MerkleTreeId.L1_TO_L2_MESSAGE_TREE], + stateAfter[MerkleTreeId.NOTE_HASH_TREE], + stateAfter[MerkleTreeId.NULLIFIER_TREE], + stateAfter[MerkleTreeId.PUBLIC_DATA_TREE], + ), + ); + } + + static get schema() { + return z + .object({ + actionCounter: z.number().int().nonnegative(), + oldCheckpointId: z.number().int().nonnegative(), + newCheckpointId: z.number().int().nonnegative(), + stateBefore: TreeSnapshots.schema, + stateAfter: TreeSnapshots.schema, + }) + .transform( + ({ actionCounter, oldCheckpointId, newCheckpointId, stateBefore, stateAfter }) => + new AvmRevertCheckpointHint(actionCounter, oldCheckpointId, newCheckpointId, stateBefore, stateAfter), + ); + } +} + //////////////////////////////////////////////////////////////////////////// // Hints (other) //////////////////////////////////////////////////////////////////////////// @@ -386,6 +472,9 @@ export class AvmExecutionHints { public readonly getLeafValueHints: AvmGetLeafValueHint[] = [], public readonly sequentialInsertHintsPublicDataTree: AvmSequentialInsertHintPublicDataTree[] = [], public readonly sequentialInsertHintsNullifierTree: AvmSequentialInsertHintNullifierTree[] = [], + public readonly createCheckpointHints: AvmCreateCheckpointHint[] = [], + public readonly commitCheckpointHints: AvmCommitCheckpointHint[] = [], + public readonly revertCheckpointHints: AvmRevertCheckpointHint[] = [], ) {} static empty() { @@ -406,6 +495,9 @@ export class AvmExecutionHints { getLeafValueHints: AvmGetLeafValueHint.schema.array(), sequentialInsertHintsPublicDataTree: AvmSequentialInsertHintPublicDataTree.schema.array(), sequentialInsertHintsNullifierTree: AvmSequentialInsertHintNullifierTree.schema.array(), + createCheckpointHints: AvmCreateCheckpointHint.schema.array(), + commitCheckpointHints: AvmCommitCheckpointHint.schema.array(), + revertCheckpointHints: AvmRevertCheckpointHint.schema.array(), }) .transform( ({ @@ -420,6 +512,9 @@ export class AvmExecutionHints { getLeafValueHints, sequentialInsertHintsPublicDataTree, sequentialInsertHintsNullifierTree, + createCheckpointHints, + commitCheckpointHints, + revertCheckpointHints, }) => new AvmExecutionHints( tx, @@ -433,6 +528,9 @@ export class AvmExecutionHints { getLeafValueHints, sequentialInsertHintsPublicDataTree, sequentialInsertHintsNullifierTree, + createCheckpointHints, + commitCheckpointHints, + revertCheckpointHints, ), ); } diff --git a/yarn-project/stdlib/src/tests/factories.ts b/yarn-project/stdlib/src/tests/factories.ts index 66377d47b001..d154bcdc4595 100644 --- a/yarn-project/stdlib/src/tests/factories.ts +++ b/yarn-project/stdlib/src/tests/factories.ts @@ -58,8 +58,10 @@ import { AvmBytecodeCommitmentHint, AvmCircuitInputs, AvmCircuitPublicInputs, + AvmCommitCheckpointHint, AvmContractClassHint, AvmContractInstanceHint, + AvmCreateCheckpointHint, AvmEnqueuedCallHint, AvmExecutionHints, AvmGetLeafPreimageHintNullifierTree, @@ -67,6 +69,7 @@ import { AvmGetLeafValueHint, AvmGetPreviousValueIndexHint, AvmGetSiblingPathHint, + AvmRevertCheckpointHint, AvmSequentialInsertHintNullifierTree, AvmSequentialInsertHintPublicDataTree, AvmTxHint, @@ -1382,6 +1385,32 @@ export function makeAvmSequentialInsertHintNullifierTree(seed = 0): AvmSequentia ); } +export function makeAvmCheckpointActionCreateCheckpointHint(seed = 0): AvmCreateCheckpointHint { + return new AvmCreateCheckpointHint( + /*actionCounter=*/ seed, + /*oldCheckpointId=*/ seed + 1, + /*newCheckpointId=*/ seed + 2, + ); +} + +export function makeAvmCheckpointActionCommitCheckpointHint(seed = 0): AvmCommitCheckpointHint { + return new AvmCommitCheckpointHint( + /*actionCounter=*/ seed, + /*oldCheckpointId=*/ seed + 1, + /*newCheckpointId=*/ seed + 2, + ); +} + +export function makeAvmCheckpointActionRevertCheckpointHint(seed = 0): AvmRevertCheckpointHint { + return new AvmRevertCheckpointHint( + /*actionCounter=*/ seed, + /*oldCheckpointId=*/ seed + 1, + /*newCheckpointId=*/ seed + 2, + /*beforeState=*/ makeTreeSnapshots(seed + 3), + /*afterState=*/ makeTreeSnapshots(seed + 7), + ); +} + /** * Makes arbitrary AvmContractInstanceHint. * @param seed - The seed to use for generating the state reference. @@ -1481,6 +1510,9 @@ export async function makeAvmExecutionHints( makeAvmSequentialInsertHintNullifierTree, seed + 0x5700, ), + createCheckpointHints: makeArray(baseLength + 5, makeAvmCheckpointActionCreateCheckpointHint, seed + 0x5900), + commitCheckpointHints: makeArray(baseLength + 5, makeAvmCheckpointActionCommitCheckpointHint, seed + 0x5b00), + revertCheckpointHints: makeArray(baseLength + 5, makeAvmCheckpointActionRevertCheckpointHint, seed + 0x5d00), ...overrides, }; @@ -1496,6 +1528,9 @@ export async function makeAvmExecutionHints( fields.getLeafValueHints, fields.sequentialInsertHintsPublicDataTree, fields.sequentialInsertHintsNullifierTree, + fields.createCheckpointHints, + fields.commitCheckpointHints, + fields.revertCheckpointHints, ); }