nakamoto_client/
handle.rs

1//! Node handles are created from nodes by users of the library, to communicate with the underlying
2//! protocol instance.
3use std::net;
4use std::ops::{RangeBounds, RangeInclusive};
5
6use crossbeam_channel as chan;
7use thiserror::Error;
8
9use nakamoto_common::bitcoin::network::constants::ServiceFlags;
10use nakamoto_common::bitcoin::network::Address;
11use nakamoto_common::bitcoin::Script;
12
13use nakamoto_common::bitcoin::network::message::NetworkMessage;
14use nakamoto_common::block::filter::BlockFilter;
15use nakamoto_common::block::tree::{BlockReader, ImportResult};
16use nakamoto_common::block::{self, Block, BlockHash, BlockHeader, Height, Transaction};
17use nakamoto_common::nonempty::NonEmpty;
18use nakamoto_p2p::fsm::Link;
19use nakamoto_p2p::fsm::{self, Command, CommandError, GetFiltersError, Peer};
20
21use crate::client::Event;
22
23/// An error resulting from a handle method.
24#[derive(Error, Debug)]
25pub enum Error {
26    /// The command channel disconnected.
27    #[error("command channel disconnected")]
28    Disconnected,
29    /// The command returned an error.
30    #[error("command failed: {0}")]
31    Command(#[from] CommandError),
32    /// Failed to fetch filters.
33    #[error("failed to get filters: {0}")]
34    GetFilters(#[from] GetFiltersError),
35    /// The operation timed out.
36    #[error("the operation timed out")]
37    Timeout,
38    /// An I/O error occured.
39    #[error(transparent)]
40    Io(#[from] std::io::Error),
41}
42
43impl From<chan::RecvError> for Error {
44    fn from(_: chan::RecvError) -> Self {
45        Self::Disconnected
46    }
47}
48
49impl From<chan::RecvTimeoutError> for Error {
50    fn from(err: chan::RecvTimeoutError) -> Self {
51        match err {
52            chan::RecvTimeoutError::Timeout => Self::Timeout,
53            chan::RecvTimeoutError::Disconnected => Self::Disconnected,
54        }
55    }
56}
57
58impl<T> From<chan::SendError<T>> for Error {
59    fn from(_: chan::SendError<T>) -> Self {
60        Self::Disconnected
61    }
62}
63
64/// A handle for communicating with a node process.
65pub trait Handle: Sized + Send + Sync + Clone {
66    /// Get the tip of the chain.
67    fn get_tip(&self) -> Result<(Height, BlockHeader), Error>;
68    /// Get a full block from the network.
69    fn get_block(&self, hash: &BlockHash) -> Result<(), Error>;
70    /// Get compact filters from the network.
71    fn get_filters(&self, range: RangeInclusive<Height>) -> Result<(), Error>;
72    /// Query the block tree using the given function. To return results from
73    /// the query function, a [channel](`crate::chan`) may be used.
74    fn query_tree(
75        &self,
76        query: impl Fn(&dyn BlockReader) + Send + Sync + 'static,
77    ) -> Result<(), Error>;
78    /// Find a branch from the active chain to the given (stale) block.
79    ///
80    /// See [BlockReader::find_branch](`nakamoto_common::block::tree::BlockReader::find_branch`).
81    fn find_branch(&self, to: &BlockHash)
82        -> Result<Option<(Height, NonEmpty<BlockHeader>)>, Error>;
83    /// Subscribe to blocks received.
84    fn blocks(&self) -> chan::Receiver<(Block, Height)>;
85    /// Subscribe to compact filters received.
86    fn filters(&self) -> chan::Receiver<(BlockFilter, BlockHash, Height)>;
87    /// Subscribe to client events.
88    fn events(&self) -> chan::Receiver<Event>;
89    /// Send a command to the client.
90    fn command(&self, cmd: Command) -> Result<(), Error>;
91    /// Rescan the blockchain for matching scripts.
92    ///
93    /// If a "reorg" takes place, filters up to the start of the provided range
94    /// will be re-fetched and scanned.
95    fn rescan(
96        &self,
97        range: impl RangeBounds<Height>,
98        watch: impl Iterator<Item = Script>,
99    ) -> Result<(), Error> {
100        // TODO: Handle invalid/empty ranges.
101
102        let from = range.start_bound().cloned();
103        let to = range.end_bound().cloned();
104
105        self.command(Command::Rescan {
106            from,
107            to,
108            watch: watch.collect(),
109        })?;
110
111        Ok(())
112    }
113    /// Update the watchlist with the provided scripts.
114    ///
115    /// Note that this won't trigger a rescan of any existing blocks. To avoid
116    /// missing matching blocks, always watch scripts before sharing their
117    /// corresponding address.
118    fn watch(&self, watch: impl Iterator<Item = Script>) -> Result<(), Error> {
119        self.command(Command::Watch {
120            watch: watch.collect(),
121        })?;
122
123        Ok(())
124    }
125    /// Broadcast a message to peers matching the predicate.
126    /// To only broadcast to outbound peers, use [`Peer::is_outbound`].
127    fn broadcast(
128        &self,
129        msg: NetworkMessage,
130        predicate: fn(Peer) -> bool,
131    ) -> Result<Vec<net::SocketAddr>, Error>;
132    /// Send a message to a random *outbound* peer. Return the chosen
133    /// peer or nothing if no peer was available.
134    fn query(&self, msg: NetworkMessage) -> Result<Option<net::SocketAddr>, Error>;
135    /// Connect to the designated peer address.
136    fn connect(&self, addr: net::SocketAddr) -> Result<Link, Error>;
137    /// Disconnect from the designated peer address.
138    fn disconnect(&self, addr: net::SocketAddr) -> Result<(), Error>;
139    /// Submit a transaction to the network.
140    ///
141    /// Returns the peer(s) the transaction was announced to, or an error if no peers were found.
142    fn submit_transaction(&self, tx: Transaction) -> Result<NonEmpty<net::SocketAddr>, Error>;
143    /// Import block headers into the node.
144    /// This may cause the node to broadcast header or inventory messages to its peers.
145    fn import_headers(
146        &self,
147        headers: Vec<BlockHeader>,
148    ) -> Result<Result<ImportResult, block::tree::Error>, Error>;
149    /// Import peer addresses into the node's address book.
150    fn import_addresses(&self, addrs: Vec<Address>) -> Result<(), Error>;
151    /// Wait for the given predicate to be fulfilled.
152    fn wait<F: FnMut(fsm::Event) -> Option<T>, T>(&self, f: F) -> Result<T, Error>;
153    /// Wait for a given number of peers to be connected with the given services.
154    fn wait_for_peers(
155        &self,
156        count: usize,
157        required_services: impl Into<ServiceFlags>,
158    ) -> Result<Vec<(net::SocketAddr, Height, ServiceFlags)>, Error>;
159    /// Wait for the node's active chain to reach a certain height. The hash at that height
160    /// is returned.
161    fn wait_for_height(&self, h: Height) -> Result<BlockHash, Error>;
162    /// Shutdown the node process.
163    fn shutdown(self) -> Result<(), Error>;
164}