From e5cc8d95290a4ea92f060b4f9a613e0233e5f2c6 Mon Sep 17 00:00:00 2001 From: Alekos Filini Date: Thu, 10 Sep 2020 18:11:24 +0200 Subject: [PATCH] [blockchain] Add an `AnyBlockchain` enum to allow switching at runtime This is related to #43 --- src/blockchain/any.rs | 166 ++++++++++++++++++++++++++++++++++++++++++ src/blockchain/mod.rs | 5 ++ 2 files changed, 171 insertions(+) create mode 100644 src/blockchain/any.rs diff --git a/src/blockchain/any.rs b/src/blockchain/any.rs new file mode 100644 index 00000000..30b9cbfc --- /dev/null +++ b/src/blockchain/any.rs @@ -0,0 +1,166 @@ +// Magical Bitcoin Library +// Written in 2020 by +// Alekos Filini +// +// Copyright (c) 2020 Magical Bitcoin +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +//! Runtime-checked blockchain types +//! +//! This module provides the implementation of [`AnyBlockchain`] which allows switching the +//! inner [`Blockchain`] type at runtime. + +use super::*; + +macro_rules! impl_from { + ( $from:ty, $to:ty, $variant:ident, $( $cfg:tt )* ) => { + $( $cfg )* + impl From<$from> for $to { + fn from(inner: $from) -> Self { + <$to>::$variant(inner) + } + } + }; +} + +macro_rules! impl_inner_method { + ( $self:expr, $name:ident $(, $args:expr)* ) => { + match $self { + #[cfg(feature = "electrum")] + AnyBlockchain::Electrum(inner) => inner.$name( $($args, )* ), + #[cfg(feature = "esplora")] + AnyBlockchain::Esplora(inner) => inner.$name( $($args, )* ), + #[cfg(feature = "compact_filters")] + AnyBlockchain::CompactFilters(inner) => inner.$name( $($args, )* ), + } + } +} + +/// Type that can contain any of the [`Blockchain`] types defined by the library +/// +/// It allows switching backend at runtime +pub enum AnyBlockchain { + #[cfg(feature = "electrum")] + #[cfg_attr(docsrs, doc(cfg(feature = "electrum")))] + Electrum(electrum::ElectrumBlockchain), + #[cfg(feature = "esplora")] + #[cfg_attr(docsrs, doc(cfg(feature = "esplora")))] + Esplora(esplora::EsploraBlockchain), + #[cfg(feature = "compact_filters")] + #[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))] + CompactFilters(compact_filters::CompactFiltersBlockchain), +} + +#[maybe_async] +impl Blockchain for AnyBlockchain { + fn get_capabilities(&self) -> HashSet { + maybe_await!(impl_inner_method!(self, get_capabilities)) + } + + fn setup( + &self, + stop_gap: Option, + database: &mut D, + progress_update: P, + ) -> Result<(), Error> { + maybe_await!(impl_inner_method!( + self, + setup, + stop_gap, + database, + progress_update + )) + } + fn sync( + &self, + stop_gap: Option, + database: &mut D, + progress_update: P, + ) -> Result<(), Error> { + maybe_await!(impl_inner_method!( + self, + sync, + stop_gap, + database, + progress_update + )) + } + + fn get_tx(&self, txid: &Txid) -> Result, Error> { + maybe_await!(impl_inner_method!(self, get_tx, txid)) + } + fn broadcast(&self, tx: &Transaction) -> Result<(), Error> { + maybe_await!(impl_inner_method!(self, broadcast, tx)) + } + + fn get_height(&self) -> Result { + maybe_await!(impl_inner_method!(self, get_height)) + } + fn estimate_fee(&self, target: usize) -> Result { + maybe_await!(impl_inner_method!(self, estimate_fee, target)) + } +} + +impl_from!(electrum::ElectrumBlockchain, AnyBlockchain, Electrum, #[cfg(feature = "electrum")]); +impl_from!(esplora::EsploraBlockchain, AnyBlockchain, Esplora, #[cfg(feature = "esplora")]); +impl_from!(compact_filters::CompactFiltersBlockchain, AnyBlockchain, CompactFilters, #[cfg(feature = "compact_filters")]); + +/// Type that can contain any of the blockchain configurations defined by the library +/// +/// This allows storing a single configuration that can be loaded into an [`AnyBlockchain`] +/// instance. Wallets that plan to offer users the ability to switch blockchain backend at runtime +/// will find this particularly useful. +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub enum AnyBlockchainConfig { + #[cfg(feature = "electrum")] + #[cfg_attr(docsrs, doc(cfg(feature = "electrum")))] + Electrum(electrum::ElectrumBlockchainConfig), + #[cfg(feature = "esplora")] + #[cfg_attr(docsrs, doc(cfg(feature = "esplora")))] + Esplora(esplora::EsploraBlockchainConfig), + #[cfg(feature = "compact_filters")] + #[cfg_attr(docsrs, doc(cfg(feature = "compact_filters")))] + CompactFilters(compact_filters::CompactFiltersBlockchainConfig), +} + +impl ConfigurableBlockchain for AnyBlockchain { + type Config = AnyBlockchainConfig; + + fn from_config(config: &Self::Config) -> Result { + Ok(match config { + #[cfg(feature = "electrum")] + AnyBlockchainConfig::Electrum(inner) => { + AnyBlockchain::Electrum(electrum::ElectrumBlockchain::from_config(inner)?) + } + #[cfg(feature = "esplora")] + AnyBlockchainConfig::Esplora(inner) => { + AnyBlockchain::Esplora(esplora::EsploraBlockchain::from_config(inner)?) + } + #[cfg(feature = "compact_filters")] + AnyBlockchainConfig::CompactFilters(inner) => AnyBlockchain::CompactFilters( + compact_filters::CompactFiltersBlockchain::from_config(inner)?, + ), + }) + } +} + +impl_from!(electrum::ElectrumBlockchainConfig, AnyBlockchainConfig, Electrum, #[cfg(feature = "electrum")]); +impl_from!(esplora::EsploraBlockchainConfig, AnyBlockchainConfig, Esplora, #[cfg(feature = "esplora")]); +impl_from!(compact_filters::CompactFiltersBlockchainConfig, AnyBlockchainConfig, CompactFilters, #[cfg(feature = "compact_filters")]); diff --git a/src/blockchain/mod.rs b/src/blockchain/mod.rs index 7d40a4c6..2577864e 100644 --- a/src/blockchain/mod.rs +++ b/src/blockchain/mod.rs @@ -42,6 +42,11 @@ use crate::FeeRate; pub(crate) mod utils; +#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))] +pub mod any; +#[cfg(any(feature = "electrum", feature = "esplora", feature = "compact_filters"))] +pub use any::{AnyBlockchain, AnyBlockchainConfig}; + #[cfg(feature = "electrum")] #[cfg_attr(docsrs, doc(cfg(feature = "electrum")))] pub mod electrum;