cuprate_blockchain/service/
free.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//! General free functions used (related to `cuprate_blockchain::service`).

//---------------------------------------------------------------------------------------------------- Import
use std::sync::Arc;

use cuprate_database::{ConcreteEnv, InitError};
use cuprate_types::{AltBlockInformation, VerifiedBlockInformation};

use crate::{
    config::Config,
    service::{
        init_read_service, init_write_service,
        types::{BlockchainReadHandle, BlockchainWriteHandle},
    },
};

//---------------------------------------------------------------------------------------------------- Init
#[cold]
#[inline(never)] // Only called once (?)
/// Initialize a database & thread-pool, and return a read/write handle to it.
///
/// Once the returned handles are [`Drop::drop`]ed, the reader
/// thread-pool and writer thread will exit automatically.
///
/// # Errors
/// This will forward the error if [`crate::open`] failed.
pub fn init(
    config: Config,
) -> Result<
    (
        BlockchainReadHandle,
        BlockchainWriteHandle,
        Arc<ConcreteEnv>,
    ),
    InitError,
> {
    let reader_threads = config.reader_threads;

    // Initialize the database itself.
    let db = Arc::new(crate::open(config)?);

    // Spawn the Reader thread pool and Writer.
    let readers = init_read_service(Arc::clone(&db), reader_threads);
    let writer = init_write_service(Arc::clone(&db));

    Ok((readers, writer, db))
}

//---------------------------------------------------------------------------------------------------- Compact history
/// Given a position in the compact history, returns the height offset that should be in that position.
///
/// The height offset is the difference between the top block's height and the block height that should be in that position.
#[inline]
pub(super) const fn compact_history_index_to_height_offset<const INITIAL_BLOCKS: usize>(
    i: usize,
) -> usize {
    // If the position is below the initial blocks just return the position back
    if i <= INITIAL_BLOCKS {
        i
    } else {
        // Otherwise we go with power of 2 offsets, the same as monerod.
        // So (INITIAL_BLOCKS + 2), (INITIAL_BLOCKS + 2 + 4), (INITIAL_BLOCKS + 2 + 4 + 8)
        // ref: <https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/cryptonote_core/blockchain.cpp#L727>
        INITIAL_BLOCKS + (2 << (i - INITIAL_BLOCKS)) - 2
    }
}

/// Returns if the genesis block was _NOT_ included when calculating the height offsets.
///
/// The genesis must always be included in the compact history.
#[inline]
pub(super) const fn compact_history_genesis_not_included<const INITIAL_BLOCKS: usize>(
    top_block_height: usize,
) -> bool {
    // If the top block height is less than the initial blocks then it will always be included.
    // Otherwise, we use the fact that to reach the genesis block this statement must be true (for a
    // single `i`):
    //
    // `top_block_height - INITIAL_BLOCKS - 2^i + 2 == 0`
    // which then means:
    // `top_block_height - INITIAL_BLOCKS + 2 == 2^i`
    // So if `top_block_height - INITIAL_BLOCKS + 2` is a power of 2 then the genesis block is in
    // the compact history already.
    top_block_height > INITIAL_BLOCKS && !(top_block_height - INITIAL_BLOCKS + 2).is_power_of_two()
}

//---------------------------------------------------------------------------------------------------- Map Block
/// Maps [`AltBlockInformation`] to [`VerifiedBlockInformation`]
///
/// # Panics
/// This will panic if the block is invalid, so should only be used on blocks that have been popped from
/// the main-chain.
pub(super) fn map_valid_alt_block_to_verified_block(
    alt_block: AltBlockInformation,
) -> VerifiedBlockInformation {
    let total_fees = alt_block.txs.iter().map(|tx| tx.fee).sum::<u64>();
    let total_miner_output = alt_block
        .block
        .miner_transaction
        .prefix()
        .outputs
        .iter()
        .map(|out| out.amount.unwrap_or(0))
        .sum::<u64>();

    VerifiedBlockInformation {
        block: alt_block.block,
        block_blob: alt_block.block_blob,
        txs: alt_block
            .txs
            .into_iter()
            .map(TryInto::try_into)
            .collect::<Result<_, _>>()
            .unwrap(),
        block_hash: alt_block.block_hash,
        pow_hash: alt_block.pow_hash,
        height: alt_block.height,
        generated_coins: total_miner_output - total_fees,
        weight: alt_block.weight,
        long_term_weight: alt_block.long_term_weight,
        cumulative_difficulty: alt_block.cumulative_difficulty,
    }
}

//---------------------------------------------------------------------------------------------------- Tests

#[cfg(test)]
mod tests {
    use proptest::prelude::*;

    use cuprate_constants::block::MAX_BLOCK_HEIGHT_USIZE;

    use super::*;

    proptest! {
        #[test]
        fn compact_history(top_height in 0..MAX_BLOCK_HEIGHT_USIZE) {
            let mut heights = (0..)
                .map(compact_history_index_to_height_offset::<11>)
                .map_while(|i| top_height.checked_sub(i))
                .collect::<Vec<_>>();

            if compact_history_genesis_not_included::<11>(top_height) {
                heights.push(0);
            }

            // Make sure the genesis and top block are always included.
            assert_eq!(*heights.last().unwrap(), 0);
            assert_eq!(*heights.first().unwrap(), top_height);

            heights.windows(2).for_each(|window| assert_ne!(window[0], window[1]));
        }
    }
}