Skip to main content

miden_core/mast/serialization/
sparse.rs

1use alloc::{collections::BTreeMap, format, string::ToString, vec::Vec};
2
3use super::{
4    TRUSTED_BYTE_READ_BUDGET_MULTIPLIER,
5    basic_blocks::{BasicBlockDataBuilder, BasicBlockDataDecoder},
6};
7use crate::{
8    Word,
9    advice::AdviceMap,
10    mast::{
11        BasicBlockNodeBuilder, CallNodeBuilder, DynNodeBuilder, ExternalNodeBuilder,
12        JoinNodeBuilder, LoopNodeBuilder, MastForest, MastForestContributor, MastNode, MastNodeExt,
13        MastNodeId, SparseMastForest, SplitNodeBuilder,
14    },
15    serde::{
16        BudgetedReader, ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
17        SliceReader, read_bounded_len,
18    },
19};
20
21const SPARSE_BLOCK: u8 = 0;
22const SPARSE_JOIN: u8 = 1;
23const SPARSE_SPLIT: u8 = 2;
24const SPARSE_LOOP: u8 = 3;
25const SPARSE_CALL: u8 = 4;
26const SPARSE_SYSCALL: u8 = 5;
27const SPARSE_DYN: u8 = 6;
28const SPARSE_DYNCALL: u8 = 7;
29const SPARSE_EXTERNAL: u8 = 8;
30
31// WRITER
32// ================================================================================================
33
34/// Writes trusted sparse trace replay data.
35///
36/// This format preserves the sparse maps produced by execution tracing. It does not prove that
37/// this sparse view is a subset of a committed [`MastForest`], and it does not share the dense
38/// [`MastForest`] wire format. Callers must only read these bytes from a trusted producer, or after
39/// an outer transport/authentication layer has accepted them. The format is version-local and is
40/// not suitable for persistence across releases.
41fn write_sparse_into<W: ByteWriter>(forest: &SparseMastForest, target: &mut W) {
42    write_node_ids(forest.procedure_roots(), target);
43    write_sparse_nodes(forest.nodes(), target);
44    write_digest_entries(forest.digest_entries(), target);
45    forest.advice_map().write_into(target);
46}
47
48fn write_node_ids<W: ByteWriter>(ids: &[MastNodeId], target: &mut W) {
49    target.write_usize(ids.len());
50    for id in ids {
51        id.write_into(target);
52    }
53}
54
55fn write_sparse_nodes<W: ByteWriter>(nodes: &BTreeMap<MastNodeId, MastNode>, target: &mut W) {
56    target.write_usize(nodes.len());
57    for (&id, node) in nodes {
58        id.write_into(target);
59        write_sparse_node(node, target);
60    }
61}
62
63fn write_digest_entries<W: ByteWriter>(digests: &BTreeMap<MastNodeId, Word>, target: &mut W) {
64    target.write_usize(digests.len());
65    for (&id, &digest) in digests {
66        id.write_into(target);
67        digest.write_into(target);
68    }
69}
70
71fn write_sparse_node<W: ByteWriter>(node: &MastNode, target: &mut W) {
72    match node {
73        MastNode::Block(block) => {
74            target.write_u8(SPARSE_BLOCK);
75            node.digest().write_into(target);
76
77            let mut basic_block_data = BasicBlockDataBuilder::new();
78            let ops_offset = basic_block_data.encode_basic_block(block);
79            debug_assert_eq!(ops_offset, 0);
80            let basic_block_data = basic_block_data.finalize();
81            target.write_usize(basic_block_data.len());
82            target.write_bytes(&basic_block_data);
83        },
84        MastNode::Join(join) => {
85            target.write_u8(SPARSE_JOIN);
86            node.digest().write_into(target);
87            join.first().write_into(target);
88            join.second().write_into(target);
89        },
90        MastNode::Split(split) => {
91            target.write_u8(SPARSE_SPLIT);
92            node.digest().write_into(target);
93            split.on_true().write_into(target);
94            split.on_false().write_into(target);
95        },
96        MastNode::Loop(loop_node) => {
97            target.write_u8(SPARSE_LOOP);
98            node.digest().write_into(target);
99            loop_node.body().write_into(target);
100        },
101        MastNode::Call(call) => {
102            target.write_u8(if call.is_syscall() { SPARSE_SYSCALL } else { SPARSE_CALL });
103            node.digest().write_into(target);
104            call.callee().write_into(target);
105        },
106        MastNode::Dyn(dyn_node) => {
107            target.write_u8(if dyn_node.is_dyncall() {
108                SPARSE_DYNCALL
109            } else {
110                SPARSE_DYN
111            });
112            node.digest().write_into(target);
113        },
114        MastNode::External(_) => {
115            target.write_u8(SPARSE_EXTERNAL);
116            node.digest().write_into(target);
117        },
118    }
119}
120
121// TRAIT IMPLS
122// ================================================================================================
123
124impl Serializable for SparseMastForest {
125    fn write_into<W: ByteWriter>(&self, target: &mut W) {
126        write_sparse_into(self, target);
127    }
128}
129
130impl Deserializable for SparseMastForest {
131    fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
132        read_sparse_from(source)
133    }
134
135    fn min_serialized_size() -> usize {
136        usize::min_serialized_size()
137    }
138
139    /// Reads one trusted sparse replay payload and rejects trailing bytes.
140    ///
141    /// This is not an untrusted input format. The reader performs cheap structural checks, but a
142    /// producer controls collection lengths and can drive allocation. Callers must only read these
143    /// bytes from a trusted producer, or after an outer transport/authentication layer has accepted
144    /// them. Payloads are version-local and must not be persisted across releases.
145    fn read_from_bytes(bytes: &[u8]) -> Result<Self, DeserializationError> {
146        let budget = bytes.len().saturating_mul(TRUSTED_BYTE_READ_BUDGET_MULTIPLIER);
147        let mut reader = BudgetedReader::new(SliceReader::new(bytes), budget);
148        let forest = read_sparse_from(&mut reader)?;
149        if reader.has_more_bytes() {
150            return Err(DeserializationError::InvalidValue(
151                "extra bytes after SparseMastForest payload".to_string(),
152            ));
153        }
154        Ok(forest)
155    }
156}
157
158// READER
159// ================================================================================================
160
161fn read_sparse_from<R: ByteReader>(
162    source: &mut R,
163) -> Result<SparseMastForest, DeserializationError> {
164    let roots = read_node_ids(source, "procedure root")?;
165    let nodes = read_sparse_nodes(source)?;
166    let digest_entries = read_digest_entries(source)?;
167    let advice_map = read_empty_advice_map(source)?;
168
169    SparseMastForest::from_serialized_parts(nodes, digest_entries, roots, advice_map)
170}
171
172fn read_empty_advice_map<R: ByteReader>(source: &mut R) -> Result<AdviceMap, DeserializationError> {
173    let count = source.read_usize()?;
174    if count != 0 {
175        return Err(DeserializationError::InvalidValue(
176            "sparse MAST replay payload must not carry advice map entries".to_string(),
177        ));
178    }
179
180    Ok(AdviceMap::default())
181}
182
183fn read_node_ids<R: ByteReader>(
184    source: &mut R,
185    label: &str,
186) -> Result<Vec<MastNodeId>, DeserializationError> {
187    let count = read_bounded_count(source, u32::min_serialized_size(), label)?;
188    let mut ids = Vec::with_capacity(count);
189    for _ in 0..count {
190        ids.push(read_node_id(source, label)?);
191    }
192    Ok(ids)
193}
194
195fn read_sparse_nodes<R: ByteReader>(
196    source: &mut R,
197) -> Result<Vec<(MastNodeId, MastNode)>, DeserializationError> {
198    let count = read_bounded_count(source, sparse_node_min_size(), "full node count")?;
199    let mut nodes = Vec::with_capacity(count);
200    let mut previous_id = None;
201
202    for _ in 0..count {
203        let id = read_node_id(source, "full node")?;
204        validate_strictly_increasing_id(previous_id, id, "full node")?;
205        let node = read_sparse_node(source, id)?;
206        nodes.push((id, node));
207        previous_id = Some(id);
208    }
209
210    Ok(nodes)
211}
212
213fn read_digest_entries<R: ByteReader>(
214    source: &mut R,
215) -> Result<Vec<(MastNodeId, Word)>, DeserializationError> {
216    let count =
217        read_bounded_count(source, sparse_digest_entry_min_size(), "digest-only node count")?;
218    let mut digests = Vec::with_capacity(count);
219    let mut previous_id = None;
220
221    for _ in 0..count {
222        let id = read_node_id(source, "digest-only node")?;
223        validate_strictly_increasing_id(previous_id, id, "digest-only node")?;
224        let digest = Word::read_from(source)?;
225        digests.push((id, digest));
226        previous_id = Some(id);
227    }
228
229    Ok(digests)
230}
231
232fn read_sparse_node<R: ByteReader>(
233    source: &mut R,
234    node_id: MastNodeId,
235) -> Result<MastNode, DeserializationError> {
236    let tag = source.read_u8()?;
237    let digest = Word::read_from(source)?;
238
239    let result = match tag {
240        SPARSE_BLOCK => {
241            let len = read_bounded_count(source, 1, "basic block data length")?;
242            let data = source.read_vec(len)?;
243            let decoder = BasicBlockDataDecoder::new(&data);
244            let op_batches = decoder.decode_operations(0)?;
245            BasicBlockNodeBuilder::from_op_batches(op_batches, digest)
246                .build()
247                .map(Into::into)
248        },
249        SPARSE_JOIN => {
250            let first = read_node_id(source, "join first child")?;
251            let second = read_node_id(source, "join second child")?;
252            JoinNodeBuilder::new([first, second])
253                .with_digest(digest)
254                .build_linked()
255                .map(Into::into)
256        },
257        SPARSE_SPLIT => {
258            let on_true = read_node_id(source, "split true child")?;
259            let on_false = read_node_id(source, "split false child")?;
260            SplitNodeBuilder::new([on_true, on_false])
261                .with_digest(digest)
262                .build_linked()
263                .map(Into::into)
264        },
265        SPARSE_LOOP => {
266            let body = read_node_id(source, "loop body")?;
267            LoopNodeBuilder::new(body).with_digest(digest).build_linked().map(Into::into)
268        },
269        SPARSE_CALL | SPARSE_SYSCALL => {
270            let callee = read_node_id(source, "call callee")?;
271            let builder = if tag == SPARSE_SYSCALL {
272                CallNodeBuilder::new_syscall(callee)
273            } else {
274                CallNodeBuilder::new(callee)
275            };
276            builder.with_digest(digest).build_linked().map(Into::into)
277        },
278        SPARSE_DYN | SPARSE_DYNCALL => {
279            let builder = if tag == SPARSE_DYNCALL {
280                DynNodeBuilder::new_dyncall()
281            } else {
282                DynNodeBuilder::new_dyn()
283            };
284            Ok(builder.with_digest(digest).build().into())
285        },
286        SPARSE_EXTERNAL => Ok(ExternalNodeBuilder::new(digest).build().into()),
287        _ => {
288            return Err(DeserializationError::InvalidValue(format!(
289                "invalid sparse MAST node tag {tag}"
290            )));
291        },
292    };
293
294    result.map_err(|err| {
295        DeserializationError::InvalidValue(format!(
296            "failed to build sparse MAST node {}: {}",
297            node_id.0, err
298        ))
299    })
300}
301
302fn read_bounded_count<R: ByteReader>(
303    source: &mut R,
304    element_size: usize,
305    label: &str,
306) -> Result<usize, DeserializationError> {
307    read_bounded_len(source, label, element_size)
308}
309
310fn sparse_node_min_size() -> usize {
311    u32::min_serialized_size() + u8::min_serialized_size() + Word::min_serialized_size()
312}
313
314fn sparse_digest_entry_min_size() -> usize {
315    u32::min_serialized_size() + Word::min_serialized_size()
316}
317
318fn read_node_id<R: ByteReader>(
319    source: &mut R,
320    label: &str,
321) -> Result<MastNodeId, DeserializationError> {
322    let raw = u32::read_from(source)?;
323    MastNodeId::from_u32_with_node_count(raw, MastForest::MAX_NODES).map_err(|err| {
324        DeserializationError::InvalidValue(format!("invalid {label} id {raw}: {err}"))
325    })
326}
327
328fn validate_strictly_increasing_id(
329    previous: Option<MastNodeId>,
330    current: MastNodeId,
331    label: &str,
332) -> Result<(), DeserializationError> {
333    if previous.is_some_and(|previous| previous >= current) {
334        return Err(DeserializationError::InvalidValue(format!(
335            "{label} ids must be strictly increasing"
336        )));
337    }
338    Ok(())
339}