Skip to content

Commit

Permalink
feat(chain)!: use custom return types for ElectrumExt methods
Browse files Browse the repository at this point in the history
This is more code, but a much more elegant solution than having
`ElectrumExt` methods return `SyncResult`/`FullScanResult` and having an
`ElectrumResultExt` extention trait.
  • Loading branch information
evanlinjin committed May 10, 2024
1 parent 2945c6b commit c0d7d60
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 52 deletions.
93 changes: 49 additions & 44 deletions crates/electrum/src/electrum_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ pub trait ElectrumExt {
request: FullScanRequest<K>,
stop_gap: usize,
batch_size: usize,
) -> Result<FullScanResult<K, ConfirmationHeightAnchor>, Error>;
) -> Result<ElectrumFullScanResult<K>, Error>;

/// Sync a set of scripts with the blockchain (via an Electrum client) for the data specified
/// and returns updates for [`bdk_chain`] data structures.
Expand All @@ -44,11 +44,7 @@ pub trait ElectrumExt {
/// may include scripts that have been used, use [`full_scan`] with the keychain.
///
/// [`full_scan`]: ElectrumExt::full_scan
fn sync(
&self,
request: SyncRequest,
batch_size: usize,
) -> Result<SyncResult<ConfirmationHeightAnchor>, Error>;
fn sync(&self, request: SyncRequest, batch_size: usize) -> Result<ElectrumSyncResult, Error>;
}

impl<E: ElectrumApi> ElectrumExt for E {
Expand All @@ -57,7 +53,7 @@ impl<E: ElectrumApi> ElectrumExt for E {
mut request: FullScanRequest<K>,
stop_gap: usize,
batch_size: usize,
) -> Result<FullScanResult<K, ConfirmationHeightAnchor>, Error> {
) -> Result<ElectrumFullScanResult<K>, Error> {
let mut request_spks = request.spks_by_keychain;

// We keep track of already-scanned spks just in case a reorg happens and we need to do a
Expand Down Expand Up @@ -134,20 +130,18 @@ impl<E: ElectrumApi> ElectrumExt for E {
};
};

Ok(update)
Ok(ElectrumFullScanResult(update))
}

fn sync(
&self,
request: SyncRequest,
batch_size: usize,
) -> Result<SyncResult<ConfirmationHeightAnchor>, Error> {
fn sync(&self, request: SyncRequest, batch_size: usize) -> Result<ElectrumSyncResult, Error> {
let mut tx_cache = request.tx_cache.clone();

let full_scan_req = FullScanRequest::from_chain_tip(request.chain_tip.clone())
.cache_txs(request.tx_cache)
.set_spks_for_keychain((), request.spks.enumerate().map(|(i, spk)| (i as u32, spk)));
let mut full_scan_res = self.full_scan(full_scan_req, usize::MAX, batch_size)?;
let mut full_scan_res = self
.full_scan(full_scan_req, usize::MAX, batch_size)?
.with_confirmation_height_anchor();

let (tip, _) = construct_update_tip(self, request.chain_tip)?;
let cps = tip
Expand All @@ -171,53 +165,64 @@ impl<E: ElectrumApi> ElectrumExt for E {
request.outpoints,
)?;

Ok(SyncResult {
Ok(ElectrumSyncResult(SyncResult {
chain_update: full_scan_res.chain_update,
graph_update: full_scan_res.graph_update,
})
}))
}
}

/// Trait that extends [`SyncResult`] and [`FullScanResult`] functionality.
/// The result of [`ElectrumExt::full_scan`].
///
/// Currently, only a single method exists that converts the update [`TxGraph`] to have an anchor
/// type of [`ConfirmationTimeHeightAnchor`].
pub trait ElectrumResultExt {
/// New result type with a [`TxGraph`] that contains the [`ConfirmationTimeHeightAnchor`].
type NewResult;

/// Convert result type to have an update [`TxGraph`] that contains the [`ConfirmationTimeHeightAnchor`] .
fn try_into_confirmation_time_result(
self,
client: &impl ElectrumApi,
) -> Result<Self::NewResult, Error>;
}

