[blockchain] Simplify the architecture of blockchain traits

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.
This commit is contained in:
Alekos Filini 2020-09-09 18:17:49 +02:00
parent 24fcb38565
commit efdd11762c
No known key found for this signature in database
GPG Key ID: 5E8AFC3034FDFA4F
9 changed files with 91 additions and 210 deletions

View 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> {

View 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);
}

View 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
))
}
}

View 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()

View File

@ -26,17 +26,8 @@
//!
//! 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
}
impl BlockchainMarker for OfflineBlockchain {}
fn is_online(&self) -> bool {
false
}
}
/// 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())
}

View 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") {

View 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,

View 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())
}

View 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;