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}