cuprate_blockchain/service/
free.rs

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