impl<K> ElectrumResultExt for FullScanResult<K, ConfirmationHeightAnchor> {
type NewResult = FullScanResult<K, ConfirmationTimeHeightAnchor>;
/// This can be transformed into a [`FullScanResult`] with either [`ConfirmationHeightAnchor`] or
/// [`ConfirmationTimeHeightAnchor`] anchor types.
pub struct ElectrumFullScanResult<K>(FullScanResult<K, ConfirmationHeightAnchor>);

impl<K> ElectrumFullScanResult<K> {
/// Return [`FullScanResult`] with [`ConfirmationHeightAnchor`].
pub fn with_confirmation_height_anchor(self) -> FullScanResult<K, ConfirmationHeightAnchor> {
self.0
}

fn try_into_confirmation_time_result(
/// Return [`FullScanResult`] with [`ConfirmationTimeHeightAnchor`].
///
/// This requires additional calls to the Electrum server.
pub fn with_confirmation_time_height_anchor(
self,
client: &impl ElectrumApi,
) -> Result<Self::NewResult, Error> {
Ok(FullScanResult::<K, ConfirmationTimeHeightAnchor> {
graph_update: try_into_confirmation_time_result(self.graph_update, client)?,
chain_update: self.chain_update,
last_active_indices: self.last_active_indices,
) -> Result<FullScanResult<K, ConfirmationTimeHeightAnchor>, Error> {
let res = self.0;
Ok(FullScanResult {
graph_update: try_into_confirmation_time_result(res.graph_update, client)?,
chain_update: res.chain_update,
last_active_indices: res.last_active_indices,
})
}
}

impl ElectrumResultExt for SyncResult<ConfirmationHeightAnchor> {
type NewResult = SyncResult<ConfirmationTimeHeightAnchor>;
/// The result of [`ElectrumExt::sync`].
///
/// This can be transformed into a [`SyncResult`] with either [`ConfirmationHeightAnchor`] or
/// [`ConfirmationTimeHeightAnchor`] anchor types.
pub struct ElectrumSyncResult(SyncResult<ConfirmationHeightAnchor>);

impl ElectrumSyncResult {
/// Return [`SyncResult`] with [`ConfirmationHeightAnchor`].
pub fn with_confirmation_height_anchor(self) -> SyncResult<ConfirmationHeightAnchor> {
self.0
}

fn try_into_confirmation_time_result(
/// Return [`SyncResult`] with [`ConfirmationTimeHeightAnchor`].
///
/// This requires additional calls to the Electrum server.
pub fn with_confirmation_time_height_anchor(
self,
client: &impl ElectrumApi,
) -> Result<Self::NewResult, Error> {
) -> Result<SyncResult<ConfirmationTimeHeightAnchor>, Error> {
let res = self.0;
Ok(SyncResult {
graph_update: try_into_confirmation_time_result(self.graph_update, client)?,
chain_update: self.chain_update,
graph_update: try_into_confirmation_time_result(res.graph_update, client)?,
chain_update: res.chain_update,
})
}
}
Expand Down
8 changes: 4 additions & 4 deletions crates/electrum/tests/test_electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use bdk_chain::{
spk_client::SyncRequest,
ConfirmationTimeHeightAnchor, IndexedTxGraph, SpkTxOutIndex,
};
use bdk_electrum::{ElectrumExt, ElectrumResultExt};
use bdk_electrum::ElectrumExt;
use bdk_testenv::{anyhow, anyhow::Result, bitcoincore_rpc::RpcApi, TestEnv};

fn get_balance(
Expand Down Expand Up @@ -67,7 +67,7 @@ fn scan_detects_confirmed_tx() -> Result<()> {
.chain_spks(core::iter::once(spk_to_track)),
5,
)?
.try_into_confirmation_time_result(&client)?;
.with_confirmation_time_height_anchor(&client)?;

let _ = recv_chain
.apply_update(update.chain_update)
Expand Down Expand Up @@ -133,7 +133,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> Result<()> {
SyncRequest::from_chain_tip(recv_chain.tip()).chain_spks([spk_to_track.clone()]),
5,
)?
.try_into_confirmation_time_result(&client)?;
.with_confirmation_time_height_anchor(&client)?;

let _ = recv_chain
.apply_update(update.chain_update)
Expand Down Expand Up @@ -163,7 +163,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> Result<()> {
SyncRequest::from_chain_tip(recv_chain.tip()).chain_spks([spk_to_track.clone()]),
5,
)?
.try_into_confirmation_time_result(&client)?;
.with_confirmation_time_height_anchor(&client)?;

let _ = recv_chain
.apply_update(update.chain_update)
Expand Down
6 changes: 4 additions & 2 deletions example-crates/example_electrum/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ fn main() -> anyhow::Result<()> {

let res = client
.full_scan::<_>(request, stop_gap, scan_options.batch_size)
.context("scanning the blockchain")?;
.context("scanning the blockchain")?
.with_confirmation_height_anchor();
(
res.chain_update,
res.graph_update,
Expand Down Expand Up @@ -311,7 +312,8 @@ fn main() -> anyhow::Result<()> {

let res = client
.sync(request, scan_options.batch_size)
.context("scanning the blockchain")?;
.context("scanning the blockchain")?
.with_confirmation_height_anchor();

// drop lock on graph and chain
drop((graph, chain));
Expand Down
3 changes: 1 addition & 2 deletions example-crates/wallet_electrum/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use bdk::bitcoin::{Address, Amount};
use bdk::chain::collections::HashSet;
use bdk::{bitcoin::Network, Wallet};
use bdk::{KeychainKind, SignOptions};
use bdk_electrum::ElectrumResultExt;
use bdk_electrum::{
electrum_client::{self, ElectrumApi},
ElectrumExt,
Expand Down Expand Up @@ -55,7 +54,7 @@ fn main() -> Result<(), anyhow::Error> {

let mut update = client
.full_scan(request, STOP_GAP, BATCH_SIZE)?
.try_into_confirmation_time_result(&client)?;
.with_confirmation_time_height_anchor(&client)?;

let now = std::time::UNIX_EPOCH.elapsed().unwrap().as_secs();
let _ = update.graph_update.update_last_seen_unconfirmed(now);
Expand Down

0 comments on commit c0d7d60

Please sign in to comment.