kyoto/lib.rs
1//! Kyoto is a conservative, private, and vetted Bitcoin client built in accordance
2//! with the [BIP157](https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki) and [BIP158](https://github.com/bitcoin/bips/blob/master/bip-0158.mediawiki)
3//! standards. _Conservative_, as in Kyoto makes very little assumptions about the underlying memory requirements of the
4//! device running the software. _Private_, as in the Bitcoin nodes that serve Kyoto nodes data do not know what transactions the
5//! client is querying for, only the entire Bitcoin block. _Vetted_, as in the dependencies of the core library are meant to remain limited,
6//! rigorously tested, and absolutely necessary.
7//!
8//! # Example usage
9//!
10//! ```no_run
11//! use kyoto::{Builder, Event, Client, Network, BlockHash};
12//!
13//! #[tokio::main]
14//! async fn main() {
15//! // Add third-party logging
16//! let subscriber = tracing_subscriber::FmtSubscriber::new();
17//! tracing::subscriber::set_global_default(subscriber).unwrap();
18//! // Create a new node builder
19//! let builder = Builder::new(Network::Signet);
20//! // Add node preferences and build the node/client
21//! let (node, client) = builder
22//! // The number of connections we would like to maintain
23//! .required_peers(2)
24//! .build()
25//! .unwrap();
26//! // Run the node and wait for the sync message;
27//! tokio::task::spawn(async move { node.run().await });
28//! // Split the client into components that send messages and listen to messages
29//! let Client { requester, info_rx: _, warn_rx: _, mut event_rx } = client;
30//! loop {
31//! if let Some(event) = event_rx.recv().await {
32//! match event {
33//! Event::FiltersSynced(_) => {
34//! tracing::info!("Sync complete!");
35//! break;
36//! },
37//! _ => (),
38//! }
39//! }
40//! }
41//! requester.shutdown();
42//! }
43//! ```
44//!
45//! # Features
46//!
47//! `rusqlite`: use the default `rusqlite` database implementations. Default and recommend feature.
48
49#![warn(missing_docs)]
50pub mod chain;
51pub mod db;
52
53mod network;
54mod prelude;
55use network::dns::CBF_SERVICE_BIT_PREFIX;
56use network::dns::CBF_V2T_SERVICE_BIT_PREFIX;
57pub(crate) use prelude::impl_sourceless_error;
58
59mod broadcaster;
60/// Convenient way to build a compact filters node.
61pub mod builder;
62pub(crate) mod channel_messages;
63/// Structures to communicate with a node.
64pub mod client;
65/// Node configuration options.
66pub(crate) mod config;
67pub(crate) mod dialog;
68/// Errors associated with a node.
69pub mod error;
70/// Messages the node may send a client.
71pub mod messages;
72/// The structure that communicates with the Bitcoin P2P network and collects data.
73pub mod node;
74
75use chain::Filter;
76
77use network::dns::DnsQuery;
78use network::dns::DNS_RESOLVER_PORT;
79
80use std::net::{IpAddr, SocketAddr};
81
82// Re-exports
83#[doc(inline)]
84pub use chain::checkpoints::HeaderCheckpoint;
85
86#[doc(inline)]
87#[cfg(feature = "rusqlite")]
88pub use db::sqlite::{headers::SqliteHeaderDb, peers::SqlitePeerDb};
89
90#[doc(inline)]
91pub use db::traits::{HeaderStore, PeerStore};
92
93#[doc(inline)]
94pub use tokio::sync::mpsc::Receiver;
95#[doc(inline)]
96pub use tokio::sync::mpsc::UnboundedReceiver;
97
98#[doc(inline)]
99pub use {
100 crate::builder::Builder,
101 crate::client::{Client, Requester},
102 crate::error::{ClientError, NodeError},
103 crate::messages::{Event, Info, Progress, RejectPayload, SyncUpdate, Warning},
104 crate::network::PeerTimeoutConfig,
105 crate::node::Node,
106};
107
108#[doc(inline)]
109pub use bitcoin::bip158::BlockFilter;
110#[doc(inline)]
111pub use bitcoin::{
112 block::Header, p2p::address::AddrV2, p2p::message_network::RejectReason, p2p::ServiceFlags,
113 Address, Block, BlockHash, FeeRate, Network, ScriptBuf, Transaction, Txid,
114};
115
116pub extern crate tokio;
117
118/// A Bitcoin [`Block`] with associated height.
119#[derive(Debug, Clone)]
120pub struct IndexedBlock {
121 /// The height or index in the chain.
122 pub height: u32,
123 /// The Bitcoin block with some matching script.
124 pub block: Block,
125}
126
127impl IndexedBlock {
128 pub(crate) fn new(height: u32, block: Block) -> Self {
129 Self { height, block }
130 }
131}
132
133/// A compact block filter with associated height.
134#[derive(Debug, Clone, PartialEq, Eq)]
135pub struct IndexedFilter {
136 height: u32,
137 filter: Filter,
138}
139
140impl IndexedFilter {
141 fn new(height: u32, filter: Filter) -> Self {
142 Self { height, filter }
143 }
144
145 /// The height in the chain.
146 pub fn height(&self) -> u32 {
147 self.height
148 }
149
150 /// Return the [`BlockHash`] associated with this filer
151 pub fn block_hash(&self) -> BlockHash {
152 *self.filter.block_hash()
153 }
154
155 /// Does the filter contain a positive match for any of the provided scripts
156 pub fn contains_any<'a>(&'a self, scripts: impl Iterator<Item = &'a ScriptBuf>) -> bool {
157 self.filter
158 .contains_any(scripts)
159 .expect("vec reader is infallible")
160 }
161
162 /// Consume the index and get underlying block filter.
163 pub fn block_filter(self) -> BlockFilter {
164 self.filter.into_filter()
165 }
166
167 /// Consume the filter and get the raw bytes
168 pub fn into_contents(self) -> Vec<u8> {
169 self.filter.contents()
170 }
171}
172
173impl std::cmp::PartialOrd for IndexedFilter {
174 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
175 Some(self.cmp(other))
176 }
177}
178
179impl std::cmp::Ord for IndexedFilter {
180 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
181 self.height.cmp(&other.height)
182 }
183}
184
185/// Broadcast a [`Transaction`] to a set of connected peers.
186#[derive(Debug, Clone)]
187pub struct TxBroadcast {
188 /// The presumably valid Bitcoin transaction.
189 pub tx: Transaction,
190 /// The strategy for how this transaction should be shared with the network.
191 pub broadcast_policy: TxBroadcastPolicy,
192}
193
194impl TxBroadcast {
195 /// Prepare a transaction for broadcast with associated broadcast strategy.
196 pub fn new(tx: Transaction, broadcast_policy: TxBroadcastPolicy) -> Self {
197 Self {
198 tx,
199 broadcast_policy,
200 }
201 }
202
203 /// Prepare a transaction to be broadcasted to a random connection.
204 pub fn random_broadcast(tx: Transaction) -> Self {
205 Self {
206 tx,
207 broadcast_policy: TxBroadcastPolicy::RandomPeer,
208 }
209 }
210}
211
212/// The strategy for how this transaction should be shared with the network.
213#[derive(Debug, Default, Clone)]
214pub enum TxBroadcastPolicy {
215 /// Broadcast the transaction to all peers at the same time.
216 AllPeers,
217 /// Broadcast the transaction to a single random peer, optimal for user privacy.
218 #[default]
219 RandomPeer,
220}
221
222/// A peer on the Bitcoin P2P network
223///
224/// # Building peers
225///
226/// ```rust
227/// use std::net::{IpAddr, Ipv4Addr};
228/// use kyoto::{TrustedPeer, ServiceFlags, AddrV2};
229///
230/// let local_host = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
231/// let mut trusted = TrustedPeer::from_ip(local_host);
232/// // Optionally set the known services of the peer later.
233/// trusted.set_services(ServiceFlags::P2P_V2);
234///
235/// let local_host = Ipv4Addr::new(0, 0, 0, 0);
236/// // Or construct a trusted peer directly.
237/// let trusted = TrustedPeer::new(AddrV2::Ipv4(local_host), None, ServiceFlags::P2P_V2);
238///
239/// // Or implicitly with `into`
240/// let local_host = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0));
241/// let trusted: TrustedPeer = (local_host, None).into();
242/// ```
243#[derive(Debug, Clone)]
244pub struct TrustedPeer {
245 /// The IP address of the remote node to connect to.
246 pub address: AddrV2,
247 /// The port to establish a TCP connection. If none is provided, the typical Bitcoin Core port is used as the default.
248 pub port: Option<u16>,
249 /// The services this peer is known to offer before starting the node.
250 pub known_services: ServiceFlags,
251}
252
253impl TrustedPeer {
254 /// Create a new trusted peer.
255 pub fn new(address: AddrV2, port: Option<u16>, services: ServiceFlags) -> Self {
256 Self {
257 address,
258 port,
259 known_services: services,
260 }
261 }
262
263 /// Create a new trusted peer using the default port for the network.
264 pub fn from_ip(ip_addr: impl Into<IpAddr>) -> Self {
265 let address = match ip_addr.into() {
266 IpAddr::V4(ip) => AddrV2::Ipv4(ip),
267 IpAddr::V6(ip) => AddrV2::Ipv6(ip),
268 };
269 Self {
270 address,
271 port: None,
272 known_services: ServiceFlags::NONE,
273 }
274 }
275
276 /// Create a new peer from a known address and port.
277 pub fn from_socket_addr(socket_addr: impl Into<SocketAddr>) -> Self {
278 let socket_addr: SocketAddr = socket_addr.into();
279 let address = match socket_addr {
280 SocketAddr::V4(ip) => AddrV2::Ipv4(*ip.ip()),
281 SocketAddr::V6(ip) => AddrV2::Ipv6(*ip.ip()),
282 };
283 Self {
284 address,
285 port: Some(socket_addr.port()),
286 known_services: ServiceFlags::NONE,
287 }
288 }
289
290 /// The IP address of the trusted peer.
291 pub fn address(&self) -> AddrV2 {
292 self.address.clone()
293 }
294
295 /// A recommended port to connect to, if there is one.
296 pub fn port(&self) -> Option<u16> {
297 self.port
298 }
299
300 /// The services this peer is known to offer.
301 pub fn services(&self) -> ServiceFlags {
302 self.known_services
303 }
304
305 /// Set the known services for this trusted peer.
306 pub fn set_services(&mut self, services: ServiceFlags) {
307 self.known_services = services;
308 }
309}
310
311impl From<(IpAddr, Option<u16>)> for TrustedPeer {
312 fn from(value: (IpAddr, Option<u16>)) -> Self {
313 let address = match value.0 {
314 IpAddr::V4(ip) => AddrV2::Ipv4(ip),
315 IpAddr::V6(ip) => AddrV2::Ipv6(ip),
316 };
317 TrustedPeer::new(address, value.1, ServiceFlags::NONE)
318 }
319}
320
321impl From<TrustedPeer> for (AddrV2, Option<u16>) {
322 fn from(value: TrustedPeer) -> Self {
323 (value.address(), value.port())
324 }
325}
326
327impl From<IpAddr> for TrustedPeer {
328 fn from(value: IpAddr) -> Self {
329 TrustedPeer::from_ip(value)
330 }
331}
332
333impl From<SocketAddr> for TrustedPeer {
334 fn from(value: SocketAddr) -> Self {
335 TrustedPeer::from_socket_addr(value)
336 }
337}
338
339/// Configure how many peers will be stored.
340#[derive(Debug, Default, Clone)]
341pub enum PeerStoreSizeConfig {
342 /// Add new peers to the store regardless of the current size. For memory-limited [`PeerStore`]
343 /// implementations, consider using a bounded size.
344 #[default]
345 Unbounded,
346 /// Bound the size of the [`PeerStore`]. When set, no new peers will be requested if the database
347 /// has at least this amount of peers.
348 Limit(u32),
349}
350
351// The state of the node with respect to connected peers.
352#[derive(Debug, Clone, Copy)]
353enum NodeState {
354 /// We are behind on block headers according to our peers.
355 Behind,
356 /// We may start downloading compact block filter headers.
357 HeadersSynced,
358 /// We may start scanning compact block filters.
359 FilterHeadersSynced,
360 /// We may start asking for blocks with matches.
361 FiltersSynced,
362}
363
364impl core::fmt::Display for NodeState {
365 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
366 match self {
367 NodeState::Behind => {
368 write!(f, "Requesting block headers.")
369 }
370 NodeState::HeadersSynced => {
371 write!(f, "Requesting compact filter headers.")
372 }
373 NodeState::FilterHeadersSynced => {
374 write!(f, "Requesting compact block filters.")
375 }
376 NodeState::FiltersSynced => write!(f, "Downloading blocks with relevant transactions."),
377 }
378 }
379}
380
381/// Query a Bitcoin DNS seeder using the configured resolver.
382///
383/// This is **not** a generic DNS implementation. It is specifically tailored to query and parse DNS for Bitcoin seeders.
384/// Iternally, three queries will be made with varying filters for the requested service flags.
385/// Similar to [`lookup_host`](tokio::net::lookup_host), this has no guarantee to return any `IpAddr`.
386///
387/// # Example usage
388///
389/// ```no_run
390/// use std::net::{IpAddr, Ipv4Addr};
391///
392/// use kyoto::lookup_host;
393///
394/// #[tokio::main]
395/// async fn main() {
396/// // Use cloudflare's DNS resolver
397/// let resolver = IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1));
398/// let addrs = lookup_host("seed.bitcoin.sipa.be", resolver).await;
399/// }
400/// ```
401pub async fn lookup_host<S: AsRef<str>>(hostname: S, resolver: impl Into<IpAddr>) -> Vec<IpAddr> {
402 let ip_addr = resolver.into();
403 let socket_addr = SocketAddr::new(ip_addr, DNS_RESOLVER_PORT);
404 let mut responses = Vec::new();
405 let dns_query = DnsQuery::new(hostname.as_ref(), None);
406 let no_filter = dns_query.lookup(socket_addr).await.unwrap_or(Vec::new());
407 responses.extend(no_filter);
408 let dns_query = DnsQuery::new(hostname.as_ref(), Some(CBF_V2T_SERVICE_BIT_PREFIX));
409 let no_filter = dns_query.lookup(socket_addr).await.unwrap_or(Vec::new());
410 responses.extend(no_filter);
411 let dns_query = DnsQuery::new(hostname.as_ref(), Some(CBF_SERVICE_BIT_PREFIX));
412 let no_filter = dns_query.lookup(socket_addr).await.unwrap_or(Vec::new());
413 responses.extend(no_filter);
414 responses
415}
416
417macro_rules! debug {
418 ($expr:expr) => {
419 #[cfg(debug_assertions)]
420 println!("{}", $expr)
421 };
422}
423
424pub(crate) use debug;