floresta_chain/pruned_utreexo/
chain_state_builder.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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! This module provides a builder pattern for constructing ChainState instances with various
//! optional configurations.
//!
//! It includes:
//! - Chain parameters
//! - Chainstore backend
//! - Initial block download status
//! - Assumed valid blocks for validation optimization
//! - UTREEXO accumulator state
//! - Current chain tip and header
use bitcoin::block::Header as BlockHeader;
use bitcoin::BlockHash;
use bitcoin::Network;
use rustreexo::accumulator::stump::Stump;

use super::chain_state::BestChain;
use super::chain_state::ChainState;
use super::chainparams::ChainParams;
use super::ChainStore;
use crate::pruned_utreexo::Box;
use crate::AssumeValidArg;
use crate::DatabaseError;
use crate::DiskBlockHeader;

#[derive(Debug)]
/// Represents errors that can occur during the construction of a ChainState instance.
pub enum BlockchainBuilderError {
    /// Indicates that the chainstore is missing.
    MissingChainstore,

    /// Indicates that the chain parameters are missing.
    MissingChainParams,

    /// Indicates that the `tip` and `tip_header` parameters were not provided together.
    IncompleteTip,

    /// Error while trying to save initial data.
    Database(Box<dyn DatabaseError>),
}

#[derive(Clone, Debug, Default)]
/// A builder for configuring and creating a `ChainState`.
///
/// It implements a few methods to access and modify the settings. Call `.build()` to consume the
/// builder and produce the `ChainState`.
pub struct ChainStateBuilder<PersistedState: ChainStore> {
    /// The accumulator stump.
    acc: Option<Stump>,

    /// The chain store.
    chainstore: Option<PersistedState>,

    /// Indicates whether the builder is in initial block download mode.
    ibd: bool,

    /// The chain parameters.
    chain_params: Option<ChainParams>,

    /// The block hash that is assumed to be valid.
    assume_valid: Option<BlockHash>,

    /// The current chain tip.
    tip: Option<(BlockHash, u32)>,

    /// The first block header.
    tip_header: Option<BlockHeader>,
}

impl<T: ChainStore> ChainStateBuilder<T> {
    /// Creates a new instance of ChainStateBuilder.
    pub fn new() -> Self {
        ChainStateBuilder {
            acc: None,
            chainstore: None,
            ibd: true,
            chain_params: None,
            assume_valid: None,
            tip: None,
            tip_header: None,
        }
    }

    /// Builds the chain state. Returns error if the `chainstore` or `chain_params` are missing, or
    /// if only one of `tip_header` and `tip` is set (either set both or none).
    pub fn build(mut self) -> Result<ChainState<T>, BlockchainBuilderError> {
        let chainstore = self
            .chainstore
            .as_mut()
            .ok_or(BlockchainBuilderError::MissingChainstore)?;

        // Tip header and tip tuple must come as a pair (both Some or both None)
        match (self.tip_header, self.tip) {
            // Persist both values
            (Some(first_header), Some((height, block_hash))) => {
                chainstore.save_header(&DiskBlockHeader::FullyValid(first_header, block_hash))?;
                chainstore.update_block_index(block_hash, height)?;
            }
            // Do nothing
            (None, None) => {}
            // One was Some and one None, return error
            _ => return Err(BlockchainBuilderError::IncompleteTip),
        }

        ChainState::try_from(self)
    }

    /// Set the chainstore backend, implementing [ChainStore]. **Always required**.
    pub fn with_chainstore(mut self, chainstore: T) -> Self {
        self.chainstore = Some(chainstore);
        self
    }

    /// Enable or disable Initial Block Download (IBD) mode.
    pub fn toggle_ibd(mut self, ibd: bool) -> Self {
        self.ibd = ibd;
        self
    }

    /// Sets the chain parameters. **Always required**.
    pub fn with_chain_params(mut self, chain_params: ChainParams) -> Self {
        self.chain_params = Some(chain_params);
        self
    }

    /// Sets the assume-valid argument, which can be `Disabled`, `Hardcoded` or `UserInput`. This
    /// option is used to skip script validation up to the specified block, speeding up IBD.
    pub fn with_assume_valid(mut self, arg: AssumeValidArg, network: Network) -> Self {
        // TODO: handle possible Err
        self.assume_valid =
            ChainParams::get_assume_valid(network, arg).expect("Unsupported network");
        self
    }

    /// Sets the utreexo accumulator, assumed as the initial state.
    pub fn assume_utreexo(mut self, acc: Stump) -> Self {
        self.acc = Some(acc);
        self
    }

    /// Sets the tip block data, assumed as the initial state.
    pub fn with_tip(mut self, tip: (BlockHash, u32), header: BlockHeader) -> Self {
        self.tip = Some(tip);
        self.tip_header = Some(header);
        self
    }

    /// Returns the utreexo accumulator that was set or None if empty.
    pub(super) fn acc(&self) -> Option<Stump> {
        self.acc.clone()
    }

    /// Take the chainstore out of the builder, returning it or an error if missing.
    pub(super) fn chainstore(&mut self) -> Result<T, BlockchainBuilderError> {
        self.chainstore
            .take()
            .ok_or(BlockchainBuilderError::MissingChainstore)
    }

    /// Returns whether Initial Block Download (IBD) mode is enabled.
    pub(super) fn ibd(&self) -> bool {
        self.ibd
    }

    /// Get the chain parameters, returning an error if they haven't been set.
    pub(super) fn chain_params(&self) -> Result<ChainParams, BlockchainBuilderError> {
        self.chain_params
            .clone()
            .ok_or(BlockchainBuilderError::MissingChainParams)
    }

    /// Get the specified best tip as a `BestChain`, or fall back to the genesis block if unset.
    /// Returns an error if chain parameters are missing when determining the genesis block.
    pub(super) fn best_block(&self) -> Result<BestChain, BlockchainBuilderError> {
        let block = match self.tip {
            Some(value) => value,
            None => (self.chain_params()?.genesis.header.block_hash(), 0),
        };

        Ok(BestChain::from(block))
    }

    /// Returns the block hash of the assume-valid option, if enabled.
    pub(super) fn assume_valid(&self) -> Option<BlockHash> {
        self.assume_valid
    }
}