miden_tx/prover/mast_store.rs
1use alloc::collections::BTreeMap;
2use alloc::sync::Arc;
3
4use miden_agglayer::agglayer_package;
5use miden_processor::{LoadedMastForest, MastForestStore};
6use miden_protocol::account::AccountCode;
7use miden_protocol::assembly::mast::MastForest;
8use miden_protocol::package::loaded_mast_forest_from_package;
9use miden_protocol::transaction::TransactionKernel;
10use miden_protocol::utils::sync::RwLock;
11use miden_protocol::vm::Package;
12use miden_protocol::{CoreLibrary, ProtocolLib, Word};
13use miden_standards::StandardsLib;
14
15// TRANSACTION MAST STORE
16// ================================================================================================
17
18/// A store for the code available during transaction execution.
19///
20/// Transaction MAST store contains a map between procedure MAST roots and [MastForest]s containing
21/// MASTs for these procedures. The VM will request [MastForest]s from the store when it encounters
22/// a procedure which it doesn't have the code for. Thus, to execute a program which makes
23/// references to external procedures, the store must be loaded with [MastForest]s containing these
24/// procedures.
25pub struct TransactionMastStore {
26 mast_forests: RwLock<BTreeMap<Word, LoadedMastForest>>,
27}
28
29#[allow(clippy::new_without_default)]
30impl TransactionMastStore {
31 /// Returns a new [TransactionMastStore] instantiated with the default libraries.
32 ///
33 /// The default libraries include:
34 /// - Miden core library [`CoreLibrary`].
35 /// - Miden protocol library [`ProtocolLib`].
36 /// - Miden standards library [`StandardsLib`].
37 /// - Transaction kernel [`TransactionKernel::package`].
38 pub fn new() -> Self {
39 let mast_forests = RwLock::new(BTreeMap::new());
40 let store = Self { mast_forests };
41
42 // load transaction kernel MAST forest
43 let kernel = TransactionKernel::package();
44 store.insert_package(kernel.as_ref());
45
46 // load the miden-core-lib MAST forest
47 store.insert_package(CoreLibrary::default().package().as_ref());
48
49 // load protocol lib MAST forest
50 let protocol_lib = ProtocolLib::default();
51 store.insert_package(protocol_lib.as_ref());
52
53 // load standards lib MAST forest
54 let standards_lib = StandardsLib::default();
55 store.insert_package(standards_lib.as_ref());
56
57 // load agglayer lib MAST forest
58 store.insert_package(&agglayer_package());
59
60 store
61 }
62
63 /// Registers all procedures of the provided [MastForest] with this store.
64 ///
65 /// This path only carries the MAST forest. If a [Package] is available, prefer
66 /// [`Self::insert_package`] so package-owned debug info is preserved.
67 pub fn insert(&self, mast_forest: Arc<MastForest>) {
68 self.insert_loaded(LoadedMastForest::new(mast_forest));
69 }
70
71 /// Registers all procedures of the provided [Package] with this store.
72 ///
73 /// This preserves package-owned debug info when the package was built or read from a trusted
74 /// local source.
75 pub fn insert_package(&self, package: &Package) {
76 self.insert_loaded(loaded_mast_forest_from_package(package));
77 }
78
79 /// Registers all procedures of the provided loaded MAST forest with this store.
80 pub fn insert_loaded(&self, loaded_mast_forest: LoadedMastForest) {
81 let mut mast_forests = self.mast_forests.write();
82
83 // only register procedures that are local to this forest
84 for proc_digest in loaded_mast_forest.mast_forest().local_procedure_digests() {
85 mast_forests.insert(proc_digest, loaded_mast_forest.clone());
86 }
87 }
88
89 /// Loads the provided account code into this store.
90 pub fn load_account_code(&self, code: &AccountCode) {
91 self.insert_loaded(code.loaded_mast_forest());
92 }
93}
94
95// MAST FOREST STORE IMPLEMENTATION
96// ================================================================================================
97
98impl MastForestStore for TransactionMastStore {
99 fn get(&self, procedure_root: &Word) -> Option<LoadedMastForest> {
100 self.mast_forests.read().get(procedure_root).cloned()
101 }
102}
103
104#[cfg(test)]
105impl TransactionMastStore {
106 /// Returns the number of procedure entries in the store (for testing only).
107 #[allow(clippy::len_without_is_empty)]
108 pub fn len(&self) -> usize {
109 self.mast_forests.read().len()
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
118 fn fresh_stores_have_same_baseline_size() {
119 // Two independently created stores should have the same number of entries
120 // (only the default libraries). This verifies that creating per-call stores
121 // in LocalTransactionProver::prove() doesn't accumulate state — each fresh
122 // store starts from the same baseline.
123 let store1 = TransactionMastStore::new();
124 let store2 = TransactionMastStore::new();
125 assert_eq!(store1.len(), store2.len());
126 assert!(store1.len() > 0, "default libraries should populate the store");
127 }
128
129 #[test]
130 fn insert_does_not_affect_other_stores() {
131 // Inserting into one store must not affect another. This models the
132 // per-call store approach in LocalTransactionProver::prove() — each call
133 // creates a fresh store and loads only the current transaction's code.
134 // A second prove() call should start from the same baseline, not carry
135 // over entries from the first.
136 let store1 = TransactionMastStore::new();
137 let baseline = store1.len();
138
139 // Simulate loading account code by inserting the kernel forest again
140 // (it adds no new entries since they already exist, but this exercises
141 // the insert path without needing to construct a custom MastForest)
142 let kernel_forest = TransactionKernel::package().mast_forest().clone();
143 store1.insert(kernel_forest);
144
145 // A fresh store should be at exactly the same baseline
146 let store2 = TransactionMastStore::new();
147 assert_eq!(store2.len(), baseline, "new store must not inherit entries from others");
148 }
149}