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}