]> Untitled Git - bdk/commitdiff
[blockchain] Simplify the architecture of blockchain traits
authorAlekos Filini <alekos.filini@gmail.com>
Wed, 9 Sep 2020 16:17:49 +0000 (18:17 +0200)
committerAlekos Filini <alekos.filini@gmail.com>
Thu, 10 Sep 2020 08:45:07 +0000 (10:45 +0200)
Instead of having two traits, `Blockchain` and `OnlineBlockchain` that need
to be implemented by the user, only the relevant one (`OnlineBlockchain`, here
renamed to `Blockchain`) will need to be implemented, since we provide a
blanket implementation for the "marker" trait (previously `Blockchain`, here
renamed to `BlockchainMarker`).

Users of the library will probably never need to implement `BlockchainMarker`
by itself, since we expose the `OfflineBlockchain` type that already does
that and should be good for any "offline" wallet. Still, it's exposed since
they might need to import it to define types with generics.

src/blockchain/compact_filters/mod.rs
src/blockchain/compact_filters/peer.rs
src/blockchain/electrum.rs
src/blockchain/esplora.rs
src/blockchain/mod.rs
src/cli.rs
src/wallet/export.rs
src/wallet/mod.rs
testutils-macros/src/lib.rs

index e528db626b1493f4d4c26af50684fda7f9095a09..f27137b1a85d05873a716358df5c80446dbf0d9e 100644 (file)
@@ -24,7 +24,7 @@
 
 //! Compact Filters
 //!
-//! This module contains a multithreaded implementation of an [`OnlineBlockchain`] backend that
+//! This module contains a multithreaded implementation of an [`Blockchain`] backend that
 //! uses BIP157 (aka "Neutrino") to populate the wallet's [database](crate::database::Database)
 //! by downloading compact filters from the P2P network.
 //!
@@ -76,7 +76,7 @@ mod peer;
 mod store;
 mod sync;
 
-use super::{Blockchain, Capability, OnlineBlockchain, Progress};
+use super::{Blockchain, Capability, Progress};
 use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
 use crate::error::Error;
 use crate::types::{ScriptType, TransactionDetails, UTXO};
@@ -97,7 +97,11 @@ const PROCESS_BLOCKS_COST: f32 = 20_000.0;
 /// ## Example
 /// See the [`blockchain::compact_filters`](crate::blockchain::compact_filters) module for a usage example.
 #[derive(Debug)]
