mithril_persistence/database/record/
block_range_root.rs

1use sqlite::Row;
2
3use mithril_common::crypto_helper::MKTreeNode;
4use mithril_common::entities::{BlockNumber, BlockRange};
5
6use crate::database::Hydrator;
7use crate::sqlite::{HydrationError, Projection, SqLiteEntity};
8
9/// Block range root record is the representation of block range with its merkle root precomputed.
10#[derive(Debug, PartialEq, Clone)]
11pub struct BlockRangeRootRecord {
12    /// Range of block numbers covered
13    pub range: BlockRange,
14    /// Merkle root of the block range, computed from the list of included transactions
15    pub merkle_root: MKTreeNode,
16}
17
18impl From<(BlockRange, MKTreeNode)> for BlockRangeRootRecord {
19    fn from(value: (BlockRange, MKTreeNode)) -> Self {
20        Self {
21            range: value.0,
22            merkle_root: value.1,
23        }
24    }
25}
26
27impl From<BlockRangeRootRecord> for (BlockRange, MKTreeNode) {
28    fn from(value: BlockRangeRootRecord) -> Self {
29        (value.range, value.merkle_root)
30    }
31}
32
33impl SqLiteEntity for BlockRangeRootRecord {
34    fn hydrate(row: Row) -> Result<Self, HydrationError>
35    where
36        Self: Sized,
37    {
38        let start = Hydrator::try_to_u64("block_range.start", row.read::<i64, _>(0))?;
39        let end = Hydrator::try_to_u64("block_range.end", row.read::<i64, _>(1))?;
40        let range = BlockRange::from_block_number(BlockNumber(start));
41        let merkle_root = row.read::<&str, _>(2);
42
43        if range.start != start || range.end != end {
44            return Err(HydrationError::InvalidData(format!(
45                "Invalid block range: start={start}, end={end}, expected_start={}, expected_end={}",
46                range.start, range.end
47            )));
48        }
49
50        Ok(Self {
51            range,
52            merkle_root: MKTreeNode::from_hex(merkle_root)
53                .map_err(|e| HydrationError::InvalidData(
54                    format!(
55                        "Field block_range.merkle_root (value={merkle_root}) is incompatible with hex representation. Error = {e}")
56                )
57                )?,
58        })
59    }
60
61    fn get_projection() -> Projection {
62        Projection::from(&[
63            ("start", "{:block_range_root:}.start", "int"),
64            ("end", "{:block_range_root:}.end", "int"),
65            ("merkle_root", "{:block_range_root:}.merkle_root", "text"),
66        ])
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use sqlite::Connection;
73
74    use super::*;
75
76    fn select_block_range_from_db(start: BlockNumber, end: BlockNumber, merkle_root: &str) -> Row {
77        let conn = Connection::open(":memory:").unwrap();
78        let query = format!("SELECT {start}, {end}, '{merkle_root}'");
79        let mut statement = conn.prepare(query).unwrap();
80        statement.iter().next().unwrap().unwrap()
81    }
82
83    #[test]
84    fn hydrate_succeed_if_valid_block_range_in_row() {
85        // A valid block range has both bounds as multiples of block range length and the interval
86        // size is equal to block range length.
87        let row = select_block_range_from_db(BlockNumber(0), BlockRange::LENGTH, "AAAA");
88        let res = BlockRangeRootRecord::hydrate(row).expect("Expected hydrate to succeed");
89
90        assert_eq!(
91            res,
92            BlockRangeRootRecord {
93                range: BlockRange::from_block_number(BlockNumber(0)),
94                merkle_root: MKTreeNode::from_hex("AAAA").unwrap(),
95            }
96        );
97    }
98
99    #[test]
100    fn hydrate_fail_if_invalid_block_range_in_row() {
101        for invalid_row in [
102            // Start is not a multiple of block range length
103            select_block_range_from_db(BlockNumber(1), BlockRange::LENGTH, "AAAA"),
104            // End is not a multiple of block range length
105            select_block_range_from_db(BlockNumber(0), BlockRange::LENGTH - 1, "AAAA"),
106            // Interval is not equal to block range length
107            select_block_range_from_db(BlockNumber(0), BlockRange::LENGTH * 4, "AAAA"),
108        ] {
109            let res =
110                BlockRangeRootRecord::hydrate(invalid_row).expect_err("Expected hydrate to fail");
111
112            assert!(
113                format!("{res:?}").contains("Invalid block range"),
114                "Expected 'Invalid block range' error, got {:?}",
115                res
116            );
117        }
118    }
119}