feat(esplora)!: update to use new sync/full-scan structures

This commit is contained in:
志宇
2024-04-25 10:36:06 +08:00
parent 4c52f3e08e
commit 0f94f24aaf
8 changed files with 243 additions and 260 deletions

View File

@@ -1,6 +1,7 @@
use std::collections::BTreeSet;
use async_trait::async_trait;
use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult};
use bdk_chain::Anchor;
use bdk_chain::{
bitcoin::{BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
@@ -11,7 +12,7 @@ use bdk_chain::{
use esplora_client::{Amount, TxStatus};
use futures::{stream::FuturesOrdered, TryStreamExt};
use crate::{anchor_from_status, FullScanUpdate, SyncUpdate};
use crate::anchor_from_status;
/// [`esplora_client::Error`]
type Error = Box<esplora_client::Error>;
@@ -50,14 +51,10 @@ pub trait EsploraAsyncExt {
/// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
async fn full_scan<K: Ord + Clone + Send>(
&self,
local_tip: CheckPoint,
keychain_spks: BTreeMap<
K,
impl IntoIterator<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + Send> + Send,
>,
request: FullScanRequest<K>,
stop_gap: usize,
parallel_requests: usize,
) -> Result<FullScanUpdate<K>, Error>;
) -> Result<FullScanResult<K>, Error>;
/// Sync a set of scripts with the blockchain (via an Esplora client) for the data
/// specified and return a [`TxGraph`].
@@ -75,12 +72,9 @@ pub trait EsploraAsyncExt {
/// [`full_scan`]: EsploraAsyncExt::full_scan
async fn sync(
&self,
local_tip: CheckPoint,
misc_spks: impl IntoIterator<IntoIter = impl Iterator<Item = ScriptBuf> + Send> + Send,
txids: impl IntoIterator<IntoIter = impl Iterator<Item = Txid> + Send> + Send,
outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
request: SyncRequest,
parallel_requests: usize,
) -> Result<SyncUpdate, Error>;
) -> Result<SyncResult, Error>;
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
@@ -88,42 +82,56 @@ pub trait EsploraAsyncExt {
impl EsploraAsyncExt for esplora_client::AsyncClient {
async fn full_scan<K: Ord + Clone + Send>(
&self,
local_tip: CheckPoint,
keychain_spks: BTreeMap<
K,
impl IntoIterator<IntoIter = impl Iterator<Item = (u32, ScriptBuf)> + Send> + Send,
>,
request: FullScanRequest<K>,
stop_gap: usize,
parallel_requests: usize,
) -> Result<FullScanUpdate<K>, Error> {
) -> Result<FullScanResult<K>, Error> {
let latest_blocks = fetch_latest_blocks(self).await?;
let (tx_graph, last_active_indices) =
full_scan_for_index_and_graph(self, keychain_spks, stop_gap, parallel_requests).await?;
let local_chain =
chain_update(self, &latest_blocks, &local_tip, tx_graph.all_anchors()).await?;
Ok(FullScanUpdate {
local_chain,
tx_graph,
let (graph_update, last_active_indices) = full_scan_for_index_and_graph(
self,
request.spks_by_keychain,
stop_gap,
parallel_requests,
)
.await?;
let chain_update = chain_update(
self,
&latest_blocks,
&request.chain_tip,
graph_update.all_anchors(),
)
.await?;
Ok(FullScanResult {
chain_update,
graph_update,
last_active_indices,
})
}
async fn sync(
&self,
local_tip: CheckPoint,
misc_spks: impl IntoIterator<IntoIter = impl Iterator<Item = ScriptBuf> + Send> + Send,
txids: impl IntoIterator<IntoIter = impl Iterator<Item = Txid> + Send> + Send,
outpoints: impl IntoIterator<IntoIter = impl Iterator<Item = OutPoint> + Send> + Send,
request: SyncRequest,
parallel_requests: usize,
) -> Result<SyncUpdate, Error> {
) -> Result<SyncResult, Error> {
let latest_blocks = fetch_latest_blocks(self).await?;
let tx_graph =
sync_for_index_and_graph(self, misc_spks, txids, outpoints, parallel_requests).await?;
let local_chain =
chain_update(self, &latest_blocks, &local_tip, tx_graph.all_anchors()).await?;
Ok(SyncUpdate {
tx_graph,
local_chain,
let graph_update = sync_for_index_and_graph(
self,
request.spks,
request.txids,
request.outpoints,
parallel_requests,
)
.await?;
let chain_update = chain_update(
self,
&latest_blocks,
&request.chain_tip,
graph_update.all_anchors(),
)
.await?;
Ok(SyncResult {
chain_update,
graph_update,
})
}
}

View File

@@ -3,6 +3,7 @@ use std::thread::JoinHandle;
use std::usize;
use bdk_chain::collections::BTreeMap;
use bdk_chain::spk_client::{FullScanRequest, FullScanResult, SyncRequest, SyncResult};
use bdk_chain::Anchor;
use bdk_chain::{
bitcoin::{Amount, BlockHash, OutPoint, ScriptBuf, TxOut, Txid},
@@ -12,8 +13,6 @@ use bdk_chain::{
use esplora_client::TxStatus;
use crate::anchor_from_status;
use crate::FullScanUpdate;
use crate::SyncUpdate;
/// [`esplora_client::Error`]
pub type Error = Box<esplora_client::Error>;
@@ -50,11 +49,10 @@ pub trait EsploraExt {
/// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
fn full_scan<K: Ord + Clone>(
&self,
local_tip: CheckPoint,
keychain_spks: BTreeMap<K, impl IntoIterator<Item = (u32, ScriptBuf)>>,
request: FullScanRequest<K>,
stop_gap: usize,
parallel_requests: usize,
) -> Result<FullScanUpdate<K>, Error>;
) -> Result<FullScanResult<K>, Error>;
/// Sync a set of scripts with the blockchain (via an Esplora client) for the data
/// specified and return a [`TxGraph`].
@@ -70,59 +68,54 @@ pub trait EsploraExt {
///
/// [`LocalChain::tip`]: bdk_chain::local_chain::LocalChain::tip
/// [`full_scan`]: EsploraExt::full_scan
fn sync(
&self,
local_tip: CheckPoint,
misc_spks: impl IntoIterator<Item = ScriptBuf>,
txids: impl IntoIterator<Item = Txid>,
outpoints: impl IntoIterator<Item = OutPoint>,
parallel_requests: usize,
) -> Result<SyncUpdate, Error>;
fn sync(&self, request: SyncRequest, parallel_requests: usize) -> Result<SyncResult, Error>;
}
impl EsploraExt for esplora_client::BlockingClient {
fn full_scan<K: Ord + Clone>(
&self,
local_tip: CheckPoint,
keychain_spks: BTreeMap<K, impl IntoIterator<Item = (u32, ScriptBuf)>>,
request: FullScanRequest<K>,
stop_gap: usize,
parallel_requests: usize,
) -> Result<FullScanUpdate<K>, Error> {
) -> Result<FullScanResult<K>, Error> {
let latest_blocks = fetch_latest_blocks(self)?;
let (tx_graph, last_active_indices) = full_scan_for_index_and_graph_blocking(
let (graph_update, last_active_indices) = full_scan_for_index_and_graph_blocking(
self,
keychain_spks,
request.spks_by_keychain,
stop_gap,
parallel_requests,
)?;
let local_chain = chain_update(self, &latest_blocks, &local_tip, tx_graph.all_anchors())?;
Ok(FullScanUpdate {
local_chain,
tx_graph,
let chain_update = chain_update(
self,
&latest_blocks,
&request.chain_tip,
graph_update.all_anchors(),
)?;
Ok(FullScanResult {
chain_update,
graph_update,
last_active_indices,
})
}
fn sync(
&self,
local_tip: CheckPoint,
misc_spks: impl IntoIterator<Item = ScriptBuf>,
txids: impl IntoIterator<Item = Txid>,
outpoints: impl IntoIterator<Item = OutPoint>,
parallel_requests: usize,
) -> Result<SyncUpdate, Error> {
fn sync(&self, request: SyncRequest, parallel_requests: usize) -> Result<SyncResult, Error> {
let latest_blocks = fetch_latest_blocks(self)?;
let tx_graph = sync_for_index_and_graph_blocking(
let graph_update = sync_for_index_and_graph_blocking(
self,
misc_spks,
txids,
outpoints,
request.spks,
request.txids,
request.outpoints,
parallel_requests,
)?;
let local_chain = chain_update(self, &latest_blocks, &local_tip, tx_graph.all_anchors())?;
Ok(SyncUpdate {
local_chain,
tx_graph,
let chain_update = chain_update(
self,
&latest_blocks,
&request.chain_tip,
graph_update.all_anchors(),
)?;
Ok(SyncResult {
chain_update,
graph_update,
})
}
}

View File

@@ -16,9 +16,7 @@
//! [`TxGraph`]: bdk_chain::tx_graph::TxGraph
//! [`example_esplora`]: https://github.com/bitcoindevkit/bdk/tree/master/example-crates/example_esplora
use std::collections::BTreeMap;
use bdk_chain::{local_chain::CheckPoint, BlockId, ConfirmationTimeHeightAnchor, TxGraph};
use bdk_chain::{BlockId, ConfirmationTimeHeightAnchor};
use esplora_client::TxStatus;
pub use esplora_client;
@@ -50,21 +48,3 @@ fn anchor_from_status(status: &TxStatus) -> Option<ConfirmationTimeHeightAnchor>
None
}
}
/// Update returns from a full scan.
pub struct FullScanUpdate<K> {
/// The update to apply to the receiving [`LocalChain`](bdk_chain::local_chain::LocalChain).
pub local_chain: CheckPoint,
/// The update to apply to the receiving [`TxGraph`].
pub tx_graph: TxGraph<ConfirmationTimeHeightAnchor>,
/// Last active indices for the corresponding keychains (`K`).
pub last_active_indices: BTreeMap<K, u32>,
}
/// Update returned from a sync.
pub struct SyncUpdate {
/// The update to apply to the receiving [`LocalChain`](bdk_chain::local_chain::LocalChain).
pub local_chain: CheckPoint,
/// The update to apply to the receiving [`TxGraph`].
pub tx_graph: TxGraph<ConfirmationTimeHeightAnchor>,
}

View File

@@ -1,8 +1,9 @@
use bdk_chain::spk_client::{FullScanRequest, SyncRequest};
use bdk_esplora::EsploraAsyncExt;
use electrsd::bitcoind::anyhow;
use electrsd::bitcoind::bitcoincore_rpc::RpcApi;
use esplora_client::{self, Builder};
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::collections::{BTreeSet, HashSet};
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;
@@ -55,20 +56,15 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
// use a full checkpoint linked list (since this is not what we are testing)
let cp_tip = env.make_checkpoint_tip();
let sync_update = client
.sync(
cp_tip.clone(),
misc_spks.into_iter(),
vec![].into_iter(),
vec![].into_iter(),
1,
)
.await?;
let sync_update = {
let request = SyncRequest::from_chain_tip(cp_tip.clone()).set_spks(misc_spks);
client.sync(request, 1).await?
};
assert!(
{
let update_cps = sync_update
.local_chain
.chain_update
.iter()
.map(|cp| cp.block_id())
.collect::<BTreeSet<_>>();
@@ -81,7 +77,7 @@ pub async fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
"update should not alter original checkpoint tip since we already started with all checkpoints",
);
let graph_update = sync_update.tx_graph;
let graph_update = sync_update.graph_update;
// Check to see if we have the floating txouts available from our two created transactions'
// previous outputs in order to calculate transaction fees.
for tx in graph_update.full_txs() {
@@ -142,8 +138,6 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.enumerate()
.map(|(i, addr)| (i as u32, addr.script_pubkey()))
.collect();
let mut keychains = BTreeMap::new();
keychains.insert(0, spks);
// Then receive coins on the 4th address.
let txid_4th_addr = env.bitcoind.client.send_to_address(
@@ -166,16 +160,25 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
// A scan with a gap limit of 3 won't find the transaction, but a scan with a gap limit of 4
// will.
let full_scan_update = client
.full_scan(cp_tip.clone(), keychains.clone(), 3, 1)
.await?;
assert!(full_scan_update.tx_graph.full_txs().next().is_none());
let full_scan_update = {
let request =
FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
client.full_scan(request, 3, 1).await?
};
assert!(full_scan_update.graph_update.full_txs().next().is_none());
assert!(full_scan_update.last_active_indices.is_empty());
let full_scan_update = client
.full_scan(cp_tip.clone(), keychains.clone(), 4, 1)
.await?;
let full_scan_update = {
let request =
FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
client.full_scan(request, 4, 1).await?
};
assert_eq!(
full_scan_update.tx_graph.full_txs().next().unwrap().txid,
full_scan_update
.graph_update
.full_txs()
.next()
.unwrap()
.txid,
txid_4th_addr
);
assert_eq!(full_scan_update.last_active_indices[&0], 3);
@@ -198,20 +201,26 @@ pub async fn test_async_update_tx_graph_stop_gap() -> anyhow::Result<()> {
// A scan with gap limit 5 won't find the second transaction, but a scan with gap limit 6 will.
// The last active indice won't be updated in the first case but will in the second one.
let full_scan_update = client
.full_scan(cp_tip.clone(), keychains.clone(), 5, 1)
.await?;
let full_scan_update = {
let request =
FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
client.full_scan(request, 5, 1).await?
};
let txs: HashSet<_> = full_scan_update
.tx_graph
.graph_update
.full_txs()
.map(|tx| tx.txid)
.collect();
assert_eq!(txs.len(), 1);
assert!(txs.contains(&txid_4th_addr));
assert_eq!(full_scan_update.last_active_indices[&0], 3);
let full_scan_update = client.full_scan(cp_tip, keychains, 6, 1).await?;
let full_scan_update = {
let request =
FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
client.full_scan(request, 6, 1).await?
};
let txs: HashSet<_> = full_scan_update
.tx_graph
.graph_update
.full_txs()
.map(|tx| tx.txid)
.collect();

View File

@@ -1,8 +1,9 @@
use bdk_chain::spk_client::{FullScanRequest, SyncRequest};
use bdk_esplora::EsploraExt;
use electrsd::bitcoind::anyhow;
use electrsd::bitcoind::bitcoincore_rpc::RpcApi;
use esplora_client::{self, Builder};
use std::collections::{BTreeMap, BTreeSet, HashSet};
use std::collections::{BTreeSet, HashSet};
use std::str::FromStr;
use std::thread::sleep;
use std::time::Duration;
@@ -55,18 +56,15 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
// use a full checkpoint linked list (since this is not what we are testing)
let cp_tip = env.make_checkpoint_tip();
let sync_update = client.sync(
cp_tip.clone(),
misc_spks.into_iter(),
vec![].into_iter(),
vec![].into_iter(),
1,
)?;
let sync_update = {
let request = SyncRequest::from_chain_tip(cp_tip.clone()).set_spks(misc_spks);
client.sync(request, 1)?
};
assert!(
{
let update_cps = sync_update
.local_chain
.chain_update
.iter()
.map(|cp| cp.block_id())
.collect::<BTreeSet<_>>();
@@ -79,7 +77,7 @@ pub fn test_update_tx_graph_without_keychain() -> anyhow::Result<()> {
"update should not alter original checkpoint tip since we already started with all checkpoints",
);
let graph_update = sync_update.tx_graph;
let graph_update = sync_update.graph_update;
// Check to see if we have the floating txouts available from our two created transactions'
// previous outputs in order to calculate transaction fees.
for tx in graph_update.full_txs() {
@@ -141,8 +139,6 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
.enumerate()
.map(|(i, addr)| (i as u32, addr.script_pubkey()))
.collect();
let mut keychains = BTreeMap::new();
keychains.insert(0, spks);
// Then receive coins on the 4th address.
let txid_4th_addr = env.bitcoind.client.send_to_address(
@@ -165,12 +161,25 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
// A scan with a stop_gap of 3 won't find the transaction, but a scan with a gap limit of 4
// will.
let full_scan_update = client.full_scan(cp_tip.clone(), keychains.clone(), 3, 1)?;
assert!(full_scan_update.tx_graph.full_txs().next().is_none());
let full_scan_update = {
let request =
FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
client.full_scan(request, 3, 1)?
};
assert!(full_scan_update.graph_update.full_txs().next().is_none());
assert!(full_scan_update.last_active_indices.is_empty());
let full_scan_update = client.full_scan(cp_tip.clone(), keychains.clone(), 4, 1)?;
let full_scan_update = {
let request =
FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
client.full_scan(request, 4, 1)?
};
assert_eq!(
full_scan_update.tx_graph.full_txs().next().unwrap().txid,
full_scan_update
.graph_update
.full_txs()
.next()
.unwrap()
.txid,
txid_4th_addr
);
assert_eq!(full_scan_update.last_active_indices[&0], 3);
@@ -193,18 +202,26 @@ pub fn test_update_tx_graph_stop_gap() -> anyhow::Result<()> {
// A scan with gap limit 5 won't find the second transaction, but a scan with gap limit 6 will.
// The last active indice won't be updated in the first case but will in the second one.
let full_scan_update = client.full_scan(cp_tip.clone(), keychains.clone(), 5, 1)?;
let full_scan_update = {
let request =
FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
client.full_scan(request, 5, 1)?
};
let txs: HashSet<_> = full_scan_update
.tx_graph
.graph_update
.full_txs()
.map(|tx| tx.txid)
.collect();
assert_eq!(txs.len(), 1);
assert!(txs.contains(&txid_4th_addr));
assert_eq!(full_scan_update.last_active_indices[&0], 3);
let full_scan_update = client.full_scan(cp_tip.clone(), keychains, 6, 1)?;
let full_scan_update = {
let request =
FullScanRequest::from_chain_tip(cp_tip.clone()).set_spks_for_keychain(0, spks.clone());
client.full_scan(request, 6, 1)?
};
let txs: HashSet<_> = full_scan_update
.tx_graph
.graph_update
.full_txs()
.map(|tx| tx.txid)
.collect();