kyoto/builder.rs
1use std::net::{IpAddr, SocketAddr};
2use std::{path::PathBuf, time::Duration};
3
4use bitcoin::Network;
5
6use super::{client::Client, config::NodeConfig, node::Node};
7#[cfg(feature = "rusqlite")]
8use crate::db::error::SqlInitializationError;
9#[cfg(feature = "rusqlite")]
10use crate::db::sqlite::{headers::SqliteHeaderDb, peers::SqlitePeerDb};
11use crate::network::dns::{DnsResolver, DNS_RESOLVER_PORT};
12use crate::network::ConnectionType;
13use crate::{
14 chain::checkpoints::HeaderCheckpoint,
15 db::traits::{HeaderStore, PeerStore},
16};
17use crate::{PeerStoreSizeConfig, TrustedPeer};
18
19#[cfg(feature = "rusqlite")]
20/// The default node returned from the [`Builder`].
21pub type NodeDefault = Node<SqliteHeaderDb, SqlitePeerDb>;
22
23const MIN_PEERS: u8 = 1;
24const MAX_PEERS: u8 = 15;
25
26/// Build a [`Node`] in an additive way.
27///
28/// # Examples
29///
30/// Nodes may be built with minimal configuration.
31///
32/// ```rust
33/// use std::net::{IpAddr, Ipv4Addr};
34/// use std::collections::HashSet;
35/// use kyoto::{Builder, Network};
36///
37/// let host = (IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), None);
38/// let builder = Builder::new(Network::Regtest);
39/// let (node, client) = builder
40/// .add_peers(vec![host.into()])
41/// .build()
42/// .unwrap();
43/// ```
44pub struct Builder {
45 config: NodeConfig,
46 network: Network,
47}
48
49impl Builder {
50 /// Create a new [`Builder`].
51 pub fn new(network: Network) -> Self {
52 Self {
53 config: NodeConfig::default(),
54 network,
55 }
56 }
57
58 /// Fetch the [`Network`] for the builder.
59 pub fn network(&self) -> Network {
60 self.network
61 }
62
63 /// Add preferred peers to try to connect to.
64 pub fn add_peers(mut self, whitelist: impl IntoIterator<Item = TrustedPeer>) -> Self {
65 self.config.white_list.extend(whitelist);
66 self
67 }
68
69 /// Add a preferred peer to try to connect to.
70 pub fn add_peer(mut self, trusted_peer: impl Into<TrustedPeer>) -> Self {
71 self.config.white_list.push(trusted_peer.into());
72 self
73 }
74
75 /// Add a path to the directory where data should be stored. If none is provided, the current
76 /// working directory will be used.
77 pub fn data_dir(mut self, path: impl Into<PathBuf>) -> Self {
78 self.config.data_path = Some(path.into());
79 self
80 }
81
82 /// Add the minimum number of peer connections that should be maintained by the node.
83 /// Adding more connections increases the node's anonymity, but requires waiting for more responses,
84 /// higher bandwidth, and higher memory requirements. If none is provided, a single connection will be maintained.
85 /// The number of connections will be clamped to a range of 1 to 15.
86 pub fn required_peers(mut self, num_peers: u8) -> Self {
87 self.config.required_peers = num_peers.clamp(MIN_PEERS, MAX_PEERS);
88 self
89 }
90
91 /// Set the desired number of peers for the database to keep track of. For limited or in-memory peer storage,
92 /// this number may be small, however a sufficient margin of peers should be set so the node can try many options
93 /// when downloading compact block filters. For nodes that store peers on disk, more peers will typically result in
94 /// fewer errors. If none is provided, no limit to the size of the store will be introduced.
95 pub fn peer_db_size(mut self, target: PeerStoreSizeConfig) -> Self {
96 self.config.target_peer_size = target;
97 self
98 }
99
100 /// Add a checkpoint for the node to look for relevant blocks _strictly after_ the given height.
101 /// This may be from the same [`HeaderCheckpoint`] every time the node is ran, or from the last known sync height.
102 /// In the case of a block reorganization, the node may scan for blocks below the given block height
103 /// to accurately reflect which relevant blocks are in the best chain.
104 /// If none is provided, the _most recent_ checkpoint will be used.
105 pub fn after_checkpoint(mut self, checkpoint: impl Into<HeaderCheckpoint>) -> Self {
106 self.config.header_checkpoint = Some(checkpoint.into());
107 self
108 }
109
110 /// Set the time a peer has to complete the initial TCP handshake. Even on unstable
111 /// connections this may be fast.
112 ///
113 /// If none is provided, a timeout of two seconds will be used.
114 pub fn handshake_timeout(mut self, handshake_timeout: impl Into<Duration>) -> Self {
115 self.config.peer_timeout_config.handshake_timeout = handshake_timeout.into();
116 self
117 }
118
119 /// Set the time duration a peer has to respond to a message from the local node.
120 ///
121 /// ## Note
122 ///
123 /// Both bandwidth and computing time should be considered when configuring this timeout.
124 /// On test networks, this value may be quite short, however on the Bitcoin network,
125 /// nodes may be slower to respond while processing blocks and transactions.
126 ///
127 /// If none is provided, a timeout of 5 seconds will be used.
128 pub fn response_timeout(mut self, response_timeout: impl Into<Duration>) -> Self {
129 self.config.peer_timeout_config.response_timeout = response_timeout.into();
130 self
131 }
132
133 /// The maximum connection time that will be maintained with a remote peer, regardless of
134 /// the quality of the peer.
135 ///
136 /// ## Note
137 ///
138 /// This value is configurable as some developers may be satisfied with a peer
139 /// as long as the peer responds promptly. Other implementations may value finding
140 /// new and reliable peers faster, so the maximum connection time may be shorter.
141 ///
142 /// If none is provided, a maximum connection time of two hours will be used.
143 pub fn maximum_connection_time(mut self, max_connection_time: impl Into<Duration>) -> Self {
144 self.config.peer_timeout_config.max_connection_time = max_connection_time.into();
145 self
146 }
147
148 /// Configure the DNS resolver to use when querying DNS seeds.
149 /// Default is `1.1.1.1:53`.
150 pub fn dns_resolver(mut self, resolver: impl Into<IpAddr>) -> Self {
151 let ip_addr = resolver.into();
152 let socket_addr = SocketAddr::new(ip_addr, DNS_RESOLVER_PORT);
153 self.config.dns_resolver = DnsResolver { socket_addr };
154 self
155 }
156
157 /// Route network traffic through a Tor daemon using a Socks5 proxy. Currently, proxies
158 /// must be reachable by IP address.
159 pub fn socks5_proxy(mut self, proxy: impl Into<SocketAddr>) -> Self {
160 let ip_addr = proxy.into();
161 let connection = ConnectionType::Socks5Proxy(ip_addr);
162 self.config.connection_type = connection;
163 self
164 }
165
166 /// Consume the node builder and receive a [`Node`] and [`Client`].
167 ///
168 /// # Errors
169 ///
170 /// Building a node and client will error if a database connection is denied or cannot be found.
171 #[cfg(feature = "rusqlite")]
172 pub fn build(&mut self) -> Result<(NodeDefault, Client), SqlInitializationError> {
173 let peer_store = SqlitePeerDb::new(self.network, self.config.data_path.clone())?;
174 let header_store = SqliteHeaderDb::new(self.network, self.config.data_path.clone())?;
175 Ok(Node::new(
176 self.network,
177 core::mem::take(&mut self.config),
178 peer_store,
179 header_store,
180 ))
181 }
182
183 /// Consume the node builder by using custom database implementations, receiving a [`Node`] and [`Client`].
184 pub fn build_with_databases<H: HeaderStore + 'static, P: PeerStore + 'static>(
185 &mut self,
186 peer_store: P,
187 header_store: H,
188 ) -> (Node<H, P>, Client) {
189 Node::new(
190 self.network,
191 core::mem::take(&mut self.config),
192 peer_store,
193 header_store,
194 )
195 }
196}