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};
9use cuprate_types::{AltBlockInformation, VerifiedBlockInformation};
10
11use crate::{
12    config::Config,
13    service::{
14        init_read_service, init_read_service_with_pool, init_write_service,
15        types::{BlockchainReadHandle, BlockchainWriteHandle},
16    },
17};
18
19//---------------------------------------------------------------------------------------------------- Init
20#[cold]
21#[inline(never)] // Only called once (?)
22/// Initialize a database & thread-pool, and return a read/write handle to it.
23///
24/// Once the returned handles are [`Drop::drop`]ed, the reader
25/// thread-pool and writer thread will exit automatically.
26///
27/// # Errors
28/// This will forward the error if [`crate::open`] failed.
29pub fn init(
30    config: Config,
31) -> Result<
32    (
33        BlockchainReadHandle,
34        BlockchainWriteHandle,
35        Arc<ConcreteEnv>,
36    ),
37    InitError,
38> {
39    let reader_threads = config.reader_threads;
40
41    // Initialize the database itself.
42    let db = Arc::new(crate::open(config)?);
43
44    // Spawn the Reader thread pool and Writer.
45    let readers = init_read_service(Arc::clone(&db), reader_threads);
46    let writer = init_write_service(Arc::clone(&db));
47
48    Ok((readers, writer, db))
49}
50
51#[cold]
52#[inline(never)] // Only called once (?)
53/// Initialize a database, and return a read/write handle to it.
54///
55/// Unlike [`init`] this will not create a thread-pool, instead using
56/// the one passed in.
57///
58/// Once the returned handles are [`Drop::drop`]ed, the reader
59/// thread-pool and writer thread will exit automatically.
60///
61/// # Errors
62/// This will forward the error if [`crate::open`] failed.
63pub fn init_with_pool(
64    config: Config,
65    pool: Arc<ThreadPool>,
66) -> Result<
67    (
68        BlockchainReadHandle,
69        BlockchainWriteHandle,
70        Arc<ConcreteEnv>,
71    ),
72    InitError,
73> {
74    // Initialize the database itself.
75    let db = Arc::new(crate::open(config)?);
76
77    // Spawn the Reader thread pool and Writer.
78    let readers = init_read_service_with_pool(Arc::clone(&db), pool);
79    let writer = init_write_service(Arc::clone(&db));
80
81    Ok((readers, writer, db))
82}
83
84//---------------------------------------------------------------------------------------------------- Compact history
85/// Given a position in the compact history, returns the height offset that should be in that position.
86///
87/// The height offset is the difference between the top block's height and the block height that should be in that position.
88#[inline]
89pub(super) const fn compact_history_index_to_height_offset<const INITIAL_BLOCKS: usize>(
90    i: usize,
91) -> usize {
92    // If the position is below the initial blocks just return the position back
93    if i <= INITIAL_BLOCKS {
94        i
95    } else {
96        // Otherwise we go with power of 2 offsets, the same as monerod.
97        // So (INITIAL_BLOCKS + 2), (INITIAL_BLOCKS + 2 + 4), (INITIAL_BLOCKS + 2 + 4 + 8)
98        // ref: <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/cryptonote_core/blockchain.cpp#L727>
99        INITIAL_BLOCKS + (2 << (i - INITIAL_BLOCKS)) - 2
100    }
101}
102
103/// Returns if the genesis block was _NOT_ included when calculating the height offsets.
104///
105/// The genesis must always be included in the compact history.
106#[inline]
107pub(super) const fn compact_history_genesis_not_included<const INITIAL_BLOCKS: usize>(
108    top_block_height: usize,
109) -> bool {
110    // If the top block height is less than the initial blocks then it will always be included.
111    // Otherwise, we use the fact that to reach the genesis block this statement must be true (for a
112    // single `i`):
113    //
114    // `top_block_height - INITIAL_BLOCKS - 2^i + 2 == 0`
115    // which then means:
116    // `top_block_height - INITIAL_BLOCKS + 2 == 2^i`
117    // So if `top_block_height - INITIAL_BLOCKS + 2` is a power of 2 then the genesis block is in
118    // the compact history already.
119    top_block_height > INITIAL_BLOCKS && !(top_block_height - INITIAL_BLOCKS + 2).is_power_of_two()
120}
121
122//---------------------------------------------------------------------------------------------------- Map Block
123/// Maps [`AltBlockInformation`] to [`VerifiedBlockInformation`]
124///
125/// # Panics
126/// This will panic if the block is invalid, so should only be used on blocks that have been popped from
127/// the main-chain.
128pub(super) fn map_valid_alt_block_to_verified_block(
129    alt_block: AltBlockInformation,
130) -> VerifiedBlockInformation {
131    let total_fees = alt_block.txs.iter().map(|tx| tx.fee).sum::<u64>();
132    let total_miner_output = alt_block
133        .block
134        .miner_transaction
135        .prefix()
136        .outputs
137        .iter()
138        .map(|out| out.amount.unwrap_or(0))
139        .sum::<u64>();
140
141    VerifiedBlockInformation {
142        block: alt_block.block,
143        block_blob: alt_block.block_blob,
144        txs: alt_block
145            .txs
146            .into_iter()
147            .map(TryInto::try_into)
148            .collect::<Result<_, _>>()
149            .unwrap(),
150        block_hash: alt_block.block_hash,
151        pow_hash: alt_block.pow_hash,
152        height: alt_block.height,
153        generated_coins: total_miner_output - total_fees,
154        weight: alt_block.weight,
155        long_term_weight: alt_block.long_term_weight,
156        cumulative_difficulty: alt_block.cumulative_difficulty,
157    }
158}
159
160//---------------------------------------------------------------------------------------------------- Tests
161
162#[cfg(test)]
163mod tests {
164    use proptest::prelude::*;
165
166    use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE;
167
168    use super::*;
169
170    proptest! {
171        #[test]
172        fn compact_history(top_height in 0..MAX_BLOCK_HEIGHT_USIZE) {
173            let mut heights = (0..)
174                .map(compact_history_index_to_height_offset::<11>)
175                .map_while(|i| top_height.checked_sub(i))
176                .collect::<Vec<_>>();
177
178            if compact_history_genesis_not_included::<11>(top_height) {
179                heights.push(0);
180            }
181
182            // Make sure the genesis and top block are always included.
183            assert_eq!(*heights.last().unwrap(), 0);
184            assert_eq!(*heights.first().unwrap(), top_height);
185
186            heights.windows(2).for_each(|window| assert_ne!(window[0], window[1]));
187        }
188    }
189}