1use alloc::{collections::BTreeMap, vec, vec::Vec};
37use codec::{Decode, DecodeWithMemTracking, Encode};
38use mmr_lib::{
39 helper::{get_peak_map, get_peaks, is_valid_mmr_size},
40 leaf_index_to_pos, MerkleProof, NodeMerkleProof,
41};
42use polkadot_core_primitives::Hash;
43use polkadot_parachain_primitives::primitives::Id as ParaId;
44use polkadot_primitives::{v9::MAX_COMMITMENT_ENTRIES, RequiresSet};
45use scale_info::TypeInfo;
46use sp_core::ConstU32;
47use sp_runtime::BoundedVec;
48
49use crate::{
50 mmr::{
51 mmr_size, MessagePosition, MmrFrontier, MmrRoot, SpecMerge, MAX_MMR_HASHES,
52 MAX_MMR_LEAF_COUNT,
53 },
54 stream::StreamId,
55 streams_root::{streams_root_from_proof, StreamProof, StreamsRoot},
56};
57
58pub type TreeInclusionProof = StreamProof;
60
61pub const MAX_INCLUSION_PROOF_ITEMS: u32 = 2 * 64;
65
66#[derive(Clone, Copy, PartialEq, Eq, Debug)]
69pub enum ProofError {
70 InvalidProof,
73 PositionOutOfRange,
75 NotForward,
77 LeafCountTooLarge,
79}
80
81#[derive(Clone, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, Debug, TypeInfo, Default)]
86pub struct MMRExtensionProof {
87 #[codec(compact)]
92 pub leaf_count: u64,
93 pub connecting_nodes: BoundedVec<Hash, ConstU32<MAX_MMR_HASHES>>,
98}
99
100impl MMRExtensionProof {
101 pub fn identity() -> Self {
103 Self { leaf_count: 0, connecting_nodes: BoundedVec::new() }
104 }
105
106 pub fn calculate_root(&self, from: &MmrFrontier) -> Result<MmrRoot, ProofError> {
111 if self.leaf_count > MAX_MMR_LEAF_COUNT {
114 return Err(ProofError::LeafCountTooLarge);
115 }
116 if self.leaf_count == 0 {
119 return if self.connecting_nodes.is_empty() {
120 Ok(from.root())
121 } else {
122 Err(ProofError::InvalidProof)
123 };
124 }
125 if self.leaf_count <= from.leaf_count() {
127 return Err(ProofError::NotForward);
128 }
129 let new_mmr_size = mmr_size(self.leaf_count);
130
131 if from.leaf_count() == 0 {
134 return MmrFrontier::from_parts(self.connecting_nodes.to_vec(), self.leaf_count)
137 .map(|f| f.root())
138 .ok_or(ProofError::InvalidProof);
139 }
140
141 let old_mmr_size = from.mmr_size();
144 let old_positions = get_peaks(old_mmr_size);
145 let positions =
149 mmr_lib::ancestry_proof::ancestry_proof_positions(old_mmr_size, new_mmr_size)
150 .map_err(|_| ProofError::InvalidProof)?;
151 if positions.len() != self.connecting_nodes.len() {
152 return Err(ProofError::InvalidProof);
153 }
154 let proof: Vec<(u64, Hash)> =
155 positions.into_iter().zip(self.connecting_nodes.iter().copied()).collect();
156 let nodes: Vec<(u64, Hash)> =
157 old_positions.into_iter().zip(from.peaks().iter().copied()).collect();
158 NodeMerkleProof::<Hash, SpecMerge>::new(new_mmr_size, proof)
159 .calculate_root(nodes)
160 .map(|bag| MmrRoot::from_bag(self.leaf_count, bag))
161 .map_err(|_| ProofError::InvalidProof)
162 }
163}
164
165#[derive(Clone, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, Debug, TypeInfo)]
170pub struct MmrInclusionProof {
171 pub mmr_size: u64,
174 pub items: BoundedVec<Hash, ConstU32<MAX_INCLUSION_PROOF_ITEMS>>,
176}
177
178impl MmrInclusionProof {
179 fn checked_leaf_count(&self) -> Result<u64, ProofError> {
181 if !is_valid_mmr_size(self.mmr_size) {
182 return Err(ProofError::InvalidProof);
183 }
184 let leaf_count = get_peak_map(self.mmr_size);
185 if leaf_count > MAX_MMR_LEAF_COUNT {
186 return Err(ProofError::LeafCountTooLarge);
187 }
188 Ok(leaf_count)
189 }
190
191 pub fn verify_head(&self, leaf: Hash) -> Result<(MessagePosition, MmrFrontier), ProofError> {
203 let leaf_count = self.checked_leaf_count()?;
204 if leaf_count == 0 {
205 return Err(ProofError::PositionOutOfRange);
206 }
207 if self.items.len() != leaf_proof_len(leaf_count, leaf_count - 1) {
208 return Err(ProofError::InvalidProof);
209 }
210
211 let other_peaks = leaf_count.count_ones() as usize - 1;
212 let (peak_items, path) = self.items.split_at(other_peaks);
213 let mut last_peak = leaf;
214 for sibling in path {
215 last_peak = <SpecMerge as mmr_lib::Merge>::merge(sibling, &last_peak)
216 .expect("SpecMerge::merge is infallible; qed");
217 }
218 let peaks: Vec<Hash> =
219 peak_items.iter().copied().chain(core::iter::once(last_peak)).collect();
220 let frontier =
223 MmrFrontier::from_parts(peaks, leaf_count).ok_or(ProofError::InvalidProof)?;
224
225 Ok((MessagePosition(leaf_count - 1), frontier))
226 }
227
228 pub fn verify_leaf(
233 &self,
234 position: MessagePosition,
235 leaf: Hash,
236 ) -> Result<MmrRoot, ProofError> {
237 let leaf_count = self.checked_leaf_count()?;
238 if position.0 >= leaf_count {
239 return Err(ProofError::PositionOutOfRange);
240 }
241 if self.items.len() != leaf_proof_len(leaf_count, position.0) {
243 return Err(ProofError::InvalidProof);
244 }
245 let pos = leaf_index_to_pos(position.0);
246 MerkleProof::<Hash, SpecMerge>::new(self.mmr_size, self.items.to_vec())
247 .calculate_root(vec![(pos, leaf)])
248 .map(|bag| MmrRoot::from_bag(leaf_count, bag))
249 .map_err(|_| ProofError::InvalidProof)
250 }
251}
252
253fn leaf_proof_len(leaf_count: u64, index: u64) -> usize {
256 let mut first = 0;
257 let heights = (0..u64::BITS).rev().filter(|h| leaf_count >> h & 1 == 1);
258 for (left_peaks, height) in heights.enumerate() {
259 let peak_leaves = 1u64 << height;
260 if index < first + peak_leaves {
261 let right_peaks = leaf_count.count_ones() as usize - left_peaks - 1;
262 return height as usize + left_peaks + usize::from(right_peaks > 0);
263 }
264 first += peak_leaves;
265 }
266 unreachable!("index < leaf_count lies under some peak; qed")
267}
268
269#[derive(Clone, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, Debug, TypeInfo)]
283pub struct Interval {
284 pub start: MmrRoot,
286 pub end: MmrFrontier,
288}
289
290#[derive(Clone, Encode, Decode, PartialEq, Eq, Debug, TypeInfo, Default)]
295pub struct ConsumptionRecord {
296 pub entries: BTreeMap<ParaId, BTreeMap<StreamId, Interval>>,
298}
299
300pub type SourceStreams = BTreeMap<StreamId, Vec<Interval>>;
303
304#[derive(Clone, Encode, Decode, DecodeWithMemTracking, PartialEq, Eq, Debug, TypeInfo)]
307pub struct RequiresLift {
308 pub advances: Vec<MMRExtensionProof>,
311 pub extension: MMRExtensionProof,
314 pub tree_proof: TreeInclusionProof,
316}
317
318#[derive(Clone, Encode, PartialEq, Eq, Debug, TypeInfo, Default)]
324pub struct LiftsBySource(BoundedVec<(ParaId, Vec<RequiresLift>), ConstU32<MAX_COMMITMENT_ENTRIES>>);
325
326impl LiftsBySource {
327 pub fn sources(&self) -> impl Iterator<Item = &ParaId> {
329 self.0.iter().map(|(source, _)| source)
330 }
331
332 pub fn iter(&self) -> impl Iterator<Item = &(ParaId, Vec<RequiresLift>)> {
334 self.0.iter()
335 }
336
337 pub fn len(&self) -> usize {
338 self.0.len()
339 }
340
341 pub fn is_empty(&self) -> bool {
342 self.0.is_empty()
343 }
344}
345
346impl TryFrom<BTreeMap<ParaId, Vec<RequiresLift>>> for LiftsBySource {
347 type Error = LiftError;
348
349 fn try_from(map: BTreeMap<ParaId, Vec<RequiresLift>>) -> Result<Self, LiftError> {
351 BoundedVec::try_from(map.into_iter().collect::<Vec<_>>())
352 .map(Self)
353 .map_err(|_| LiftError::TooManySources)
354 }
355}
356
357impl Decode for LiftsBySource {
359 fn decode<I: codec::Input>(input: &mut I) -> Result<Self, codec::Error> {
360 let inner =
361 BoundedVec::<(ParaId, Vec<RequiresLift>), ConstU32<MAX_COMMITMENT_ENTRIES>>::decode(
362 input,
363 )?;
364 if inner.windows(2).any(|w| w[0].0 >= w[1].0) {
365 return Err("LiftsBySource: sources must be strictly increasing".into());
366 }
367 Ok(Self(inner))
368 }
369}
370
371impl DecodeWithMemTracking for LiftsBySource {}
372
373#[derive(Clone, Copy, PartialEq, Eq, Debug)]
376pub enum LiftError {
377 EmptyRecord,
379 MissingAdvance,
381 StrayAdvance,
383 BrokenChain,
385 BadExtension,
387 BadTreeProof,
389 LiftCountMismatch,
391 DivergentRoots,
393 LiftSourceMismatch,
395 TooManySources,
397}
398
399pub fn stitch(
404 intervals: &[Interval],
405 advances: &[MMRExtensionProof],
406) -> Result<MmrFrontier, LiftError> {
407 let (first, rest) = intervals.split_first().ok_or(LiftError::EmptyRecord)?;
408 let mut gaps = advances.iter();
409 let mut current = &first.end;
414 for next in rest {
415 let current_root = current.root();
416 if next.start != current_root {
417 let proof = gaps.next().ok_or(LiftError::MissingAdvance)?;
419 let extended = proof.calculate_root(current).map_err(|_| LiftError::BrokenChain)?;
420 if extended != next.start {
421 return Err(LiftError::BrokenChain);
422 }
423 }
424 current = &next.end;
425 }
426 if gaps.next().is_some() {
427 return Err(LiftError::StrayAdvance);
428 }
429 Ok(current.clone())
430}
431
432pub fn build_requires_entry(
437 streams: &SourceStreams,
438 lifts: &[RequiresLift],
439) -> Result<StreamsRoot, LiftError> {
440 if streams.len() != lifts.len() {
441 return Err(LiftError::LiftCountMismatch);
442 }
443 let mut entry: Option<StreamsRoot> = None;
444 for ((stream, intervals), lift) in streams.iter().zip(lifts) {
445 let endpoint = stitch(intervals, &lift.advances)?;
446 let current =
448 lift.extension.calculate_root(&endpoint).map_err(|_| LiftError::BadExtension)?;
449 let root = streams_root_from_proof(*stream, current.0, &lift.tree_proof)
452 .ok_or(LiftError::BadTreeProof)?;
453 match entry {
454 None => entry = Some(root),
455 Some(prev) if prev != root => return Err(LiftError::DivergentRoots),
456 Some(_) => {},
457 }
458 }
459 entry.ok_or(LiftError::EmptyRecord)
460}
461
462pub fn build_requires(
469 records: &[ConsumptionRecord],
470 lifts: &LiftsBySource,
471) -> Result<Option<RequiresSet>, LiftError> {
472 let mut merged: BTreeMap<ParaId, SourceStreams> = BTreeMap::new();
474 for record in records {
475 for (source, streams) in &record.entries {
476 let by_stream = merged.entry(*source).or_default();
477 for (stream, interval) in streams {
478 by_stream.entry(*stream).or_default().push(interval.clone());
479 }
480 }
481 }
482 if !merged.keys().eq(lifts.sources()) {
484 return Err(LiftError::LiftSourceMismatch);
485 }
486 if merged.is_empty() {
487 return Ok(None);
488 }
489 let entries = merged
490 .iter()
491 .zip(lifts.iter())
492 .map(|((source, by_stream), (_, source_lifts))| {
493 build_requires_entry(by_stream, source_lifts).map(|root| (*source, root))
494 })
495 .collect::<Result<Vec<_>, _>>()?;
496 RequiresSet::try_from_iter(entries)
497 .map(Some)
498 .map_err(|_| LiftError::TooManySources)
499}
500
501#[cfg(test)]
502mod tests {
503 use super::*;
504 use crate::{
505 streams_root::{gen_stream_proof, streams_root, TreeStep},
506 test_utils::{ch, extension, frontier_at, leaves, mmr_size},
507 };
508 use mmr_lib::{
509 ancestry_proof::ancestry_proof_positions,
510 util::{MemMMR, MemStore},
511 };
512 use polkadot_primitives::v9::MAX_COMMITMENT_ENTRIES;
513 use sp_core::H256;
514
515 fn root_at(all: &[Hash], k: usize) -> MmrRoot {
517 frontier_at(all, k).root()
518 }
519
520 #[test]
521 fn extension_computes_root_and_rejects_tampering() {
522 let all = leaves(5);
523 let ext = extension(&all, 2, 5);
524 assert_eq!(ext.calculate_root(&frontier_at(&all, 2)), Ok(root_at(&all, 5)));
526 assert_ne!(ext.calculate_root(&frontier_at(&all, 3)), Ok(root_at(&all, 5)));
528 let mut bad = ext.clone();
530 bad.connecting_nodes[0] = H256::repeat_byte(0xff);
531 assert_ne!(bad.calculate_root(&frontier_at(&all, 2)), Ok(root_at(&all, 5)));
532 let id = MMRExtensionProof::identity();
534 assert_eq!(id.calculate_root(&frontier_at(&all, 3)), Ok(root_at(&all, 3)));
535 }
536
537 #[test]
542 fn ancestry_positions_matches_mmr_lib() {
543 for n in 2..=256usize {
544 let all = leaves(n as u64);
545 let store = MemStore::<Hash>::default();
546 let mut mmr = MemMMR::<Hash, SpecMerge>::new(0, &store);
547 for l in &all {
548 mmr.push(*l).unwrap();
549 }
550 for k in 1..n {
551 let ap = mmr.gen_ancestry_proof(mmr_size(k)).unwrap();
552 let expected: Vec<u64> =
553 ap.prev_peaks_proof.proof_items().iter().map(|(p, _)| *p).collect();
554 let got = ancestry_proof_positions(mmr_size(k), mmr_size(n)).unwrap();
555 assert_eq!(got, expected, "position mismatch at k={k}, n={n}");
556 }
557 }
558 }
559
560 #[test]
561 fn extension_leaf_count_disambiguates_equal_node_counts() {
562 let all = leaves(4);
565 let to3 = extension(&all, 1, 3);
566 let to4 = extension(&all, 1, 4);
567 assert_eq!(to3.connecting_nodes.len(), 2);
568 assert_eq!(to4.connecting_nodes.len(), 2);
569 let from1 = frontier_at(&all, 1);
570 assert_eq!(to3.calculate_root(&from1), Ok(root_at(&all, 3)));
571 assert_eq!(to4.calculate_root(&from1), Ok(root_at(&all, 4)));
572 let relabelled =
574 MMRExtensionProof { leaf_count: 4, connecting_nodes: to3.connecting_nodes };
575 let got = relabelled.calculate_root(&from1);
576 assert_ne!(got, Ok(root_at(&all, 3)));
577 assert_ne!(got, Ok(root_at(&all, 4)));
578 }
579
580 #[test]
581 fn extension_verify_rejects_non_forward() {
582 let all = leaves(5);
583 let mut ext = extension(&all, 2, 5);
586 ext.leaf_count = 2;
587 assert_eq!(ext.calculate_root(&frontier_at(&all, 2)), Err(ProofError::NotForward));
588 }
589
590 #[test]
591 fn extension_verify_bounds_untrusted_leaf_counts() {
592 let all = leaves(5);
593 let mut ext = extension(&all, 2, 5);
596 ext.leaf_count = u64::MAX;
597 assert_eq!(ext.calculate_root(&frontier_at(&all, 2)), Err(ProofError::LeafCountTooLarge));
598 ext.leaf_count = MAX_MMR_LEAF_COUNT + 1;
599 assert_eq!(ext.calculate_root(&frontier_at(&all, 2)), Err(ProofError::LeafCountTooLarge));
600 assert!(MmrFrontier::from_parts(vec![all[0]], 1 << 63).is_none());
603 }
604
605 #[test]
606 fn extension_verify_rejects_zero_leaf_count() {
607 let all = leaves(5);
608 let mut ext = extension(&all, 2, 5);
610 ext.leaf_count = 0;
611 assert_eq!(ext.calculate_root(&frontier_at(&all, 2)), Err(ProofError::InvalidProof));
612 }
613
614 #[test]
615 fn extension_identity_on_empty_frontier_yields_empty_root() {
616 assert_eq!(
619 MMRExtensionProof::identity().calculate_root(&MmrFrontier::default()),
620 Ok(MmrFrontier::default().root()),
621 );
622 }
623
624 #[test]
625 fn the_longest_valid_extension_fits_the_node_ceiling() {
626 let nodes =
629 ancestry_proof_positions(mmr_size(1), mmr_size((MAX_MMR_LEAF_COUNT - 1) as usize))
630 .unwrap()
631 .len();
632 assert_eq!(nodes, 48);
633 assert!(nodes <= MAX_MMR_HASHES as usize);
634 }
635
636 #[test]
637 fn over_long_extension_proof_fails_to_decode() {
638 let max = MAX_MMR_HASHES as usize;
640 let at_bound = (codec::Compact(5u64), vec![H256::zero(); max]).encode();
641 assert!(MMRExtensionProof::decode(&mut &at_bound[..]).is_ok());
642 let over = (codec::Compact(5u64), vec![H256::zero(); max + 1]).encode();
643 assert!(MMRExtensionProof::decode(&mut &over[..]).is_err());
644 }
645
646 #[test]
647 fn stitch_caught_up_needs_no_advance() {
648 let all = leaves(5);
649 let i1 = Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 2) };
650 let i2 = Interval { start: root_at(&all, 2), end: frontier_at(&all, 5) };
651 assert_eq!(stitch(&[i1, i2], &[]).unwrap(), frontier_at(&all, 5));
652 }
653
654 #[test]
655 fn stitch_bridges_a_gap_and_flags_bad_advance_counts() {
656 let all = leaves(5);
657 let i1 = Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 2) };
658 let i2 = Interval { start: root_at(&all, 4), end: frontier_at(&all, 5) };
660 let advance = extension(&all, 2, 4);
661
662 assert_eq!(
663 stitch(&[i1.clone(), i2.clone()], &[advance.clone()]).unwrap(),
664 frontier_at(&all, 5)
665 );
666 assert_eq!(stitch(&[i1.clone(), i2.clone()], &[]), Err(LiftError::MissingAdvance));
668 let cont = Interval { start: root_at(&all, 2), end: frontier_at(&all, 5) };
670 assert_eq!(stitch(&[i1, cont], &[advance]), Err(LiftError::StrayAdvance));
671 }
672
673 #[test]
674 fn stitch_rejects_a_gap_bridged_to_the_wrong_context() {
675 let all = leaves(5);
678 let i1 = Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 2) };
679 let i2 = Interval { start: root_at(&all, 4), end: frontier_at(&all, 5) };
680 assert_eq!(
682 stitch(&[i1.clone(), i2.clone()], &[extension(&all, 2, 3)]),
683 Err(LiftError::BrokenChain)
684 );
685 assert_eq!(stitch(&[i1, i2], &[extension(&all, 1, 4)]), Err(LiftError::BrokenChain));
687 }
688
689 #[test]
690 fn stitch_rejects_a_backward_step() {
691 let all = leaves(5);
694 let i1 = Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 4) };
695 let i2 = Interval { start: root_at(&all, 2), end: frontier_at(&all, 2) };
696 assert_eq!(stitch(&[i1.clone(), i2.clone()], &[]), Err(LiftError::MissingAdvance));
697 assert_eq!(stitch(&[i1, i2], &[extension(&all, 4, 5)]), Err(LiftError::BrokenChain));
698 }
699
700 #[test]
701 fn nothing_recorded_lifts_nothing() {
702 assert_eq!(stitch(&[], &[]), Err(LiftError::EmptyRecord));
703 assert_eq!(build_requires_entry(&SourceStreams::new(), &[]), Err(LiftError::EmptyRecord));
704 }
705
706 #[test]
707 fn an_empty_source_entry_fails_closed() {
708 let source = ParaId::from(1000);
711 let record = ConsumptionRecord { entries: BTreeMap::from([(source, BTreeMap::new())]) };
712 let lifts = LiftsBySource::try_from(BTreeMap::from([(source, Vec::new())])).unwrap();
713 assert_eq!(build_requires(&[record], &lifts), Err(LiftError::EmptyRecord));
714 }
715
716 #[test]
717 fn consumption_record_encodes_as_the_sorted_vec_of_pairs() {
718 let all = leaves(3);
721 let (a, b) = (ch(1), ch(2));
722 let (ia, ib) = (
723 Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 1) },
724 Interval { start: root_at(&all, 1), end: frontier_at(&all, 3) },
725 );
726 let keyed = ConsumptionRecord {
727 entries: BTreeMap::from([(
728 ParaId::from(7),
729 BTreeMap::from([(b, ib.clone()), (a, ia.clone())]),
730 )]),
731 };
732 let as_vec: BTreeMap<ParaId, Vec<(StreamId, Interval)>> =
733 BTreeMap::from([(ParaId::from(7), vec![(a, ia), (b, ib)])]);
734 assert_eq!(keyed.encode(), as_vec.encode());
735 }
736
737 #[test]
738 fn hot_path_single_block_single_stream() {
739 let all = leaves(3);
740 let root3 = root_at(&all, 3);
741 let stream = ch(2000);
742 let entries = BTreeMap::from([(stream, root3.0)]);
744 let expected = streams_root(&entries).unwrap();
745 let (_r, tree_proof) = gen_stream_proof(&entries, stream).unwrap();
746
747 let interval = Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 3) };
749 let lift = RequiresLift {
750 advances: Vec::new(),
751 extension: MMRExtensionProof::identity(),
752 tree_proof,
753 };
754 let streams = SourceStreams::from([(stream, vec![interval])]);
755 let root = build_requires_entry(&streams, &[lift]).unwrap();
756 assert_eq!(root, expected);
757 }
758
759 #[test]
760 fn build_requires_end_to_end_and_source_match() {
761 let all = leaves(3);
762 let root3 = root_at(&all, 3);
763 let source = ParaId::from(1000);
764 let stream = ch(2000);
765 let entries = BTreeMap::from([(stream, root3.0)]);
766 let expected = streams_root(&entries).unwrap();
767 let (_r, tree_proof) = gen_stream_proof(&entries, stream).unwrap();
768
769 let record = ConsumptionRecord {
770 entries: BTreeMap::from([(
771 source,
772 BTreeMap::from([(
773 stream,
774 Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 3) },
775 )]),
776 )]),
777 };
778 let mut lifts = BTreeMap::new();
779 lifts.insert(
780 source,
781 vec![RequiresLift {
782 advances: Vec::new(),
783 extension: MMRExtensionProof::identity(),
784 tree_proof,
785 }],
786 );
787
788 let lifts_by_source = LiftsBySource::try_from(lifts.clone()).unwrap();
789 let requires = build_requires(&[record.clone()], &lifts_by_source).unwrap().unwrap();
790 assert_eq!(requires.get(source), Some(&expected));
791
792 let mut extra = lifts.clone();
794 extra.insert(ParaId::from(9999), Vec::new());
795 let extra = LiftsBySource::try_from(extra).unwrap();
796 assert_eq!(build_requires(&[record], &extra), Err(LiftError::LiftSourceMismatch));
797
798 assert_eq!(build_requires(&[], &LiftsBySource::default()), Ok(None));
800 }
801
802 #[test]
803 fn lifts_by_source_decode_enforces_canonical_form() {
804 let lift = || RequiresLift {
805 advances: Vec::new(),
806 extension: MMRExtensionProof::identity(),
807 tree_proof: StreamProof { steps: Default::default() },
808 };
809 let pair = |id: u32| (ParaId::from(id), vec![lift()]);
810 let good = vec![pair(1), pair(2)].encode();
812 assert!(LiftsBySource::decode(&mut &good[..]).is_ok());
813 let unsorted = vec![pair(2), pair(1)].encode();
815 assert!(LiftsBySource::decode(&mut &unsorted[..]).is_err());
816 let duplicate = vec![pair(1), pair(1)].encode();
817 assert!(LiftsBySource::decode(&mut &duplicate[..]).is_err());
818 let too_many: Vec<_> = (0..=MAX_COMMITMENT_ENTRIES).map(pair).collect();
820 assert!(LiftsBySource::decode(&mut &too_many.encode()[..]).is_err());
821 let map: BTreeMap<_, _> = (0..=MAX_COMMITMENT_ENTRIES).map(pair).collect();
823 assert_eq!(LiftsBySource::try_from(map).err(), Some(LiftError::TooManySources));
824 }
825
826 #[test]
827 fn late_block_non_identity_extension_end_to_end() {
828 let all = leaves(5);
830 let stream = ch(2000);
831 let committed = root_at(&all, 5);
832 let entries = BTreeMap::from([(stream, committed.0)]);
833 let expected = streams_root(&entries).unwrap();
834 let (_r, tree_proof) = gen_stream_proof(&entries, stream).unwrap();
835
836 let interval = Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 2) };
837 let lift =
838 RequiresLift { advances: Vec::new(), extension: extension(&all, 2, 5), tree_proof };
839 let streams = SourceStreams::from([(stream, vec![interval])]);
840 assert_eq!(build_requires_entry(&streams, &[lift]).unwrap(), expected);
841 }
842
843 #[test]
844 fn multi_stream_source_converges_on_one_root() {
845 let all = leaves(5);
847 let (a, b) = (ch(2000), ch(3000));
848 let (ra, rb) = (root_at(&all, 3), root_at(&all, 5));
849 let entries = BTreeMap::from([(a, ra.0), (b, rb.0)]);
850 let expected = streams_root(&entries).unwrap();
851 let (_pa, proof_a) = gen_stream_proof(&entries, a).unwrap();
852 let (_pb, proof_b) = gen_stream_proof(&entries, b).unwrap();
853
854 let lift = |tree_proof| RequiresLift {
855 advances: Vec::new(),
856 extension: MMRExtensionProof::identity(),
857 tree_proof,
858 };
859 let streams = SourceStreams::from([
861 (a, vec![Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 3) }]),
862 (b, vec![Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 5) }]),
863 ]);
864 assert_eq!(
865 build_requires_entry(&streams, &[lift(proof_a), lift(proof_b)]).unwrap(),
866 expected
867 );
868 }
869
870 #[test]
871 fn build_requires_covers_multiple_sources() {
872 let all = leaves(3);
874 let committed = root_at(&all, 3);
875 let (src_a, src_b) = (ParaId::from(1000), ParaId::from(2000));
876 let stream = ch(4000);
877 let entries = BTreeMap::from([(stream, committed.0)]);
878 let expected = streams_root(&entries).unwrap();
879
880 let record_for = |source| {
881 let (_r, tree_proof) = gen_stream_proof(&entries, stream).unwrap();
882 let record = ConsumptionRecord {
883 entries: BTreeMap::from([(
884 source,
885 BTreeMap::from([(
886 stream,
887 Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 3) },
888 )]),
889 )]),
890 };
891 let lift = RequiresLift {
892 advances: Vec::new(),
893 extension: MMRExtensionProof::identity(),
894 tree_proof,
895 };
896 (record, lift)
897 };
898 let (ra, la) = record_for(src_a);
899 let (rb, lb) = record_for(src_b);
900 let lifts = LiftsBySource::try_from(BTreeMap::from([(src_a, vec![la]), (src_b, vec![lb])]))
901 .unwrap();
902
903 let requires = build_requires(&[ra, rb], &lifts).unwrap().unwrap();
904 assert_eq!(requires.len(), 2);
905 assert_eq!(requires.get(src_a), Some(&expected));
906 assert_eq!(requires.get(src_b), Some(&expected));
907 }
908
909 #[test]
910 fn build_requires_entry_stitches_a_bundle_gap() {
911 let all = leaves(5);
914 let stream = ch(2000);
915 let committed = root_at(&all, 5);
916 let entries = BTreeMap::from([(stream, committed.0)]);
917 let expected = streams_root(&entries).unwrap();
918 let (_r, tree_proof) = gen_stream_proof(&entries, stream).unwrap();
919
920 let intervals = vec![
921 Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 2) },
922 Interval { start: root_at(&all, 4), end: frontier_at(&all, 5) }, ];
924 let lift = RequiresLift {
925 advances: vec![extension(&all, 2, 4)], extension: MMRExtensionProof::identity(), tree_proof,
928 };
929 let streams = SourceStreams::from([(stream, intervals)]);
930 assert_eq!(build_requires_entry(&streams, &[lift]).unwrap(), expected);
931 }
932
933 #[test]
934 fn lift_count_must_match_stream_count() {
935 let all = leaves(3);
936 let stream = ch(2000);
937 let streams = SourceStreams::from([(
938 stream,
939 vec![Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 3) }],
940 )]);
941 let lift = || RequiresLift {
942 advances: Vec::new(),
943 extension: MMRExtensionProof::identity(),
944 tree_proof: StreamProof { steps: Default::default() },
945 };
946 assert_eq!(build_requires_entry(&streams, &[]), Err(LiftError::LiftCountMismatch));
948 assert_eq!(
949 build_requires_entry(&streams, &[lift(), lift()]),
950 Err(LiftError::LiftCountMismatch)
951 );
952 }
953
954 #[test]
955 fn build_requires_entry_rejects_a_backward_extension() {
956 let all = leaves(5);
959 let stream = ch(2000);
960 let (_r, tree_proof) =
961 gen_stream_proof(&BTreeMap::from([(stream, root_at(&all, 5).0)]), stream).unwrap();
962 let streams = SourceStreams::from([(
963 stream,
964 vec![Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 3) }],
965 )]);
966 let lift = RequiresLift {
969 advances: Vec::new(),
970 extension: MMRExtensionProof { leaf_count: 1, connecting_nodes: BoundedVec::new() },
971 tree_proof,
972 };
973 assert_eq!(build_requires_entry(&streams, &[lift]), Err(LiftError::BadExtension));
974 }
975
976 #[test]
977 fn stitch_ignores_the_first_interval_start() {
978 let all = leaves(5);
981 let garbage =
982 Interval { start: MmrRoot(H256::repeat_byte(0xEE)), end: frontier_at(&all, 2) };
983 let cont = Interval { start: root_at(&all, 2), end: frontier_at(&all, 5) };
984 assert_eq!(stitch(&[garbage, cont], &[]).unwrap(), frontier_at(&all, 5));
985 }
986
987 #[test]
988 fn build_requires_entry_rejects_a_malformed_tree_proof() {
989 let all = leaves(3);
991 let stream = ch(2000);
992 let streams = SourceStreams::from([(
993 stream,
994 vec![Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 3) }],
995 )]);
996 let bad = StreamProof {
998 steps: BoundedVec::try_from(vec![TreeStep { split_bit: 64, sibling: H256::zero() }])
999 .unwrap(),
1000 };
1001 let lift = RequiresLift {
1002 advances: Vec::new(),
1003 extension: MMRExtensionProof::identity(),
1004 tree_proof: bad,
1005 };
1006 assert_eq!(build_requires_entry(&streams, &[lift]), Err(LiftError::BadTreeProof));
1007 }
1008
1009 #[test]
1010 fn build_requires_entry_flags_missing_and_stray_advances() {
1011 let all = leaves(5);
1013 let stream = ch(2000);
1014 let (_r, tree_proof) =
1015 gen_stream_proof(&BTreeMap::from([(stream, root_at(&all, 5).0)]), stream).unwrap();
1016 let mk = |intervals, advances| {
1017 let lift = RequiresLift {
1018 advances,
1019 extension: MMRExtensionProof::identity(),
1020 tree_proof: tree_proof.clone(),
1021 };
1022 build_requires_entry(&SourceStreams::from([(stream, intervals)]), &[lift])
1023 };
1024
1025 let gapped = vec![
1027 Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 2) },
1028 Interval { start: root_at(&all, 4), end: frontier_at(&all, 5) },
1029 ];
1030 assert_eq!(mk(gapped, Vec::new()), Err(LiftError::MissingAdvance));
1031
1032 let continuous = vec![
1034 Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 2) },
1035 Interval { start: root_at(&all, 2), end: frontier_at(&all, 5) },
1036 ];
1037 assert_eq!(mk(continuous, vec![extension(&all, 2, 4)]), Err(LiftError::StrayAdvance));
1038 }
1039
1040 #[test]
1041 fn divergent_roots_rejected() {
1042 let all = leaves(3);
1043 let root3 = root_at(&all, 3);
1044 let (sa, sb) = (ch(2000), ch(3000));
1045
1046 let (_ra, proof_a) = gen_stream_proof(&BTreeMap::from([(sa, root3.0)]), sa).unwrap();
1048 let (_rb, proof_b) = gen_stream_proof(&BTreeMap::from([(sb, root3.0)]), sb).unwrap();
1049
1050 let mk = |tree_proof| RequiresLift {
1051 advances: Vec::new(),
1052 extension: MMRExtensionProof::identity(),
1053 tree_proof,
1054 };
1055 let streams = SourceStreams::from([
1056 (sa, vec![Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 3) }]),
1057 (sb, vec![Interval { start: MmrRoot(H256::zero()), end: frontier_at(&all, 3) }]),
1058 ]);
1059 assert_eq!(
1060 build_requires_entry(&streams, &[mk(proof_a), mk(proof_b)]),
1061 Err(LiftError::DivergentRoots),
1062 );
1063 }
1064
1065 fn inclusion(all: &[Hash], n: usize, index: usize) -> MmrInclusionProof {
1067 let store = MemStore::<Hash>::default();
1068 let mut mmr = MemMMR::<Hash, SpecMerge>::new(0, &store);
1069 for l in &all[..n] {
1070 mmr.push(*l).unwrap();
1071 }
1072 let proof = mmr.gen_proof(vec![leaf_index_to_pos(index as u64)]).unwrap();
1073 MmrInclusionProof {
1074 mmr_size: mmr_size(n),
1075 items: BoundedVec::truncate_from(proof.proof_items().to_vec()),
1076 }
1077 }
1078
1079 #[test]
1080 fn inclusion_verify_head_pins_head_and_derives_root() {
1081 let all = leaves(6);
1082 let proof = inclusion(&all, 6, 5);
1085
1086 let (pos, frontier) = proof.verify_head(all[5]).expect("well-formed head proof");
1088 assert_eq!(pos, MessagePosition(5));
1089 assert_eq!(frontier.leaf_count(), 6);
1090 assert_eq!(frontier.root(), root_at(&all, 6));
1091
1092 let (_, forged) = proof.verify_head(all[0]).expect("shape still parses");
1095 assert_ne!(forged.root(), root_at(&all, 6));
1096
1097 let mut bad = proof.clone();
1099 bad.items.try_push(H256::zero()).expect("below the bound; qed");
1100 assert_eq!(bad.verify_head(all[5]), Err(ProofError::InvalidProof));
1101
1102 assert_eq!(
1104 MmrInclusionProof { mmr_size: 2, items: BoundedVec::new() }.verify_head(all[5]),
1105 Err(ProofError::InvalidProof),
1106 );
1107 }
1108
1109 #[test]
1110 fn inclusion_verify_head_boundary_shapes() {
1111 let all1 = leaves(1);
1117 let (pos1, f1) = inclusion(&all1, 1, 0).verify_head(all1[0]).expect("n=1 head");
1118 assert_eq!(pos1, MessagePosition(0));
1119 assert_eq!(f1.leaf_count(), 1);
1120 assert_eq!(f1.root(), root_at(&all1, 1));
1121
1122 let all8 = leaves(8);
1124 let (pos8, f8) = inclusion(&all8, 8, 7).verify_head(all8[7]).expect("n=8 head");
1125 assert_eq!(pos8, MessagePosition(7));
1126 assert_eq!(f8.leaf_count(), 8);
1127 assert_eq!(f8.root(), root_at(&all8, 8));
1128 }
1129
1130 #[test]
1131 fn inclusion_verify_leaf_binds_position_and_value() {
1132 let all = leaves(6);
1133 let want = root_at(&all, 6);
1134 let proof = inclusion(&all, 6, 3);
1135
1136 assert_eq!(proof.verify_leaf(MessagePosition(3), all[3]), Ok(want));
1138 assert_ne!(proof.verify_leaf(MessagePosition(3), H256::repeat_byte(0xAA)), Ok(want));
1140 assert_ne!(proof.verify_leaf(MessagePosition(4), all[3]), Ok(want));
1142 assert_eq!(
1144 proof.verify_leaf(MessagePosition(6), all[3]),
1145 Err(ProofError::PositionOutOfRange)
1146 );
1147 assert_eq!(
1149 MmrInclusionProof { mmr_size: 2, items: BoundedVec::new() }
1150 .verify_leaf(MessagePosition(0), all[0]),
1151 Err(ProofError::InvalidProof),
1152 );
1153 }
1154
1155 #[test]
1156 fn oversized_inclusion_proof_fails_to_decode() {
1157 let max = MAX_INCLUSION_PROOF_ITEMS as usize;
1159 let at_bound = (mmr_size(6) as u64, vec![H256::zero(); max]).encode();
1160 assert!(MmrInclusionProof::decode(&mut &at_bound[..]).is_ok());
1161 let over = (mmr_size(6) as u64, vec![H256::zero(); max + 1]).encode();
1162 assert!(MmrInclusionProof::decode(&mut &over[..]).is_err());
1163 }
1164
1165 #[test]
1167 fn inclusion_verify_leaf_rejects_padding() {
1168 let all = leaves(6);
1169 let one = inclusion(&all, 1, 0);
1170 assert!(one.items.is_empty());
1171 assert_eq!(one.verify_leaf(MessagePosition(0), all[0]), Ok(root_at(&all, 1)));
1172 let mut padded = one.clone();
1173 padded.items.try_push(H256::repeat_byte(0xAB)).expect("below the bound; qed");
1174 assert_eq!(padded.verify_leaf(MessagePosition(0), all[0]), Err(ProofError::InvalidProof));
1175
1176 let mut longer = inclusion(&all, 6, 3);
1177 longer.items.try_push(H256::zero()).expect("below the bound; qed");
1178 assert_eq!(longer.verify_leaf(MessagePosition(3), all[3]), Err(ProofError::InvalidProof));
1179 }
1180
1181 #[test]
1183 fn leaf_proof_len_matches_mmr_lib() {
1184 let all = leaves(130);
1185 for n in 1..=130 {
1186 for i in 0..n {
1187 assert_eq!(
1188 leaf_proof_len(n as u64, i as u64),
1189 inclusion(&all, n, i).items.len(),
1190 "leaf {i} of {n}"
1191 );
1192 }
1193 }
1194 }
1195
1196 #[test]
1197 fn inclusion_verify_head_rejects_non_head_proof() {
1198 let all = leaves(6);
1199 let non_head = inclusion(&all, 6, 2);
1201 assert_eq!(non_head.verify_head(all[2]), Err(ProofError::InvalidProof));
1202 }
1203
1204 #[test]
1205 fn inclusion_head_and_leaf_agree_on_root() {
1206 let all = leaves(6);
1208 let proof = inclusion(&all, 6, 5);
1209 let (pos, frontier) = proof.verify_head(all[5]).expect("head verifies");
1210 assert_eq!(pos, MessagePosition(5));
1211 let via_head = frontier.root();
1212 let via_leaf = proof.verify_leaf(pos, all[5]).expect("leaf verifies");
1213 assert_eq!(via_head, via_leaf);
1214 assert_eq!(via_head, root_at(&all, 6));
1215 }
1216}