Skip to content

Commit 67ec82d

Browse files
committed
feat(persist_test_utils): Add persist_wallet_changeset_async
1 parent fca6523 commit 67ec82d

1 file changed

Lines changed: 258 additions & 1 deletion

File tree

src/persist_test_utils.rs

Lines changed: 258 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
//! Utilities for testing custom persistence backends for `bdk_wallet`
22
3+
use alloc::boxed::Box;
4+
use core::fmt;
5+
#[cfg(feature = "std")]
6+
use std::error::Error as StdErr;
7+
38
use crate::{
49
bitcoin::{
510
absolute, key::Secp256k1, transaction, Address, Amount, Network, OutPoint, ScriptBuf,
@@ -11,7 +16,7 @@ use crate::{
1116
},
1217
locked_outpoints,
1318
miniscript::descriptor::{Descriptor, DescriptorPublicKey},
14-
ChangeSet, WalletPersister,
19+
AsyncWalletPersister, ChangeSet, WalletPersister,
1520
};
1621

1722
macro_rules! block_id {
@@ -432,3 +437,255 @@ where
432437
assert_eq!(changeset_read.descriptor.unwrap(), descriptor);
433438
assert_eq!(changeset_read.change_descriptor, None);
434439
}
440+
441+
/// Creates a [`ChangeSet`].
442+
fn get_changeset(tx1: Transaction) -> ChangeSet {
443+
let descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[0].parse().unwrap();
444+
let change_descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[1].parse().unwrap();
445+
446+
let local_chain_changeset = local_chain::ChangeSet {
447+
blocks: [
448+
(910234, Some(hash!("B"))),
449+
(910233, Some(hash!("T"))),
450+
(910235, Some(hash!("C"))),
451+
]
452+
.into(),
453+
};
454+
455+
let txid1 = tx1.compute_txid();
456+
457+
let conf_anchor: ConfirmationBlockTime = ConfirmationBlockTime {
458+
block_id: block_id!(910234, "B"),
459+
confirmation_time: 1755317160,
460+
};
461+
462+
let outpoint = OutPoint::new(hash!("Rust"), 0);
463+
464+
let tx_graph_changeset = tx_graph::ChangeSet::<ConfirmationBlockTime> {
465+
txs: [Arc::new(tx1)].into(),
466+
txouts: [
467+
(
468+
outpoint,
469+
TxOut {
470+
value: Amount::from_sat(1300),
471+
script_pubkey: spk_at_index(&descriptor, 4),
472+
},
473+
),
474+
(
475+
OutPoint::new(hash!("REDB"), 0),
476+
TxOut {
477+
value: Amount::from_sat(1400),
478+
script_pubkey: spk_at_index(&descriptor, 10),
479+
},
480+
),
481+
]
482+
.into(),
483+
anchors: [(conf_anchor, txid1)].into(),
484+
last_seen: [(txid1, 1755317760)].into(),
485+
first_seen: [(txid1, 1755317750)].into(),
486+
last_evicted: [(txid1, 1755317760)].into(),
487+
};
488+
489+
let keychain_txout_changeset = keychain_txout::ChangeSet {
490+
last_revealed: [
491+
(descriptor.descriptor_id(), 12),
492+
(change_descriptor.descriptor_id(), 10),
493+
]
494+
.into(),
495+
spk_cache: [
496+
(
497+
descriptor.descriptor_id(),
498+
SpkIterator::new_with_range(&descriptor, 0..=37).collect(),
499+
),
500+
(
501+
change_descriptor.descriptor_id(),
502+
SpkIterator::new_with_range(&change_descriptor, 0..=35).collect(),
503+
),
504+
]
505+
.into(),
506+
};
507+
508+
let locked_outpoints_changeset = locked_outpoints::ChangeSet {
509+
outpoints: [(outpoint, true)].into(),
510+
};
511+
512+
ChangeSet {
513+
descriptor: Some(descriptor.clone()),
514+
change_descriptor: Some(change_descriptor.clone()),
515+
network: Some(Network::Testnet),
516+
local_chain: local_chain_changeset,
517+
tx_graph: tx_graph_changeset,
518+
indexer: keychain_txout_changeset,
519+
locked_outpoints: locked_outpoints_changeset,
520+
}
521+
}
522+
523+
/// Creates another [`ChangeSet`].
524+
fn get_changeset_two(tx2: Transaction) -> ChangeSet {
525+
let descriptor: Descriptor<DescriptorPublicKey> = DESCRIPTORS[0].parse().unwrap();
526+
527+
let local_chain_changeset = local_chain::ChangeSet {
528+
blocks: [(910236, Some(hash!("BDK")))].into(),
529+
};
530+
531+
let conf_anchor: ConfirmationBlockTime = ConfirmationBlockTime {
532+
block_id: block_id!(910236, "BDK"),
533+
confirmation_time: 1755317760,
534+
};
535+
536+
let txid2 = tx2.compute_txid();
537+
538+
let outpoint = OutPoint::new(hash!("Bitcoin_fixes_things"), 0);
539+
540+
let tx_graph_changeset = tx_graph::ChangeSet::<ConfirmationBlockTime> {
541+
txs: [Arc::new(tx2)].into(),
542+
txouts: [(
543+
outpoint,
544+
TxOut {
545+
value: Amount::from_sat(10000),
546+
script_pubkey: spk_at_index(&descriptor, 21),
547+
},
548+
)]
549+
.into(),
550+
anchors: [(conf_anchor, txid2)].into(),
551+
last_seen: [(txid2, 1755317700)].into(),
552+
first_seen: [(txid2, 1755317700)].into(),
553+
last_evicted: [(txid2, 1755317760)].into(),
554+
};
555+
556+
let keychain_txout_changeset = keychain_txout::ChangeSet {
557+
last_revealed: [(descriptor.descriptor_id(), 14)].into(),
558+
spk_cache: [(
559+
descriptor.descriptor_id(),
560+
SpkIterator::new_with_range(&descriptor, 37..=39).collect(),
561+
)]
562+
.into(),
563+
};
564+
565+
let locked_outpoints_changeset = locked_outpoints::ChangeSet {
566+
outpoints: [(outpoint, true)].into(),
567+
};
568+
569+
ChangeSet {
570+
descriptor: None,
571+
change_descriptor: None,
572+
network: None,
573+
local_chain: local_chain_changeset,
574+
tx_graph: tx_graph_changeset,
575+
indexer: keychain_txout_changeset,
576+
locked_outpoints: locked_outpoints_changeset,
577+
}
578+
}
579+
580+
/// Errors caused by a failed wallet persister test.
581+
#[derive(Debug)]
582+
pub enum PersistError {
583+
/// Change set mismatch
584+
ChangeSetMismatch {
585+
/// the resulting changeset
586+
got: Box<ChangeSet>,
587+
/// the expected changeset
588+
expected: Box<ChangeSet>,
589+
},
590+
/// The wallet persister implementation failed
591+
Persister(Box<dyn StdErr + 'static>),
592+
}
593+
594+
impl fmt::Display for PersistError {
595+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
596+
match self {
597+
Self::Persister(e) => write!(f, "{e}"),
598+
Self::ChangeSetMismatch { got, expected } => {
599+
write!(f, "expected: {expected:?}, got: {got:?}")
600+
}
601+
}
602+
}
603+
}
604+
605+
#[cfg(feature = "std")]
606+
impl StdErr for PersistError {}
607+
608+
impl PersistError {
609+
/// Converts `e` to a [`PersistError::Persister`].
610+
fn persister<E>(e: E) -> Self
611+
where
612+
E: StdErr + 'static,
613+
{
614+
Self::Persister(Box::new(e))
615+
}
616+
}
617+
618+
/// Test the functionality of an [`AsyncWalletPersister`].
619+
///
620+
/// # Errors
621+
///
622+
/// If any of the following doesn't occur:
623+
///
624+
/// - The store must initially be empty
625+
/// - The store must persist non-empty changesets
626+
/// - The store must return the expected changeset after being persisted
627+
pub async fn persist_wallet_changeset_async<F, P>(create_store: F) -> Result<(), PersistError>
628+
where
629+
F: AsyncFn() -> Result<P, P::Error>,
630+
P: AsyncWalletPersister,
631+
P::Error: StdErr + 'static,
632+
{
633+
use PersistError as E;
634+
635+
// Create store
636+
let mut store = create_store().await.map_err(E::persister)?;
637+
let changeset = AsyncWalletPersister::initialize(&mut store)
638+
.await
639+
.map_err(E::persister)?;
640+
641+
// A newly created store must return an empty changeset
642+
if !changeset.is_empty() {
643+
return Err(PersistError::ChangeSetMismatch {
644+
got: Box::new(changeset),
645+
expected: Box::new(ChangeSet::default()),
646+
});
647+
}
648+
649+
let tx1 = create_one_inp_one_out_tx(hash!("We_are_all_Satoshi"), 30_000);
650+
let tx2 = create_one_inp_one_out_tx(tx1.compute_txid(), 20_000);
651+
652+
// Persist changeset
653+
let mut expected_changeset = get_changeset(tx1);
654+
655+
AsyncWalletPersister::persist(&mut store, &expected_changeset)
656+
.await
657+
.map_err(E::persister)?;
658+
659+
let changeset_read = AsyncWalletPersister::initialize(&mut store)
660+
.await
661+
.map_err(E::persister)?;
662+
663+
if changeset_read != expected_changeset {
664+
return Err(E::ChangeSetMismatch {
665+
got: Box::new(changeset_read),
666+
expected: Box::new(expected_changeset),
667+
});
668+
}
669+
670+
// Persist another changeset
671+
let changeset_2 = get_changeset_two(tx2);
672+
673+
AsyncWalletPersister::persist(&mut store, &changeset_2)
674+
.await
675+
.map_err(E::persister)?;
676+
677+
let changeset_read = AsyncWalletPersister::initialize(&mut store)
678+
.await
679+
.map_err(E::persister)?;
680+
681+
expected_changeset.merge(changeset_2);
682+
683+
if changeset_read != expected_changeset {
684+
return Err(E::ChangeSetMismatch {
685+
got: Box::new(changeset_read),
686+
expected: Box::new(expected_changeset),
687+
});
688+
}
689+
690+
Ok(())
691+
}

0 commit comments

Comments
 (0)