Skip to main content

miden_node_proto/domain/
block.rs

1use std::ops::RangeInclusive;
2
3use miden_protobuf::{BuildUnchecked, ConversionResultExt, Verify, VerifyWith};
4use miden_protocol::block::{BlockHeader, BlockNumber, SignedBlock};
5use miden_protocol::protocol_config::ProtocolConfig;
6use thiserror::Error;
7
8use super::protocol_config::verify_protocol_config_commitment;
9use crate::errors::ConversionError;
10use crate::generated as proto;
11
12impl BuildUnchecked for proto::miden::node::v1::DecodedBlockSubscriptionResponse {
13    type Output = (SignedBlock, BlockNumber, Option<ProtocolConfig>);
14    type Error = ConversionError;
15
16    /// Check block consistency without verifying signatures or linkage against a trusted parent.
17    /// The caller must verify the block against trusted chain state before applying it. The
18    /// committed chain tip remains an upstream claim.
19    fn build_unchecked(self) -> Result<Self::Output, Self::Error> {
20        // SAFETY: The caller must authenticate the block before applying it. This conversion checks
21        // consistency only.
22        let block = self.block.build_unchecked().context("block")?;
23        let protocol_config = self.protocol_config.try_map(|config| {
24            verify_protocol_config_commitment(
25                config.verify().map_err(ConversionError::new)?,
26                block.header(),
27            )
28        })?;
29        Ok((block, self.committed_chain_tip.into(), protocol_config))
30    }
31}
32
33impl VerifyWith<&BlockHeader> for proto::miden::node::v1::DecodedBlockSubscriptionResponse {
34    type Verified = (SignedBlock, BlockNumber, Option<ProtocolConfig>);
35    type Error = ConversionError;
36
37    /// Verify the block against a trusted parent header. This does not re-execute transactions or
38    /// validate account and nullifier state transitions. The committed chain tip remains an
39    /// upstream claim.
40    fn verify_with(self, parent: &BlockHeader) -> Result<Self::Verified, Self::Error> {
41        let block = self.block.verify_with(parent).context("block")?;
42        let protocol_config = self.protocol_config.try_map(|config| {
43            verify_protocol_config_commitment(
44                config.verify().map_err(ConversionError::new)?,
45                block.header(),
46            )
47        })?;
48        Ok((block, self.committed_chain_tip.into(), protocol_config))
49    }
50}
51
52#[derive(Debug, Clone, Error, PartialEq, Eq)]
53pub enum InvalidBlockRange {
54    #[error("start ({start}) greater than end ({end})")]
55    StartGreaterThanEnd { start: BlockNumber, end: BlockNumber },
56}
57
58impl Verify for proto::miden::node::v1::DecodedBlockRange {
59    type Verified = RangeInclusive<BlockNumber>;
60    type Error = InvalidBlockRange;
61
62    /// Converts the block range into an inclusive range.
63    ///
64    /// A `RangeInclusive` is empty exactly when `start > end`, so that case is
65    /// reported as [`InvalidBlockRange::StartGreaterThanEnd`]. Equal endpoints
66    /// are a valid single-block range.
67    fn verify(self) -> Result<Self::Verified, Self::Error> {
68        let block_range = RangeInclusive::new(self.block_from.into(), self.block_to.into());
69
70        if block_range.start() > block_range.end() {
71            return Err(InvalidBlockRange::StartGreaterThanEnd {
72                start: *block_range.start(),
73                end: *block_range.end(),
74            });
75        }
76
77        Ok(block_range)
78    }
79}
80
81impl From<RangeInclusive<BlockNumber>> for proto::miden::node::v1::BlockRange {
82    fn from(range: RangeInclusive<BlockNumber>) -> Self {
83        Self {
84            block_from: range.start().as_u32(),
85            block_to: range.end().as_u32(),
86        }
87    }
88}
89
90#[cfg(test)]
91mod tests {
92
93    use super::*;
94
95    fn range(from: u32, to: u32) -> proto::miden::node::v1::DecodedBlockRange {
96        use crate::DecodeMessage;
97        proto::miden::node::v1::BlockRange { block_from: from, block_to: to }
98            .decode_fields()
99            .unwrap()
100    }
101
102    #[test]
103    fn verify_rejects_start_greater_than_end() {
104        let err = range(5, 4).verify().expect_err("inverted range must be rejected");
105        assert_eq!(
106            err,
107            InvalidBlockRange::StartGreaterThanEnd {
108                start: BlockNumber::from(5u32),
109                end: BlockNumber::from(4u32),
110            }
111        );
112    }
113
114    #[test]
115    fn verify_accepts_single_block() {
116        let got = range(7, 7).verify().expect("start == end is a valid inclusive range");
117        assert_eq!(*got.start(), BlockNumber::from(7u32));
118        assert_eq!(*got.end(), BlockNumber::from(7u32));
119    }
120
121    #[test]
122    fn verify_accepts_ascending_span() {
123        let got = range(1, 3).verify().expect("ascending range must be accepted");
124        assert_eq!(*got.start(), BlockNumber::from(1u32));
125        assert_eq!(*got.end(), BlockNumber::from(3u32));
126    }
127}