11use bdk_chain:: {
2- bitcoin:: { hashes:: Hash , Address , Amount , ScriptBuf , WScriptHash } ,
2+ bitcoin:: {
3+ consensus:: encode, hashes:: Hash , secp256k1:: Secp256k1 , transaction:: Version , Address ,
4+ Amount , OutPoint , PrivateKey , Psbt , PublicKey , ScriptBuf , Transaction , TxIn , TxOut ,
5+ WScriptHash ,
6+ } ,
7+ indexer:: keychain_txout:: KeychainTxOutIndex ,
38 local_chain:: LocalChain ,
9+ miniscript:: {
10+ descriptor:: { DescriptorSecretKey , SinglePubKey } ,
11+ Descriptor , DescriptorPublicKey ,
12+ } ,
413 spk_client:: { FullScanRequest , SyncRequest , SyncResponse } ,
514 spk_txout:: SpkTxOutIndex ,
615 Balance , ConfirmationBlockTime , IndexedTxGraph , Indexer , Merge , TxGraph ,
716} ;
817use bdk_electrum:: BdkElectrumClient ;
9- use bdk_testenv:: { anyhow, bitcoincore_rpc:: RpcApi , TestEnv } ;
18+ use bdk_testenv:: { anyhow, bitcoincore_rpc:: RpcApi , utils :: new_tx , TestEnv } ;
1019use core:: time:: Duration ;
11- use std:: collections:: { BTreeSet , HashSet } ;
20+ use std:: collections:: { BTreeSet , HashMap , HashSet } ;
1221use std:: str:: FromStr ;
1322
1423// Batch size for `sync_with_electrum`.
@@ -26,7 +35,7 @@ fn get_balance(
2635 Ok ( balance)
2736}
2837
29- fn sync_with_electrum < I , Spks > (
38+ fn sync_with_spks < I , Spks > (
3039 client : & BdkElectrumClient < electrum_client:: Client > ,
3140 spks : Spks ,
3241 chain : & mut LocalChain ,
@@ -54,6 +63,167 @@ where
5463 Ok ( update)
5564}
5665
66+ fn sync_with_keychain (
67+ client : & BdkElectrumClient < electrum_client:: Client > ,
68+ chain : & mut LocalChain ,
69+ graph : & mut IndexedTxGraph < ConfirmationBlockTime , KeychainTxOutIndex < ( ) > > ,
70+ ) -> anyhow:: Result < SyncResponse > {
71+ use bdk_chain:: keychain_txout:: SyncRequestBuilderExt ;
72+
73+ let update = client. sync (
74+ SyncRequest :: builder ( )
75+ . chain_tip ( chain. tip ( ) )
76+ . revealed_spks_from_indexer ( & graph. index , ..)
77+ . unconfirmed_outpoints (
78+ graph. graph ( ) . canonical_iter ( chain, chain. tip ( ) . block_id ( ) ) ,
79+ & graph. index ,
80+ ) ,
81+ BATCH_SIZE ,
82+ true ,
83+ ) ?;
84+
85+ if let Some ( chain_update) = update. chain_update . clone ( ) {
86+ let _ = chain
87+ . apply_update ( chain_update)
88+ . map_err ( |err| anyhow:: anyhow!( "LocalChain update error: {:?}" , err) ) ?;
89+ }
90+ let _ = graph. apply_update ( update. tx_update . clone ( ) ) ;
91+
92+ Ok ( update)
93+ }
94+
95+ #[ test]
96+ pub fn detect_receive_tx_cancel ( ) -> anyhow:: Result < ( ) > {
97+ let env = TestEnv :: new ( ) ?;
98+ let electrum_client = electrum_client:: Client :: new ( env. electrsd . electrum_url . as_str ( ) ) ?;
99+ let client = BdkElectrumClient :: new ( electrum_client) ;
100+
101+ let secp = Secp256k1 :: new ( ) ;
102+ let ( descriptor, keymap) = Descriptor :: parse_descriptor ( & Secp256k1 :: signing_only ( ) , "tr([73c5da0a/86'/0'/0']xprv9xgqHN7yz9MwCkxsBPN5qetuNdQSUttZNKw1dcYTV4mkaAFiBVGQziHs3NRSWMkCzvgjEe3n9xV8oYywvM8at9yRqyaZVz6TYYhX98VjsUk/0/*)" )
103+ . expect ( "must be valid" ) ;
104+ let receiver_spk = descriptor. at_derivation_index ( 9 ) . unwrap ( ) . script_pubkey ( ) ;
105+
106+ let mut graph = IndexedTxGraph :: < ConfirmationBlockTime , KeychainTxOutIndex < ( ) > > :: new (
107+ KeychainTxOutIndex :: new ( 10 ) ,
108+ ) ;
109+ let _ = graph
110+ . index
111+ . insert_descriptor ( ( ) , descriptor. clone ( ) )
112+ . unwrap ( ) ;
113+ let ( mut chain, _) = LocalChain :: from_genesis_hash ( env. bitcoind . client . get_block_hash ( 0 ) ?) ;
114+
115+ env. mine_blocks ( 101 , None ) ?;
116+
117+ let funding_txid = env. bitcoind . client . send_to_address (
118+ & Address :: from_str ( "bcrt1qfjg5lv3dvc9az8patec8fjddrs4aqtauadnagr" ) ?. assume_checked ( ) ,
119+ Amount :: from_sat ( 10_000 ) ,
120+ None ,
121+ None ,
122+ None ,
123+ None ,
124+ Some ( 1 ) ,
125+ None ,
126+ ) ?;
127+
128+ env. mine_blocks ( 1 , None ) ?;
129+ env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
130+
131+ let funding_tx = env
132+ . rpc_client ( )
133+ . get_transaction ( & funding_txid, None ) ?
134+ . transaction ( ) ?;
135+
136+ let funding_amt = funding_tx
137+ . output
138+ . iter ( )
139+ . map ( |o| o. value . to_sat ( ) )
140+ . sum :: < u64 > ( ) ;
141+
142+ let mut send_psbt = Psbt :: from_unsigned_tx ( Transaction {
143+ input : vec ! [ TxIn {
144+ previous_output: OutPoint :: new( funding_txid, 0 ) ,
145+ ..Default :: default ( )
146+ } ] ,
147+ output : vec ! [
148+ TxOut {
149+ value: Amount :: from_sat( 10_000 ) ,
150+ script_pubkey: receiver_spk. clone( ) ,
151+ } ,
152+ TxOut {
153+ value: Amount :: from_sat( funding_amt - 10_500 ) ,
154+ script_pubkey: funding_tx. output[ 0 ] . script_pubkey. clone( ) ,
155+ } ,
156+ ] ,
157+ version : Version :: TWO ,
158+ ..new_tx ( 1 )
159+ } ) ?;
160+ send_psbt. inputs [ 0 ] . non_witness_utxo = Some ( funding_tx. clone ( ) ) ;
161+
162+ let mut undo_send_psbt = Psbt :: from_unsigned_tx ( Transaction {
163+ input : vec ! [ TxIn {
164+ previous_output: OutPoint :: new( funding_txid, 0 ) ,
165+ ..Default :: default ( )
166+ } ] ,
167+ output : vec ! [
168+ TxOut {
169+ value: Amount :: from_sat( 5_000 ) ,
170+ script_pubkey: receiver_spk,
171+ } ,
172+ TxOut {
173+ value: Amount :: from_sat( funding_amt - 5_500 ) ,
174+ script_pubkey: funding_tx. output[ 0 ] . script_pubkey. clone( ) ,
175+ } ,
176+ ] ,
177+ version : Version :: TWO ,
178+ ..new_tx ( 2 )
179+ } ) ?;
180+ undo_send_psbt. inputs [ 0 ] . non_witness_utxo = Some ( funding_tx. clone ( ) ) ;
181+
182+ let _ = match keymap. iter ( ) . next ( ) . expect ( "keymap not empty" ) {
183+ ( DescriptorPublicKey :: Single ( single_pub) , DescriptorSecretKey :: Single ( prv) ) => {
184+ let pk = match single_pub. key {
185+ SinglePubKey :: FullKey ( pk) => pk,
186+ SinglePubKey :: XOnly ( _) => unimplemented ! ( "single xonly pubkey" ) ,
187+ } ;
188+ let keys: HashMap < PublicKey , PrivateKey > = [ ( pk, prv. key ) ] . into ( ) ;
189+ send_psbt. sign ( & keys, & secp) . unwrap ( ) ;
190+ undo_send_psbt. sign ( & keys, & secp) . unwrap ( ) ;
191+ }
192+ ( _, DescriptorSecretKey :: XPrv ( k) ) => {
193+ send_psbt. sign ( & k. xkey , & secp) . unwrap ( ) ;
194+ undo_send_psbt. sign ( & k. xkey , & secp) . unwrap ( ) ;
195+ }
196+ _ => unimplemented ! ( "multi xkey signer" ) ,
197+ } ;
198+
199+ // Broadcast the send transaction.
200+ let send_txid = env
201+ . rpc_client ( )
202+ . send_raw_transaction ( & encode:: serialize ( & send_psbt. extract_tx ( ) ?) ) ?;
203+
204+ // Broadcast the cancel transaction to create a conflict.
205+ let undo_send_txid = env
206+ . rpc_client ( )
207+ . send_raw_transaction ( & encode:: serialize ( & undo_send_psbt. extract_tx ( ) ?) ) ?;
208+
209+ // Sync and check for conflicts.
210+ let sync_result = sync_with_keychain ( & client, & mut chain, & mut graph) ?;
211+ assert ! ( sync_result
212+ . tx_update
213+ . txs
214+ . iter( )
215+ . any( |tx| tx. compute_txid( ) == send_txid) ) ;
216+ assert ! ( sync_result
217+ . tx_update
218+ . txs
219+ . iter( )
220+ . any( |tx| tx. compute_txid( ) == undo_send_txid) ) ;
221+
222+ println ! ( "Detected transactions: {:?}" , sync_result. tx_update. txs) ;
223+
224+ Ok ( ( ) )
225+ }
226+
57227#[ test]
58228pub fn test_update_tx_graph_without_keychain ( ) -> anyhow:: Result < ( ) > {
59229 let env = TestEnv :: new ( ) ?;
@@ -322,7 +492,7 @@ fn test_sync() -> anyhow::Result<()> {
322492 let txid = env. send ( & addr_to_track, SEND_AMOUNT ) ?;
323493 env. wait_until_electrum_sees_txid ( txid, Duration :: from_secs ( 6 ) ) ?;
324494
325- let _ = sync_with_electrum (
495+ let _ = sync_with_spks (
326496 & client,
327497 [ spk_to_track. clone ( ) ] ,
328498 & mut recv_chain,
@@ -343,7 +513,7 @@ fn test_sync() -> anyhow::Result<()> {
343513 env. mine_blocks ( 1 , None ) ?;
344514 env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
345515
346- let _ = sync_with_electrum (
516+ let _ = sync_with_spks (
347517 & client,
348518 [ spk_to_track. clone ( ) ] ,
349519 & mut recv_chain,
@@ -364,7 +534,7 @@ fn test_sync() -> anyhow::Result<()> {
364534 env. reorg_empty_blocks ( 1 ) ?;
365535 env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
366536
367- let _ = sync_with_electrum (
537+ let _ = sync_with_spks (
368538 & client,
369539 [ spk_to_track. clone ( ) ] ,
370540 & mut recv_chain,
@@ -384,7 +554,7 @@ fn test_sync() -> anyhow::Result<()> {
384554 env. mine_blocks ( 1 , None ) ?;
385555 env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
386556
387- let _ = sync_with_electrum ( & client, [ spk_to_track] , & mut recv_chain, & mut recv_graph) ?;
557+ let _ = sync_with_spks ( & client, [ spk_to_track] , & mut recv_chain, & mut recv_graph) ?;
388558
389559 // Check if balance is correct once transaction is confirmed again.
390560 assert_eq ! (
@@ -470,7 +640,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> {
470640
471641 // Sync up to tip.
472642 env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
473- let update = sync_with_electrum (
643+ let update = sync_with_spks (
474644 & client,
475645 [ spk_to_track. clone ( ) ] ,
476646 & mut recv_chain,
@@ -501,7 +671,7 @@ fn tx_can_become_unconfirmed_after_reorg() -> anyhow::Result<()> {
501671 env. reorg_empty_blocks ( depth) ?;
502672
503673 env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
504- let update = sync_with_electrum (
674+ let update = sync_with_spks (
505675 & client,
506676 [ spk_to_track. clone ( ) ] ,
507677 & mut recv_chain,
@@ -549,7 +719,7 @@ fn test_sync_with_coinbase() -> anyhow::Result<()> {
549719 env. wait_until_electrum_sees_block ( Duration :: from_secs ( 6 ) ) ?;
550720
551721 // Check to see if electrum syncs properly.
552- assert ! ( sync_with_electrum (
722+ assert ! ( sync_with_spks (
553723 & client,
554724 [ spk_to_track. clone( ) ] ,
555725 & mut recv_chain,
0 commit comments