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}