@@ -432,3 +432,234 @@ where
432432 assert_eq ! ( changeset_read. descriptor. unwrap( ) , descriptor) ;
433433 assert_eq ! ( changeset_read. change_descriptor, None ) ;
434434}
435+
436+ // WIP: Add async support to `persist_test_utils`
437+ // TODO:
438+ // - Refactor persist_wallet_changeset (to remove tempfile, anyhow)
439+ // - Introduce PersistError, and handle more errors
440+ // - Extract changeset construction to be used by both sync/async
441+
442+ use alloc:: string:: { String , ToString } ;
443+ use core:: fmt;
444+
445+ use crate :: AsyncWalletPersister ;
446+
447+ /// Error while testing a wallet persister.
448+ #[ derive( Debug ) ]
449+ pub enum PersistError {
450+ /// Store error
451+ Store ( String ) ,
452+ }
453+
454+ impl fmt:: Display for PersistError {
455+ fn fmt ( & self , f : & mut fmt:: Formatter < ' _ > ) -> fmt:: Result {
456+ match self {
457+ Self :: Store ( e) => write ! ( f, "{e}" ) ,
458+ }
459+ }
460+ }
461+
462+ #[ cfg( feature = "std" ) ]
463+ impl std:: error:: Error for PersistError { }
464+
465+ /// Creates a [`ChangeSet`].
466+ fn get_changeset ( tx1 : Transaction ) -> ChangeSet {
467+ let descriptor: Descriptor < DescriptorPublicKey > = DESCRIPTORS [ 0 ] . parse ( ) . unwrap ( ) ;
468+ let change_descriptor: Descriptor < DescriptorPublicKey > = DESCRIPTORS [ 1 ] . parse ( ) . unwrap ( ) ;
469+
470+ let local_chain_changeset = local_chain:: ChangeSet {
471+ blocks : [
472+ ( 910234 , Some ( hash ! ( "B" ) ) ) ,
473+ ( 910233 , Some ( hash ! ( "T" ) ) ) ,
474+ ( 910235 , Some ( hash ! ( "C" ) ) ) ,
475+ ]
476+ . into ( ) ,
477+ } ;
478+
479+ let txid1 = tx1. compute_txid ( ) ;
480+
481+ let conf_anchor: ConfirmationBlockTime = ConfirmationBlockTime {
482+ block_id : block_id ! ( 910234 , "B" ) ,
483+ confirmation_time : 1755317160 ,
484+ } ;
485+
486+ let outpoint = OutPoint :: new ( hash ! ( "Rust" ) , 0 ) ;
487+
488+ let tx_graph_changeset = tx_graph:: ChangeSet :: < ConfirmationBlockTime > {
489+ txs : [ Arc :: new ( tx1) ] . into ( ) ,
490+ txouts : [
491+ (
492+ outpoint,
493+ TxOut {
494+ value : Amount :: from_sat ( 1300 ) ,
495+ script_pubkey : spk_at_index ( & descriptor, 4 ) ,
496+ } ,
497+ ) ,
498+ (
499+ OutPoint :: new ( hash ! ( "REDB" ) , 0 ) ,
500+ TxOut {
501+ value : Amount :: from_sat ( 1400 ) ,
502+ script_pubkey : spk_at_index ( & descriptor, 10 ) ,
503+ } ,
504+ ) ,
505+ ]
506+ . into ( ) ,
507+ anchors : [ ( conf_anchor, txid1) ] . into ( ) ,
508+ last_seen : [ ( txid1, 1755317760 ) ] . into ( ) ,
509+ first_seen : [ ( txid1, 1755317750 ) ] . into ( ) ,
510+ last_evicted : [ ( txid1, 1755317760 ) ] . into ( ) ,
511+ } ;
512+
513+ let keychain_txout_changeset = keychain_txout:: ChangeSet {
514+ last_revealed : [
515+ ( descriptor. descriptor_id ( ) , 12 ) ,
516+ ( change_descriptor. descriptor_id ( ) , 10 ) ,
517+ ]
518+ . into ( ) ,
519+ spk_cache : [
520+ (
521+ descriptor. descriptor_id ( ) ,
522+ SpkIterator :: new_with_range ( & descriptor, 0 ..=37 ) . collect ( ) ,
523+ ) ,
524+ (
525+ change_descriptor. descriptor_id ( ) ,
526+ SpkIterator :: new_with_range ( & change_descriptor, 0 ..=35 ) . collect ( ) ,
527+ ) ,
528+ ]
529+ . into ( ) ,
530+ } ;
531+
532+ let locked_outpoints_changeset = locked_outpoints:: ChangeSet {
533+ outpoints : [ ( outpoint, true ) ] . into ( ) ,
534+ } ;
535+
536+ ChangeSet {
537+ descriptor : Some ( descriptor. clone ( ) ) ,
538+ change_descriptor : Some ( change_descriptor. clone ( ) ) ,
539+ network : Some ( Network :: Testnet ) ,
540+ local_chain : local_chain_changeset,
541+ tx_graph : tx_graph_changeset,
542+ indexer : keychain_txout_changeset,
543+ locked_outpoints : locked_outpoints_changeset,
544+ }
545+ }
546+
547+ /// Creates another [`ChangeSet`].
548+ fn get_changeset_two ( tx2 : Transaction ) -> ChangeSet {
549+ let descriptor: Descriptor < DescriptorPublicKey > = DESCRIPTORS [ 0 ] . parse ( ) . unwrap ( ) ;
550+
551+ let local_chain_changeset = local_chain:: ChangeSet {
552+ blocks : [ ( 910236 , Some ( hash ! ( "BDK" ) ) ) ] . into ( ) ,
553+ } ;
554+
555+ let conf_anchor: ConfirmationBlockTime = ConfirmationBlockTime {
556+ block_id : block_id ! ( 910236 , "BDK" ) ,
557+ confirmation_time : 1755317760 ,
558+ } ;
559+
560+ let txid2 = tx2. compute_txid ( ) ;
561+
562+ let outpoint = OutPoint :: new ( hash ! ( "Bitcoin_fixes_things" ) , 0 ) ;
563+
564+ let tx_graph_changeset = tx_graph:: ChangeSet :: < ConfirmationBlockTime > {
565+ txs : [ Arc :: new ( tx2) ] . into ( ) ,
566+ txouts : [ (
567+ outpoint,
568+ TxOut {
569+ value : Amount :: from_sat ( 10000 ) ,
570+ script_pubkey : spk_at_index ( & descriptor, 21 ) ,
571+ } ,
572+ ) ]
573+ . into ( ) ,
574+ anchors : [ ( conf_anchor, txid2) ] . into ( ) ,
575+ last_seen : [ ( txid2, 1755317700 ) ] . into ( ) ,
576+ first_seen : [ ( txid2, 1755317700 ) ] . into ( ) ,
577+ last_evicted : [ ( txid2, 1755317760 ) ] . into ( ) ,
578+ } ;
579+
580+ let keychain_txout_changeset = keychain_txout:: ChangeSet {
581+ last_revealed : [ ( descriptor. descriptor_id ( ) , 14 ) ] . into ( ) ,
582+ spk_cache : [ (
583+ descriptor. descriptor_id ( ) ,
584+ SpkIterator :: new_with_range ( & descriptor, 37 ..=39 ) . collect ( ) ,
585+ ) ]
586+ . into ( ) ,
587+ } ;
588+
589+ let locked_outpoints_changeset = locked_outpoints:: ChangeSet {
590+ outpoints : [ ( outpoint, true ) ] . into ( ) ,
591+ } ;
592+
593+ ChangeSet {
594+ descriptor : None ,
595+ change_descriptor : None ,
596+ network : None ,
597+ local_chain : local_chain_changeset,
598+ tx_graph : tx_graph_changeset,
599+ indexer : keychain_txout_changeset,
600+ locked_outpoints : locked_outpoints_changeset,
601+ }
602+ }
603+
604+ /// Persist changeset async.
605+ #[ allow( clippy:: print_stderr) ]
606+ pub async fn persist_wallet_changeset_async < F , P > ( create_store : F ) -> Result < ( ) , PersistError >
607+ where
608+ F : AsyncFn ( ) -> Result < P , P :: Error > ,
609+ P : AsyncWalletPersister ,
610+ P :: Error : fmt:: Debug + fmt:: Display ,
611+ {
612+ // Create store
613+ let mut store = create_store ( ) . await . expect ( "store should get created" ) ;
614+ let changeset = AsyncWalletPersister :: initialize ( & mut store)
615+ . await
616+ . expect ( "empty changeset should get loaded" ) ;
617+ assert_eq ! ( changeset, ChangeSet :: default ( ) ) ;
618+
619+ let tx1 = create_one_inp_one_out_tx ( hash ! ( "We_are_all_Satoshi" ) , 30_000 ) ;
620+ let tx2 = create_one_inp_one_out_tx ( tx1. compute_txid ( ) , 20_000 ) ;
621+
622+ // Create changeset
623+ let mut changeset = get_changeset ( tx1) ;
624+
625+ AsyncWalletPersister :: persist ( & mut store, & changeset)
626+ . await
627+ . expect ( "changeset should get persisted" ) ;
628+
629+ let changeset_read = AsyncWalletPersister :: initialize ( & mut store)
630+ . await
631+ . expect ( "should load persisted changeset" ) ;
632+
633+ if changeset != changeset_read {
634+ eprintln ! ( "BUG: Changeset read does not match persisted, read: {changeset_read:#?}\n persisted: {changeset:#?}" ) ;
635+ return Err ( PersistError :: Store (
636+ "failed test changeset equality" . to_string ( ) ,
637+ ) ) ;
638+ }
639+
640+ assert_eq ! ( changeset_read, changeset) ;
641+
642+ // Create another changeset
643+ let changeset_2 = get_changeset_two ( tx2) ;
644+
645+ AsyncWalletPersister :: persist ( & mut store, & changeset_2)
646+ . await
647+ . expect ( "changeset should get persisted" ) ;
648+
649+ let changeset_read = AsyncWalletPersister :: initialize ( & mut store)
650+ . await
651+ . expect ( "persister should not fail" ) ;
652+
653+ changeset. merge ( changeset_2) ;
654+
655+ if changeset_read != changeset {
656+ eprintln ! ( "BUG: Changeset read does not match persisted, read: {changeset_read:#?}\n persisted: {changeset:#?}" ) ;
657+ return Err ( PersistError :: Store (
658+ "failed test changeset equality" . to_string ( ) ,
659+ ) ) ;
660+ }
661+
662+ assert_eq ! ( changeset, changeset_read) ;
663+
664+ Ok ( ( ) )
665+ }
0 commit comments