@@ -6,7 +6,7 @@ use bdk_chain::{BlockId, CanonicalizationParams, ConfirmationBlockTime};
66use bdk_wallet:: coin_selection;
77use bdk_wallet:: descriptor:: { calc_checksum, DescriptorError } ;
88use bdk_wallet:: error:: CreateTxError ;
9- use bdk_wallet:: psbt:: PsbtUtils ;
9+ use bdk_wallet:: psbt:: { self , PsbtUtils } ;
1010use bdk_wallet:: signer:: { SignOptions , SignerError } ;
1111use bdk_wallet:: test_utils:: * ;
1212use bdk_wallet:: KeychainKind ;
@@ -25,6 +25,87 @@ use rand::SeedableRng;
2525
2626mod common;
2727
28+ // Test we can select and spend an indexed but not-yet-canonical utxo
29+ #[ test]
30+ fn test_spend_non_canonical_txout ( ) -> anyhow:: Result < ( ) > {
31+ let ( desc, change_desc) = get_test_wpkh_and_change_desc ( ) ;
32+ let mut wallet = Wallet :: create ( desc, change_desc)
33+ . network ( Network :: Regtest )
34+ . create_wallet_no_persist ( )
35+ . unwrap ( ) ;
36+
37+ let recip = ScriptBuf :: from_hex ( "0014446906a6560d8ad760db3156706e72e171f3a2aa" ) . unwrap ( ) ;
38+
39+ // Receive tx0 (coinbase)
40+ let tx = Transaction {
41+ input : vec ! [ TxIn :: default ( ) ] ,
42+ output : vec ! [ TxOut {
43+ value: Amount :: ONE_BTC ,
44+ script_pubkey: wallet
45+ . reveal_next_address( KeychainKind :: External )
46+ . script_pubkey( ) ,
47+ } ] ,
48+ ..new_tx ( 1 )
49+ } ;
50+ let block = BlockId {
51+ height : 100 ,
52+ hash : Hash :: hash ( b"100" ) ,
53+ } ;
54+ insert_tx_anchor ( & mut wallet, tx, block) ;
55+ let block = BlockId {
56+ height : 1000 ,
57+ hash : Hash :: hash ( b"1000" ) ,
58+ } ;
59+ insert_checkpoint ( & mut wallet, block) ;
60+
61+ // Create tx1
62+ let mut params = psbt:: PsbtParams :: default ( ) ;
63+ params. add_recipients ( [ ( recip. clone ( ) , Amount :: from_btc ( 0.01 ) ?) ] ) ;
64+ let psbt = wallet. create_psbt ( params) ?. 0 ;
65+ let txid = psbt. unsigned_tx . compute_txid ( ) ;
66+ let ( vout, _) = psbt
67+ . unsigned_tx
68+ . output
69+ . iter ( )
70+ . enumerate ( )
71+ . find ( |( _, txo) | wallet. is_mine ( txo. script_pubkey . clone ( ) ) )
72+ . unwrap ( ) ;
73+ let to_select_op = OutPoint :: new ( txid, vout as u32 ) ;
74+
75+ let txid1 = psbt. unsigned_tx . compute_txid ( ) ;
76+ wallet. insert_tx ( psbt. unsigned_tx ) ;
77+
78+ // Create tx2, spending the change of tx1
79+ let mut params = psbt:: PsbtParams :: default ( ) ;
80+ params
81+ . add_utxos ( & [ to_select_op] )
82+ . feerate ( FeeRate :: from_sat_per_vb_unchecked ( 10 ) )
83+ . add_recipients ( [ ( recip, Amount :: from_btc ( 0.01 ) ?) ] ) ;
84+
85+ let psbt = wallet. create_psbt ( params) ?. 0 ;
86+
87+ assert_eq ! ( psbt. unsigned_tx. input. len( ) , 1 ) ;
88+ assert_eq ! ( psbt. unsigned_tx. input[ 0 ] . previous_output, to_select_op) ;
89+
90+ let txid2 = psbt. unsigned_tx . compute_txid ( ) ;
91+ wallet. insert_tx ( psbt. unsigned_tx ) ;
92+
93+ // Query the set of unbroadcasted txs
94+ let txs = wallet
95+ . transactions_with_params ( CanonicalizationParams {
96+ assume_canonical : vec ! [ txid2] ,
97+ } )
98+ . filter ( |c| c. chain_position . is_unconfirmed ( ) )
99+ . collect :: < Vec < _ > > ( ) ;
100+
101+ assert_eq ! ( txs. len( ) , 2 ) ;
102+
103+ assert ! ( txs. iter( ) . any( |c| c. tx_node. txid == txid1) ) ;
104+ assert ! ( txs. iter( ) . any( |c| c. tx_node. txid == txid2) ) ;
105+
106+ Ok ( ( ) )
107+ }
108+
28109#[ test]
29110fn test_error_external_and_internal_are_the_same ( ) {
30111 // identical descriptors should fail to create wallet
0 commit comments