-pub struct CompactFiltersBlockchain(Option<CompactFilters>);
+pub struct CompactFiltersBlockchain {
+    peers: Vec<Arc<Peer>>,
+    headers: Arc<ChainStore<Full>>,
+    skip_blocks: Option<usize>,
+}
 
 impl CompactFiltersBlockchain {
     /// Construct a new instance given a list of peers, a path to store headers and block
@@ -108,29 +112,6 @@ impl CompactFiltersBlockchain {
     /// in parallel. It's currently recommended to only connect to a single peer to avoid
     /// inconsistencies in the data returned, optionally with multiple connections in parallel to
     /// speed-up the sync process.
-    pub fn new<P: AsRef<Path>>(
-        peers: Vec<Peer>,
-        storage_dir: P,
-        skip_blocks: Option<usize>,
-    ) -> Result<Self, CompactFiltersError> {
-        Ok(CompactFiltersBlockchain(Some(CompactFilters::new(
-            peers,
-            storage_dir,
-            skip_blocks,
-        )?)))
-    }
-}
-
-/// Internal struct that contains the state of a [`CompactFiltersBlockchain`]
-#[derive(Debug)]
-struct CompactFilters {
-    peers: Vec<Arc<Peer>>,
-    headers: Arc<ChainStore<Full>>,
-    skip_blocks: Option<usize>,
-}
-
-impl CompactFilters {
-    /// Constructor, see [`CompactFiltersBlockchain::new`] for the documentation
     pub fn new<P: AsRef<Path>>(
         peers: Vec<Peer>,
         storage_dir: P,
@@ -160,7 +141,7 @@ impl CompactFilters {
             headers.recover_snapshot(cf_name)?;
         }
 
-        Ok(CompactFilters {
+        Ok(CompactFiltersBlockchain {
             peers: peers.into_iter().map(Arc::new).collect(),
             headers,
             skip_blocks,
@@ -250,16 +231,6 @@ impl CompactFilters {
 }
 
 impl Blockchain for CompactFiltersBlockchain {
-    fn offline() -> Self {
-        CompactFiltersBlockchain(None)
-    }
-
-    fn is_online(&self) -> bool {
-        self.0.is_some()
-    }
-}
-
-impl OnlineBlockchain for CompactFiltersBlockchain {
     fn get_capabilities(&self) -> HashSet<Capability> {
         vec![Capability::FullHistory].into_iter().collect()
     }
@@ -270,14 +241,13 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
         database: &mut D,
         progress_update: P,
     ) -> Result<(), Error> {
-        let inner = self.0.as_ref().ok_or(Error::OfflineClient)?;
-        let first_peer = &inner.peers[0];
+        let first_peer = &self.peers[0];
 
-        let skip_blocks = inner.skip_blocks.unwrap_or(0);
+        let skip_blocks = self.skip_blocks.unwrap_or(0);
 
-        let cf_sync = Arc::new(CFSync::new(Arc::clone(&inner.headers), skip_blocks, 0x00)?);
+        let cf_sync = Arc::new(CFSync::new(Arc::clone(&self.headers), skip_blocks, 0x00)?);
 
-        let initial_height = inner.headers.get_height()?;
+        let initial_height = self.headers.get_height()?;
         let total_bundles = (first_peer.get_version().start_height as usize)
             .checked_sub(skip_blocks)
             .map(|x| x / 1000)
@@ -297,7 +267,7 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
 
         if let Some(snapshot) = sync::sync_headers(
             Arc::clone(&first_peer),
-            Arc::clone(&inner.headers),
+            Arc::clone(&self.headers),
             |new_height| {
                 let local_headers_cost =
                     new_height.checked_sub(initial_height).unwrap_or(0) as f32 * SYNC_HEADERS_COST;
@@ -307,13 +277,13 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
                 )
             },
         )? {
-            if snapshot.work()? > inner.headers.work()? {
+            if snapshot.work()? > self.headers.work()? {
                 info!("Applying snapshot with work: {}", snapshot.work()?);
-                inner.headers.apply_snapshot(snapshot)?;
+                self.headers.apply_snapshot(snapshot)?;
             }
         }
 
-        let synced_height = inner.headers.get_height()?;
+        let synced_height = self.headers.get_height()?;
         let buried_height = synced_height
             .checked_sub(sync::BURIED_CONFIRMATIONS)
             .unwrap_or(0);
@@ -333,11 +303,11 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
         let synced_bundles = Arc::new(AtomicUsize::new(0));
         let progress_update = Arc::new(Mutex::new(progress_update));
 
-        let mut threads = Vec::with_capacity(inner.peers.len());
-        for peer in &inner.peers {
+        let mut threads = Vec::with_capacity(self.peers.len());
+        for peer in &self.peers {
             let cf_sync = Arc::clone(&cf_sync);
             let peer = Arc::clone(&peer);
-            let headers = Arc::clone(&inner.headers);
+            let headers = Arc::clone(&self.headers);
             let all_scripts = Arc::clone(&all_scripts);
             let last_synced_block = Arc::clone(&last_synced_block);
             let progress_update = Arc::clone(&progress_update);
@@ -420,9 +390,9 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
         let mut internal_max_deriv = None;
         let mut external_max_deriv = None;
 
-        for (height, block) in inner.headers.iter_full_blocks()? {
+        for (height, block) in self.headers.iter_full_blocks()? {
             for tx in &block.txdata {
-                inner.process_tx(
+                self.process_tx(
                     database,
                     tx,
                     Some(height as u32),
@@ -433,7 +403,7 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
             }
         }
         for tx in first_peer.get_mempool().iter_txs().iter() {
-            inner.process_tx(
+            self.process_tx(
                 database,
                 tx,
                 None,
@@ -458,7 +428,7 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
         }
 
         info!("Dropping blocks until {}", buried_height);
-        inner.headers.delete_blocks_until(buried_height)?;
+        self.headers.delete_blocks_until(buried_height)?;
 
         progress_update
             .lock()
@@ -469,24 +439,19 @@ impl OnlineBlockchain for CompactFiltersBlockchain {
     }
 
     fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        let inner = self.0.as_ref().ok_or(Error::OfflineClient)?;
-
-        Ok(inner.peers[0]
+        Ok(self.peers[0]
             .get_mempool()
             .get_tx(&Inventory::Transaction(*txid)))
     }
 
     fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
-        let inner = self.0.as_ref().ok_or(Error::OfflineClient)?;
-        inner.peers[0].broadcast_tx(tx.clone())?;
+        self.peers[0].broadcast_tx(tx.clone())?;
 
         Ok(())
     }
 
     fn get_height(&self) -> Result<u32, Error> {
-        let inner = self.0.as_ref().ok_or(Error::OfflineClient)?;
-
-        Ok(inner.headers.get_height()? as u32)
+        Ok(self.headers.get_height()? as u32)
     }
 
     fn estimate_fee(&self, _target: usize) -> Result<FeeRate, Error> {
index eb2cb2207c8b68749aebb60df8b29b7d654b8400..cb111a563b3b1478cc903685fd2977452b9f9836 100644 (file)
@@ -63,7 +63,7 @@ impl Mempool {
     /// Add a transaction to the mempool
     ///
     /// Note that this doesn't propagate the transaction to other
-    /// peers. To do that, [`broadcast`](crate::blockchain::OnlineBlockchain::broadcast) should be used.
+    /// peers. To do that, [`broadcast`](crate::blockchain::Blockchain::broadcast) should be used.
     pub fn add_tx(&self, tx: Transaction) {
         self.txs.write().unwrap().insert(tx.txid(), tx);
     }
index 0f8774678f7c8e3df375406bc0c6e12efa1cf350..7d62e5be6fce7aacab9ff57b4ea0c7db98097d7d 100644 (file)
@@ -24,7 +24,7 @@
 
 //! Electrum
 //!
-//! This module defines an [`OnlineBlockchain`] struct that wraps an [`electrum_client::Client`]
+//! This module defines a [`Blockchain`] struct that wraps an [`electrum_client::Client`]
 //! and implements the logic required to populate the wallet's [database](crate::database::Database) by
 //! querying the inner client.
 //!
@@ -56,7 +56,7 @@ use crate::FeeRate;
 ///
 /// ## Example
 /// See the [`blockchain::electrum`](crate::blockchain::electrum) module for a usage example.
-pub struct ElectrumBlockchain(Option<Client>);
+pub struct ElectrumBlockchain(Client);
 
 #[cfg(test)]
 #[cfg(feature = "test-electrum")]
@@ -67,21 +67,11 @@ fn local_electrs() -> ElectrumBlockchain {
 
 impl std::convert::From<Client> for ElectrumBlockchain {
     fn from(client: Client) -> Self {
-        ElectrumBlockchain(Some(client))
+        ElectrumBlockchain(client)
     }
 }
 
 impl Blockchain for ElectrumBlockchain {
-    fn offline() -> Self {
-        ElectrumBlockchain(None)
-    }
-
-    fn is_online(&self) -> bool {
-        self.0.is_some()
-    }
-}
-
-impl OnlineBlockchain for ElectrumBlockchain {
     fn get_capabilities(&self) -> HashSet<Capability> {
         vec![
             Capability::FullHistory,
@@ -99,27 +89,15 @@ impl OnlineBlockchain for ElectrumBlockchain {
         progress_update: P,
     ) -> Result<(), Error> {
         self.0
-            .as_ref()
-            .ok_or(Error::OfflineClient)?
             .electrum_like_setup(stop_gap, database, progress_update)
     }
 
     fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        Ok(self
-            .0
-            .as_ref()
-            .ok_or(Error::OfflineClient)?
-            .transaction_get(txid)
-            .map(Option::Some)?)
+        Ok(self.0.transaction_get(txid).map(Option::Some)?)
     }
 
     fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
-        Ok(self
-            .0
-            .as_ref()
-            .ok_or(Error::OfflineClient)?
-            .transaction_broadcast(tx)
-            .map(|_| ())?)
+        Ok(self.0.transaction_broadcast(tx).map(|_| ())?)
     }
 
     fn get_height(&self) -> Result<u32, Error> {
@@ -127,18 +105,13 @@ impl OnlineBlockchain for ElectrumBlockchain {
 
         Ok(self
             .0
-            .as_ref()
-            .ok_or(Error::OfflineClient)?
             .block_headers_subscribe()
             .map(|data| data.height as u32)?)
     }
 
     fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
         Ok(FeeRate::from_btc_per_kvb(
-            self.0
-                .as_ref()
-                .ok_or(Error::OfflineClient)?
-                .estimate_fee(target)? as f32,
+            self.0.estimate_fee(target)? as f32
         ))
     }
 }
index 7eaf4b57a9a70060bd17e1ec5cafd2f5b0868436..214852efdd5219d8f457a454100694208ab1cf6e 100644 (file)
@@ -24,7 +24,7 @@
 
 //! Esplora
 //!
-//! This module defines an [`OnlineBlockchain`] struct that can query an Esplora backend
+//! This module defines a [`Blockchain`] struct that can query an Esplora backend
 //! populate the wallet's [database](crate::database::Database) by
 //!
 //! ## Example
@@ -71,36 +71,26 @@ struct UrlClient {
 /// ## Example
 /// See the [`blockchain::esplora`](crate::blockchain::esplora) module for a usage example.
 #[derive(Debug)]
-pub struct EsploraBlockchain(Option<UrlClient>);
+pub struct EsploraBlockchain(UrlClient);
 
 impl std::convert::From<UrlClient> for EsploraBlockchain {
     fn from(url_client: UrlClient) -> Self {
-        EsploraBlockchain(Some(url_client))
+        EsploraBlockchain(url_client)
     }
 }
 
 impl EsploraBlockchain {
     /// Create a new instance of the client from a base URL
     pub fn new(base_url: &str) -> Self {
-        EsploraBlockchain(Some(UrlClient {
+        EsploraBlockchain(UrlClient {
             url: base_url.to_string(),
             client: Client::new(),
-        }))
-    }
-}
-
-impl Blockchain for EsploraBlockchain {
-    fn offline() -> Self {
-        EsploraBlockchain(None)
-    }
-
-    fn is_online(&self) -> bool {
-        self.0.is_some()
+        })
     }
 }
 
 #[maybe_async]
-impl OnlineBlockchain for EsploraBlockchain {
+impl Blockchain for EsploraBlockchain {
     fn get_capabilities(&self) -> HashSet<Capability> {
         vec![
             Capability::FullHistory,
@@ -119,41 +109,23 @@ impl OnlineBlockchain for EsploraBlockchain {
     ) -> Result<(), Error> {
         maybe_await!(self
             .0
-            .as_ref()
-            .ok_or(Error::OfflineClient)?
             .electrum_like_setup(stop_gap, database, progress_update))
     }
 
     fn get_tx(&self, txid: &Txid) -> Result<Option<Transaction>, Error> {
-        Ok(await_or_block!(self
-            .0
-            .as_ref()
-            .ok_or(Error::OfflineClient)?
-            ._get_tx(txid))?)
+        Ok(await_or_block!(self.0._get_tx(txid))?)
     }
 
     fn broadcast(&self, tx: &Transaction) -> Result<(), Error> {
-        Ok(await_or_block!(self
-            .0
-            .as_ref()
-            .ok_or(Error::OfflineClient)?
-            ._broadcast(tx))?)
+        Ok(await_or_block!(self.0._broadcast(tx))?)
     }
 
     fn get_height(&self) -> Result<u32, Error> {
-        Ok(await_or_block!(self
-            .0
-            .as_ref()
-            .ok_or(Error::OfflineClient)?
-            ._get_height())?)
+        Ok(await_or_block!(self.0._get_height())?)
     }
 
     fn estimate_fee(&self, target: usize) -> Result<FeeRate, Error> {
-        let estimates = await_or_block!(self
-            .0
-            .as_ref()
-            .ok_or(Error::OfflineClient)?
-            ._get_fee_estimates())?;
+        let estimates = await_or_block!(self.0._get_fee_estimates())?;
 
         let fee_val = estimates
             .into_iter()
index dd78bfa633672a4fc4d1030c8f311e869339ae08..8fcee951ef38896679358cdc39bb1d29ac2da6a1 100644 (file)
 //!
 //! This module provides the implementation of a few commonly-used backends like
 //! [Electrum](crate::blockchain::electrum), [Esplora](crate::blockchain::esplora) and
-//! [Compact Filters/Neutrino](crate::blockchain::compact_filters), along with two generalized
-//! traits [`Blockchain`] and [`OnlineBlockchain`] that can be implemented to build customized
-//! backends.
-//!
-//! Types that only implement the [`Blockchain`] trait can be used as backends for [`Wallet`](crate::wallet::Wallet)s, but any
-//! action that requires interacting with the blockchain won't be available ([`Wallet::sync`](crate::wallet::Wallet::sync) and
-//! [`Wallet::broadcast`](crate::wallet::Wallet::broadcast)). This allows the creation of physically air-gapped wallets, that have no
-//! ability to contact the outside world. An example of an offline-only client is [`OfflineBlockchain`].
-//!
-//! Types that also implement [`OnlineBlockchain`] will make the two aforementioned actions
-//! available.
+//! [Compact Filters/Neutrino](crate::blockchain::compact_filters), along with a generalized trait
+//! [`Blockchain`] that can be implemented to build customized backends.
 
 use std::collections::HashSet;
 use std::ops::Deref;
@@ -69,7 +60,7 @@ pub mod compact_filters;
 #[cfg(feature = "compact_filters")]
 pub use self::compact_filters::CompactFiltersBlockchain;
 
-/// Capabilities that can be supported by an [`OnlineBlockchain`] backend
+/// Capabilities that can be supported by a [`Blockchain`] backend
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
 pub enum Capability {
     /// Can recover the full history of a wallet and not only the set of currently spendable UTXOs
@@ -80,56 +71,42 @@ pub enum Capability {
     AccurateFees,
 }
 
-/// Base trait for a blockchain backend
+/// Marker trait for a blockchain backend
 ///
-/// This trait is always required, even for "air-gapped" backends that don't actually make any
-/// external call. Clients that have the ability to make external calls must also implement `OnlineBlockchain`.
-pub trait Blockchain {
-    /// Return whether or not the client has the ability to fullfill requests
-    ///
-    /// This should always be `false` for offline-only types, and can be true for types that also
-    /// implement [`OnlineBlockchain`], if they have the ability to fullfill requests.
-    fn is_online(&self) -> bool;
+/// This is a marker trait for blockchain types. It is automatically implemented for types that
+/// implement [`Blockchain`], so as a user of the library you won't have to implement this
+/// manually.
+///
+/// Users of the library will probably never have to implement this trait manually, but they
+/// could still need to import it to define types and structs with generics;
+/// Implementing only the marker trait is pointless, since [`OfflineBlockchain`]
+/// already does that, and whenever [`Blockchain`] is implemented, the marker trait is also
+/// automatically implemented by the library.
+pub trait BlockchainMarker {}
 
-    /// Create a new instance of the client that is offline-only
-    ///
-    /// For types that also implement [`OnlineBlockchain`], this means creating an instance that
-    /// returns [`Error::OfflineClient`](crate::error::Error::OfflineClient) if any of the "online"
-    /// methods are called.
-    ///
-    /// This is generally implemented by wrapping the client in an [`Option`] that has [`Option::None`] value
-    /// when created with this method, and is [`Option::Some`] if properly instantiated.
-    fn offline() -> Self;
-}
+/// The [`BlockchainMarker`] marker trait is automatically implemented for [`Blockchain`] types
+impl<T: Blockchain> BlockchainMarker for T {}
 
-/// Type that only implements [`Blockchain`] and is always offline
+/// Type that only implements [`Blockchain`] and is always "offline"
 pub struct OfflineBlockchain;
-impl Blockchain for OfflineBlockchain {
-    fn offline() -> Self {
-        OfflineBlockchain
-    }
-
-    fn is_online(&self) -> bool {
-        false
-    }
-}
+impl BlockchainMarker for OfflineBlockchain {}
 
-/// Trait that defines the actions that must be supported by an online [`Blockchain`]
+/// Trait that defines the actions that must be supported by a blockchain backend
 #[maybe_async]
-pub trait OnlineBlockchain: Blockchain {
+pub trait Blockchain: BlockchainMarker {
     /// Return the set of [`Capability`] supported by this backend
     fn get_capabilities(&self) -> HashSet<Capability>;
 
     /// Setup the backend and populate the internal database for the first time
     ///
-    /// This method is the equivalent of [`OnlineBlockchain::sync`], but it's guaranteed to only be
+    /// This method is the equivalent of [`Blockchain::sync`], but it's guaranteed to only be
     /// called once, at the first [`Wallet::sync`](crate::wallet::Wallet::sync).
     ///
     /// The rationale behind the distinction between `sync` and `setup` is that some custom backends
     /// might need to perform specific actions only the first time they are synced.
     ///
     /// For types that do not have that distinction, only this method can be implemented, since
-    /// [`OnlineBlockchain::sync`] defaults to calling this internally if not overridden.
+    /// [`Blockchain::sync`] defaults to calling this internally if not overridden.
     fn setup<D: BatchDatabase, P: 'static + Progress>(
         &self,
         stop_gap: Option<usize>,
@@ -138,7 +115,7 @@ pub trait OnlineBlockchain: Blockchain {
     ) -> Result<(), Error>;
     /// Populate the internal database with transactions and UTXOs
     ///
-    /// If not overridden, it defaults to calling [`OnlineBlockchain::setup`] internally.
+    /// If not overridden, it defaults to calling [`Blockchain::setup`] internally.
     ///
     /// This method should implement the logic required to iterate over the list of the wallet's
     /// script_pubkeys using [`Database::iter_script_pubkeys`] and look for relevant transactions
@@ -178,8 +155,8 @@ pub trait OnlineBlockchain: Blockchain {
 /// Data sent with a progress update over a [`channel`]
 pub type ProgressData = (f32, Option<String>);
 
-/// Trait for types that can receive and process progress updates during [`OnlineBlockchain::sync`] and
-/// [`OnlineBlockchain::setup`]
+/// Trait for types that can receive and process progress updates during [`Blockchain::sync`] and
+/// [`Blockchain::setup`]
 pub trait Progress: Send {
     /// Send a new progress update
     ///
@@ -236,18 +213,8 @@ impl Progress for LogProgress {
     }
 }
 
-impl<T: Blockchain> Blockchain for Arc<T> {
-    fn is_online(&self) -> bool {
-        self.deref().is_online()
-    }
-
-    fn offline() -> Self {
-        Arc::new(T::offline())
-    }
-}
-
 #[maybe_async]
-impl<T: OnlineBlockchain> OnlineBlockchain for Arc<T> {
+impl<T: Blockchain> Blockchain for Arc<T> {
     fn get_capabilities(&self) -> HashSet<Capability> {
         maybe_await!(self.deref().get_capabilities())
     }
index e09fedd4efb8d109f780093aeff5461863e3b106..bf50d961263c01f260ee2d7dd8b3d791af45d180 100644 (file)
@@ -363,7 +363,7 @@ pub fn handle_matches<C, D>(
     matches: ArgMatches<'_>,
 ) -> Result<serde_json::Value, Error>
 where
-    C: crate::blockchain::OnlineBlockchain,
+    C: crate::blockchain::Blockchain,
     D: crate::database::BatchDatabase,
 {
     if let Some(_sub_matches) = matches.subcommand_matches("get_new_address") {
index 2df89bbefdd2e854cb2a6dcbe6e6faa0c031c751..42d94f306e880fb19521e2a7d1b7940a05a18b33 100644 (file)
@@ -73,7 +73,7 @@ use serde::{Deserialize, Serialize};
 
 use miniscript::{Descriptor, ScriptContext, Terminal};
 
-use crate::blockchain::Blockchain;
+use crate::blockchain::BlockchainMarker;
 use crate::database::BatchDatabase;
 use crate::wallet::Wallet;
 
@@ -115,7 +115,7 @@ impl WalletExport {
     ///
     /// If the database is empty or `include_blockheight` is false, the `blockheight` field
     /// returned will be `0`.
-    pub fn export_wallet<B: Blockchain, D: BatchDatabase>(
+    pub fn export_wallet<B: BlockchainMarker, D: BatchDatabase>(
         wallet: &Wallet<B, D>,
         label: &str,
         include_blockheight: bool,
index 48f5599fbd91717c58e5b2e460ba36b2a979d2b5..d0a7ed621f113687e4fc39afa0cf397e8848ecea 100644 (file)
@@ -58,7 +58,7 @@ use signer::{Signer, SignerId, SignerOrdering, SignersContainer};
 use tx_builder::TxBuilder;
 use utils::{After, Older};
 
-use crate::blockchain::{Blockchain, OfflineBlockchain, OnlineBlockchain, Progress};
+use crate::blockchain::{Blockchain, BlockchainMarker, OfflineBlockchain, Progress};
 use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
 use crate::descriptor::{
     get_checksum, DescriptorMeta, DescriptorScripts, ExtendedDescriptor, ExtractPolicy, Policy,
@@ -80,9 +80,9 @@ pub type OfflineWallet<D> = Wallet<OfflineBlockchain, D>;
 /// [creating transactions](Wallet::create_tx), etc.
 ///
 /// A wallet can be either "online" if the [`blockchain`](crate::blockchain) type provided
-/// implements [`OnlineBlockchain`], or "offline" if it doesn't. Offline wallets only expose
+/// implements [`Blockchain`], or "offline" [`OfflineBlockchain`] is used. Offline wallets only expose
 /// methods that don't need any interaction with the blockchain to work.
-pub struct Wallet<B: Blockchain, D: BatchDatabase> {
+pub struct Wallet<B: BlockchainMarker, D: BatchDatabase> {
     descriptor: ExtendedDescriptor,
     change_descriptor: Option<ExtendedDescriptor>,
 
@@ -95,14 +95,14 @@ pub struct Wallet<B: Blockchain, D: BatchDatabase> {
 
     current_height: Option<u32>,
 
-    client: B,
+    client: Option<B>,
     database: RefCell<D>,
 }
 
 // offline actions, always available
 impl<B, D> Wallet<B, D>
 where
-    B: Blockchain,
+    B: BlockchainMarker,
     D: BatchDatabase,
 {
     /// Create a new "offline" wallet
@@ -147,7 +147,7 @@ where
 
             current_height: None,
 
-            client: B::offline(),
+            client: None,
             database: RefCell::new(database),
         })
     }
@@ -1076,7 +1076,7 @@ where
 
 impl<B, D> Wallet<B, D>
 where
-    B: OnlineBlockchain,
+    B: Blockchain,
     D: BatchDatabase,
 {
     /// Create a new "online" wallet
@@ -1091,7 +1091,7 @@ where
         let mut wallet = Self::new_offline(descriptor, change_descriptor, network, database)?;
 
         wallet.current_height = Some(maybe_await!(client.get_height())? as u32);
-        wallet.client = client;
+        wallet.client = Some(client);
 
         Ok(wallet)
     }
@@ -1144,13 +1144,13 @@ where
         // TODO: what if i generate an address first and cache some addresses?
         // TODO: we should sync if generating an address triggers a new batch to be stored
         if run_setup {
-            maybe_await!(self.client.setup(
+            maybe_await!(self.client.as_ref().ok_or(Error::OfflineClient)?.setup(
                 None,
                 self.database.borrow_mut().deref_mut(),
                 progress_update,
             ))
         } else {
-            maybe_await!(self.client.sync(
+            maybe_await!(self.client.as_ref().ok_or(Error::OfflineClient)?.sync(
                 None,
                 self.database.borrow_mut().deref_mut(),
                 progress_update,
@@ -1159,14 +1159,18 @@ where
     }
 
     /// Return a reference to the internal blockchain client
-    pub fn client(&self) -> &B {
-        &self.client
+    pub fn client(&self) -> Option<&B> {
+        self.client.as_ref()
     }
 
     /// Broadcast a transaction to the network
     #[maybe_async]
     pub fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
-        maybe_await!(self.client.broadcast(&tx))?;
+        maybe_await!(self
+            .client
+            .as_ref()
+            .ok_or(Error::OfflineClient)?
+            .broadcast(&tx))?;
 
         Ok(tx.txid())
     }
index cf8fe42406bf46acdcab20c2d1880ceed6389ea7..25622d1390aa6d53b5a06260cc5af81ebd6f3372 100644 (file)
@@ -63,7 +63,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
                 ReturnType::Type(_, ref t) => t.clone(),
                 ReturnType::Default => {
                     return (quote! {
-                        compile_error!("The tagged function must return a type that impl `OnlineBlockchain`")
+                        compile_error!("The tagged function must return a type that impl `Blockchain`")
                     }).into();
                 }
             };
@@ -79,7 +79,7 @@ pub fn magical_blockchain_tests(attr: TokenStream, item: TokenStream) -> TokenSt
 
                 use testutils::{TestClient, serial};
 
-                use #root_ident::blockchain::{OnlineBlockchain, noop_progress};
+                use #root_ident::blockchain::{Blockchain, noop_progress};
                 use #root_ident::descriptor::ExtendedDescriptor;
                 use #root_ident::database::MemoryDatabase;
                 use #root_ident::types::ScriptType;