kyoto-cbf 0.15.1

A Bitcoin light-client according to the BIP-157/BIP-158 specifications
Documentation
use std::net::{IpAddr, SocketAddr};
use std::{path::PathBuf, time::Duration};

use bitcoin::Network;

use super::{client::Client, config::NodeConfig, node::Node};
#[cfg(feature = "rusqlite")]
use crate::db::error::SqlInitializationError;
#[cfg(feature = "rusqlite")]
use crate::db::sqlite::{headers::SqliteHeaderDb, peers::SqlitePeerDb};
use crate::network::dns::{DnsResolver, DNS_RESOLVER_PORT};
use crate::network::ConnectionType;
use crate::{
    chain::checkpoints::HeaderCheckpoint,
    db::traits::{HeaderStore, PeerStore},
};
use crate::{PeerStoreSizeConfig, TrustedPeer};

#[cfg(feature = "rusqlite")]
/// The default node returned from the [`Builder`].
pub type NodeDefault = Node<SqliteHeaderDb, SqlitePeerDb>;

const MIN_PEERS: u8 = 1;
const MAX_PEERS: u8 = 15;

/// Build a [`Node`] in an additive way.
///
/// # Examples
///
/// Nodes may be built with minimal configuration.
///
/// ```rust
/// use std::net::{IpAddr, Ipv4Addr};
/// use std::collections::HashSet;
/// use kyoto::{Builder, Network};
///
/// let host = (IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), None);
/// let builder = Builder::new(Network::Regtest);
/// let (node, client) = builder
///     .add_peers(vec![host.into()])
///     .build()
///     .unwrap();
/// ```
pub struct Builder {
    config: NodeConfig,
    network: Network,
}

impl Builder {
    /// Create a new [`Builder`].
    pub fn new(network: Network) -> Self {
        Self {
            config: NodeConfig::default(),
            network,
        }
    }

    /// Fetch the [`Network`] for the builder.
    pub fn network(&self) -> Network {
        self.network
    }

    /// Add preferred peers to try to connect to.
    pub fn add_peers(mut self, whitelist: impl IntoIterator<Item = TrustedPeer>) -> Self {
        self.config.white_list.extend(whitelist);
        self
    }

    /// Add a preferred peer to try to connect to.
    pub fn add_peer(mut self, trusted_peer: impl Into<TrustedPeer>) -> Self {
        self.config.white_list.push(trusted_peer.into());
        self
    }

    /// Add a path to the directory where data should be stored. If none is provided, the current
    /// working directory will be used.
    pub fn data_dir(mut self, path: impl Into<PathBuf>) -> Self {
        self.config.data_path = Some(path.into());
        self
    }

    /// Add the minimum number of peer connections that should be maintained by the node.
    /// Adding more connections increases the node's anonymity, but requires waiting for more responses,
    /// higher bandwidth, and higher memory requirements. If none is provided, a single connection will be maintained.
    /// The number of connections will be clamped to a range of 1 to 15.
    pub fn required_peers(mut self, num_peers: u8) -> Self {
        self.config.required_peers = num_peers.clamp(MIN_PEERS, MAX_PEERS);
        self
    }

    /// Set the desired number of peers for the database to keep track of. For limited or in-memory peer storage,
    /// this number may be small, however a sufficient margin of peers should be set so the node can try many options
    /// when downloading compact block filters. For nodes that store peers on disk, more peers will typically result in
    /// fewer errors. If none is provided, no limit to the size of the store will be introduced.
    pub fn peer_db_size(mut self, target: PeerStoreSizeConfig) -> Self {
        self.config.target_peer_size = target;
        self
    }

    /// Add a checkpoint for the node to look for relevant blocks _strictly after_ the given height.
    /// This may be from the same [`HeaderCheckpoint`] every time the node is ran, or from the last known sync height.
    /// In the case of a block reorganization, the node may scan for blocks below the given block height
    /// to accurately reflect which relevant blocks are in the best chain.
    /// If none is provided, the _most recent_ checkpoint will be used.
    pub fn after_checkpoint(mut self, checkpoint: impl Into<HeaderCheckpoint>) -> Self {
        self.config.header_checkpoint = Some(checkpoint.into());
        self
    }

    /// Set the time a peer has to complete the initial TCP handshake. Even on unstable
    /// connections this may be fast.
    ///
    /// If none is provided, a timeout of two seconds will be used.
    pub fn handshake_timeout(mut self, handshake_timeout: impl Into<Duration>) -> Self {
        self.config.peer_timeout_config.handshake_timeout = handshake_timeout.into();
        self
    }

    /// Set the time duration a peer has to respond to a message from the local node.
    ///
    /// ## Note
    ///
    /// Both bandwidth and computing time should be considered when configuring this timeout.
    /// On test networks, this value may be quite short, however on the Bitcoin network,
    /// nodes may be slower to respond while processing blocks and transactions.
    ///
    /// If none is provided, a timeout of 5 seconds will be used.
    pub fn response_timeout(mut self, response_timeout: impl Into<Duration>) -> Self {
        self.config.peer_timeout_config.response_timeout = response_timeout.into();
        self
    }

    /// The maximum connection time that will be maintained with a remote peer, regardless of
    /// the quality of the peer.
    ///
    /// ## Note
    ///
    /// This value is configurable as some developers may be satisfied with a peer
    /// as long as the peer responds promptly. Other implementations may value finding
    /// new and reliable peers faster, so the maximum connection time may be shorter.
    ///
    /// If none is provided, a maximum connection time of two hours will be used.
    pub fn maximum_connection_time(mut self, max_connection_time: impl Into<Duration>) -> Self {
        self.config.peer_timeout_config.max_connection_time = max_connection_time.into();
        self
    }

    /// Configure the DNS resolver to use when querying DNS seeds.
    /// Default is `1.1.1.1:53`.
    pub fn dns_resolver(mut self, resolver: impl Into<IpAddr>) -> Self {
        let ip_addr = resolver.into();
        let socket_addr = SocketAddr::new(ip_addr, DNS_RESOLVER_PORT);
        self.config.dns_resolver = DnsResolver { socket_addr };
        self
    }

    /// Route network traffic through a Tor daemon using a Socks5 proxy. Currently, proxies
    /// must be reachable by IP address.
    pub fn socks5_proxy(mut self, proxy: impl Into<SocketAddr>) -> Self {
        let ip_addr = proxy.into();
        let connection = ConnectionType::Socks5Proxy(ip_addr);
        self.config.connection_type = connection;
        self
    }

    /// Consume the node builder and receive a [`Node`] and [`Client`].
    ///
    /// # Errors
    ///
    /// Building a node and client will error if a database connection is denied or cannot be found.
    #[cfg(feature = "rusqlite")]
    pub fn build(&mut self) -> Result<(NodeDefault, Client), SqlInitializationError> {
        let peer_store = SqlitePeerDb::new(self.network, self.config.data_path.clone())?;
        let header_store = SqliteHeaderDb::new(self.network, self.config.data_path.clone())?;
        Ok(Node::new(
            self.network,
            core::mem::take(&mut self.config),
            peer_store,
            header_store,
        ))
    }

    /// Consume the node builder by using custom database implementations, receiving a [`Node`] and [`Client`].
    pub fn build_with_databases<H: HeaderStore + 'static, P: PeerStore + 'static>(
        &mut self,
        peer_store: P,
        header_store: H,
    ) -> (Node<H, P>, Client) {
        Node::new(
            self.network,
            core::mem::take(&mut self.config),
            peer_store,
            header_store,
        )
    }
}