node/ledger/
mod.rs

1pub mod read;
2pub mod write;
3
4mod ledger_config;
5use ark_ff::fields::arithmetic::InvalidBigInt;
6pub use ledger_config::*;
7
8mod ledger_event;
9pub use ledger_event::*;
10
11mod ledger_actions;
12pub use ledger_actions::*;
13
14mod ledger_state;
15pub use ledger_state::*;
16
17mod ledger_reducer;
18
19mod ledger_service;
20pub use ledger_service::*;
21
22pub mod ledger_manager;
23
24pub use ledger::{AccountIndex as LedgerAccountIndex, Address as LedgerAddress};
25pub use ledger_manager::LedgerManager;
26
27use ledger::TreeVersion;
28use mina_p2p_messages::v2;
29
30// FIXME(tizoc): both networks use the same value, but this will break if that changes
31pub const LEDGER_DEPTH: usize =
32    crate::core::network::mainnet::CONSTRAINT_CONSTANTS.ledger_depth as usize;
33
34lazy_static::lazy_static! {
35    /// Array size needs to be changed when the tree's depth change
36    static ref LEDGER_HASH_EMPTIES: [v2::LedgerHash; LEDGER_DEPTH + 1] = {
37        use ledger::TreeVersion;
38
39        std::array::from_fn(|i| {
40            let hash = ledger::V2::empty_hash_at_height(LEDGER_DEPTH.saturating_sub(i));
41            v2::MinaBaseLedgerHash0StableV1(hash.into()).into()
42        })
43    };
44}
45
46pub fn ledger_empty_hash_at_depth(depth: usize) -> v2::LedgerHash {
47    LEDGER_HASH_EMPTIES.get(depth).unwrap().clone()
48}
49
50/// Given the hash of the subtree containing all accounts of height `subtree_height`
51/// compute the hash of a tree of size `LEDGER_DEPTH` if all other nodes were
52/// empty.
53pub fn complete_height_tree_with_empties(
54    content_hash: &v2::LedgerHash,
55    subtree_height: usize,
56) -> Result<v2::LedgerHash, InvalidBigInt> {
57    assert!(LEDGER_DEPTH >= subtree_height);
58    let content_hash = content_hash.0.to_field()?;
59
60    let computed_hash = (subtree_height..LEDGER_DEPTH).fold(content_hash, |prev_hash, height| {
61        let depth = LEDGER_DEPTH.saturating_sub(height);
62        let empty_right = ledger_empty_hash_at_depth(depth).0.to_field().unwrap(); // We know empties are valid
63        ledger::V2::hash_node(height, prev_hash, empty_right)
64    });
65
66    Ok(v2::LedgerHash::from_fp(computed_hash))
67}
68
69/// Returns the minimum tree height required for storing `num_accounts` accounts.
70pub fn tree_height_for_num_accounts(num_accounts: u64) -> usize {
71    if num_accounts == 1 {
72        1
73    } else if num_accounts.is_power_of_two() {
74        num_accounts.ilog2() as usize
75    } else {
76        num_accounts.next_power_of_two().ilog2() as usize
77    }
78}
79
80/// Given the hash of the subtree containing `num_accounts` accounts
81/// compute the hash of a tree of size `LEDGER_DEPTH` if all other nodes were
82/// empty.
83///
84/// NOTE: For out of range sizes, en empty tree hash is returned.
85pub fn complete_num_accounts_tree_with_empties(
86    contents_hash: &v2::LedgerHash,
87    num_accounts: u64,
88) -> Result<v2::LedgerHash, InvalidBigInt> {
89    // Note, we assume there is always at least one account
90    if num_accounts == 0 {
91        return Ok(ledger_empty_hash_at_depth(0));
92    }
93
94    let subtree_height = tree_height_for_num_accounts(num_accounts);
95
96    // This would not be a valid number of accounts because it doesn't fit the tree
97    if subtree_height > LEDGER_DEPTH {
98        Ok(ledger_empty_hash_at_depth(0))
99    } else {
100        complete_height_tree_with_empties(contents_hash, subtree_height)
101    }
102}
103
104pub fn hash_node_at_depth(
105    depth: usize,
106    left: mina_curves::pasta::Fp,
107    right: mina_curves::pasta::Fp,
108) -> mina_curves::pasta::Fp {
109    let height = LEDGER_DEPTH.saturating_sub(depth).saturating_sub(1);
110    ledger::V2::hash_node(height, left, right)
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_complete_with_empties() {
119        let subtree_height = 14;
120        let expected_hash: v2::LedgerHash = "jwxdRe86RJV99CZbxZzb4JoDwEnvNQbc6Ha8iPx7pr3FxYpjHBG"
121            .parse()
122            .unwrap();
123        let contents_hash = "jwav4pBszibQqek634VUQEc5WZAbF3CnT7sMyhqXe3vucyXdjJs"
124            .parse()
125            .unwrap();
126
127        let actual_hash =
128            complete_height_tree_with_empties(&contents_hash, subtree_height).unwrap();
129
130        assert_eq!(expected_hash, actual_hash);
131    }
132
133    #[test]
134    fn test_complete_with_empties_with_num_accounts() {
135        let subtree_height = 8517;
136        let expected_hash: v2::LedgerHash = "jwxdRe86RJV99CZbxZzb4JoDwEnvNQbc6Ha8iPx7pr3FxYpjHBG"
137            .parse()
138            .unwrap();
139        let contents_hash = "jwav4pBszibQqek634VUQEc5WZAbF3CnT7sMyhqXe3vucyXdjJs"
140            .parse()
141            .unwrap();
142
143        let actual_hash =
144            complete_num_accounts_tree_with_empties(&contents_hash, subtree_height).unwrap();
145
146        assert_eq!(expected_hash, actual_hash);
147    }
148}