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
}
}