Skip to main content

ssz/
missing.rs

1//! `MissingOr<T>` — first-class summary placeholder for SSZ subtree
2//! substitution.
3//!
4//! Two variants:
5//! * `Materialized(T)` — the full value is present.
6//! * `Missing([u8; 32])` — only the precomputed `hash_tree_root` is known.
7//!
8//! Hash invariant (the load-bearing property):
9//! ```text
10//! Missing(h).hash_tree_root::<D>()      == h
11//! Materialized(t).hash_tree_root::<D>() == t.hash_tree_root::<D>()
12//! ```
13//! No `mix_in_selector` is applied — that would defeat substitution.
14//!
15//! Wire form (jar-specific extension; not standard SSZ):
16//! * byte 0 = `0` + payload bytes (Materialized)
17//! * byte 0 = `1` + 32 raw hash bytes (Missing)
18
19use alloc::vec::Vec;
20use core::fmt;
21use digest::Digest;
22use digest::typenum::U32;
23
24use crate::{BYTES_PER_LENGTH_OFFSET, Decode, DecodeError, Encode, HashTreeRoot, read_slice};
25
26/// Either a fully materialized value or a precomputed hash placeholder.
27pub enum MissingOr<T> {
28    Materialized(T),
29    Missing([u8; 32]),
30}
31
32impl<T: fmt::Debug> fmt::Debug for MissingOr<T> {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            Self::Materialized(t) => f.debug_tuple("Materialized").field(t).finish(),
36            Self::Missing(h) => f.debug_tuple("Missing").field(h).finish(),
37        }
38    }
39}
40
41impl<T: Clone> Clone for MissingOr<T> {
42    fn clone(&self) -> Self {
43        match self {
44            Self::Materialized(t) => Self::Materialized(t.clone()),
45            Self::Missing(h) => Self::Missing(*h),
46        }
47    }
48}
49
50impl<T: PartialEq> PartialEq for MissingOr<T> {
51    fn eq(&self, other: &Self) -> bool {
52        match (self, other) {
53            (Self::Materialized(a), Self::Materialized(b)) => a == b,
54            (Self::Missing(a), Self::Missing(b)) => a == b,
55            _ => false,
56        }
57    }
58}
59
60impl<T: Eq> Eq for MissingOr<T> {}
61
62impl<T: Encode> Encode for MissingOr<T> {
63    fn is_ssz_fixed_len() -> bool {
64        false
65    }
66    fn ssz_fixed_len() -> usize {
67        BYTES_PER_LENGTH_OFFSET
68    }
69    fn ssz_bytes_len(&self) -> usize {
70        1 + match self {
71            Self::Materialized(t) => t.ssz_bytes_len(),
72            Self::Missing(_) => 32,
73        }
74    }
75    fn ssz_append(&self, buf: &mut Vec<u8>) {
76        match self {
77            Self::Materialized(t) => {
78                buf.push(0);
79                t.ssz_append(buf);
80            }
81            Self::Missing(h) => {
82                buf.push(1);
83                buf.extend_from_slice(h);
84            }
85        }
86    }
87}
88
89impl<T: Decode> Decode for MissingOr<T> {
90    fn is_ssz_fixed_len() -> bool {
91        false
92    }
93    fn ssz_fixed_len() -> usize {
94        BYTES_PER_LENGTH_OFFSET
95    }
96    fn from_ssz_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
97        let tag = read_slice(bytes, 0, 1)?[0];
98        match tag {
99            0 => Ok(Self::Materialized(T::from_ssz_bytes(&bytes[1..])?)),
100            1 => {
101                if bytes.len() != 33 {
102                    return Err(DecodeError::TrailingBytes {
103                        expected: 33,
104                        actual: bytes.len(),
105                    });
106                }
107                let mut h = [0u8; 32];
108                h.copy_from_slice(&bytes[1..33]);
109                Ok(Self::Missing(h))
110            }
111            v => Err(DecodeError::InvalidSelector(v)),
112        }
113    }
114}
115
116impl<T: HashTreeRoot> HashTreeRoot for MissingOr<T> {
117    fn hash_tree_root<D: Digest<OutputSize = U32>>(&self) -> [u8; 32] {
118        // CRITICAL: no mix_in_selector. Substitution requires identity.
119        match self {
120            Self::Materialized(t) => t.hash_tree_root::<D>(),
121            Self::Missing(h) => *h,
122        }
123    }
124}