Skip to main content

cuprate_blockchain/service/
free.rs

1//! General free functions used (related to `cuprate_blockchain::service`).
2use std::sync::Arc;
3
4use rayon::ThreadPool;
5
6use crate::{
7    config::Config,
8    error::BlockchainError,
9    service::init_write_service,
10    service::{read::BlockchainReadHandle, write::BlockchainWriteHandle},
11    BlockchainDatabase,
12};
13
14#[cold]
15#[inline(never)] // Only called once (?)
16/// Initialize a database, and return a read/write handle to it.
17///
18/// Once the returned handles are [`Drop::drop`]ed, the reader
19/// thread-pool and writer thread will exit automatically.
20///
21/// # Errors
22/// This will error if we fail to open the database.
23pub fn init_with_pool(
24    config: &Config,
25    fjall: fjall::Database,
26    pool: Arc<ThreadPool>,
27) -> Result<
28    (
29        BlockchainReadHandle,
30        BlockchainWriteHandle,
31        Arc<BlockchainDatabase>,
32    ),
33    BlockchainError,
34> {
35    // Initialise the database itself.
36    let mut db = BlockchainDatabase::open_with_fjall_database(config, fjall)?;
37    db.make_consistent()?;
38    let db = Arc::new(db);
39
40    // Spawn the Reader thread pool and Writer.
41    let readers = BlockchainReadHandle {
42        blockchain: Arc::clone(&db),
43        pool,
44    };
45    let writer = init_write_service(Arc::clone(&db));
46
47    Ok((readers, writer, db))
48}
49
50//---------------------------------------------------------------------------------------------------- Compact history
51/// Given a position in the compact history, returns the height offset that should be in that position.
52///
53/// The height offset is the difference between the top block's height and the block height that should be in that position.
54#[inline]
55pub(super) const fn compact_history_index_to_height_offset<const INITIAL_BLOCKS: usize>(
56    i: usize,
57) -> usize {
58    // If the position is below the initial blocks just return the position back
59    if i <= INITIAL_BLOCKS {
60        i
61    } else {
62        // Otherwise we go with power of 2 offsets, the same as monerod.
63        // So (INITIAL_BLOCKS + 2), (INITIAL_BLOCKS + 2 + 4), (INITIAL_BLOCKS + 2 + 4 + 8)
64        // ref: <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/cryptonote_core/blockchain.cpp#L727>
65        INITIAL_BLOCKS + (2 << (i - INITIAL_BLOCKS)) - 2
66    }
67}
68
69/// Returns if the genesis block was _NOT_ included when calculating the height offsets.
70///
71/// The genesis must always be included in the compact history.
72#[inline]
73pub(super) const fn compact_history_genesis_not_included<const INITIAL_BLOCKS: usize>(
74    top_block_height: usize,
75) -> bool {
76    // If the top block height is less than the initial blocks then it will always be included.
77    // Otherwise, we use the fact that to reach the genesis block this statement must be true (for a
78    // single `i`):
79    //
80    // `top_block_height - INITIAL_BLOCKS - 2^i + 2 == 0`
81    // which then means:
82    // `top_block_height - INITIAL_BLOCKS + 2 == 2^i`
83    // So if `top_block_height - INITIAL_BLOCKS + 2` is a power of 2 then the genesis block is in
84    // the compact history already.
85    top_block_height > INITIAL_BLOCKS && !(top_block_height - INITIAL_BLOCKS + 2).is_power_of_two()
86}
87
88//---------------------------------------------------------------------------------------------------- Tests
89
90#[cfg(test)]
91mod tests {
92    use proptest::prelude::*;
93
94    use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE;
95
96    use super::*;
97
98    proptest! {
99        #[test]
100        fn compact_history(top_height in 0..MAX_BLOCK_HEIGHT_USIZE) {
101            let mut heights = (0..)
102                .map(compact_history_index_to_height_offset::<11>)
103                .map_while(|i| top_height.checked_sub(i))
104                .collect::<Vec<_>>();
105
106            if compact_history_genesis_not_included::<11>(top_height) {
107                heights.push(0);
108            }
109
110            // Make sure the genesis and top block are always included.
111            assert_eq!(*heights.last().unwrap(), 0);
112            assert_eq!(*heights.first().unwrap(), top_height);
113
114            heights.windows(2).for_each(|window| assert_ne!(window[0], window[1]));
115        }
116    }
117}