1
- use serde_json:: json;
2
1
use std:: cmp;
3
- use std:: collections:: HashMap ;
4
2
use std:: env;
5
3
use std:: fmt;
6
4
use std:: str:: FromStr ;
@@ -10,11 +8,10 @@ use anyhow::bail;
10
8
use anyhow:: Context ;
11
9
use bdk_chain:: bitcoin:: {
12
10
absolute, address:: NetworkUnchecked , bip32, consensus, constants, hex:: DisplayHex , relative,
13
- secp256k1:: Secp256k1 , transaction, Address , Amount , Network , NetworkKind , PrivateKey , Psbt ,
14
- PublicKey , Sequence , Transaction , TxIn , TxOut ,
11
+ secp256k1:: Secp256k1 , Address , Amount , Network , NetworkKind , Psbt , Transaction , TxOut , Txid ,
15
12
} ;
16
13
use bdk_chain:: miniscript:: {
17
- descriptor:: { DescriptorSecretKey , SinglePubKey } ,
14
+ descriptor:: DefiniteDescriptorKey ,
18
15
plan:: { Assets , Plan } ,
19
16
psbt:: PsbtExt ,
20
17
Descriptor , DescriptorPublicKey , ForEachKey ,
@@ -27,12 +24,14 @@ use bdk_chain::{
27
24
tx_graph, ChainOracle , DescriptorExt , FullTxOut , IndexedTxGraph , Merge ,
28
25
} ;
29
26
use bdk_coin_select:: {
30
- metrics:: LowestFee , Candidate , ChangePolicy , CoinSelector , DrainWeights , FeeRate , Target ,
31
- TargetFee , TargetOutputs ,
27
+ metrics, Candidate , ChangePolicy , CoinSelector , DrainWeights , FeeRate , Target , TargetFee ,
28
+ TargetOutputs ,
32
29
} ;
33
30
use bdk_file_store:: Store ;
31
+ use bdk_tx:: { self , DataProvider , Signer } ;
34
32
use clap:: { Parser , Subcommand } ;
35
33
use rand:: prelude:: * ;
34
+ use serde_json:: json;
36
35
37
36
pub use anyhow;
38
37
pub use clap;
@@ -263,21 +262,19 @@ pub fn create_tx<O: ChainOracle>(
263
262
graph : & mut KeychainTxGraph ,
264
263
chain : & O ,
265
264
assets : & Assets ,
266
- cs_algorithm : CoinSelectionAlgo ,
265
+ coin_selection : CoinSelectionAlgo ,
267
266
address : Address ,
268
267
value : u64 ,
269
268
feerate : f32 ,
270
269
) -> anyhow:: Result < ( Psbt , Option < ChangeInfo > ) >
271
270
where
272
271
O :: Error : std:: error:: Error + Send + Sync + ' static ,
273
272
{
274
- let mut changeset = keychain_txout:: ChangeSet :: default ( ) ;
275
-
276
273
// get planned utxos
277
274
let mut plan_utxos = planned_utxos ( graph, chain, assets) ?;
278
275
279
- // sort utxos if cs-algo requires it
280
- match cs_algorithm {
276
+ // sort utxos if coin selection requires it
277
+ match coin_selection {
281
278
CoinSelectionAlgo :: LargestFirst => {
282
279
plan_utxos. sort_by_key ( |( _, utxo) | cmp:: Reverse ( utxo. txout . value ) )
283
280
}
@@ -301,120 +298,109 @@ where
301
298
} )
302
299
. collect ( ) ;
303
300
304
- // create recipient output(s)
305
- let mut outputs = vec ! [ TxOut {
306
- value: Amount :: from_sat( value) ,
307
- script_pubkey: address. script_pubkey( ) ,
308
- } ] ;
301
+ // if this is a segwit spend, don't bother setting the input `non_witness_utxo`
302
+ let only_witness_utxo = candidates. iter ( ) . all ( |c| c. is_segwit ) ;
309
303
310
304
let ( change_keychain, _) = graph
311
305
. index
312
306
. keychains ( )
313
307
. last ( )
314
308
. expect ( "must have a keychain" ) ;
315
309
316
- let ( ( change_index, change_script) , index_changeset) = graph
310
+ // get drain script
311
+ let ( ( drain_index, drain_spk) , index_changeset) = graph
317
312
. index
318
313
. next_unused_spk ( change_keychain)
319
314
. expect ( "Must exist" ) ;
320
- changeset. merge ( index_changeset) ;
321
-
322
- let mut change_output = TxOut {
323
- value : Amount :: ZERO ,
324
- script_pubkey : change_script,
325
- } ;
326
315
327
316
let change_desc = graph
328
317
. index
329
318
. keychains ( )
330
319
. find ( |( k, _) | k == & change_keychain)
331
320
. expect ( "must exist" )
332
321
. 1 ;
322
+ let min_value = 3 * change_desc. dust_value ( ) . to_sat ( ) ;
323
+
324
+ let change_policy = ChangePolicy {
325
+ min_value,
326
+ drain_weights : DrainWeights :: TR_KEYSPEND ,
327
+ } ;
328
+
329
+ let mut builder = bdk_tx:: Builder :: new ( ) ;
333
330
334
- let min_drain_value = change_desc. dust_value ( ) . to_sat ( ) ;
331
+ let locktime = assets
332
+ . absolute_timelock
333
+ . unwrap_or ( absolute:: LockTime :: from_consensus (
334
+ chain. get_chain_tip ( ) ?. height ,
335
+ ) ) ;
336
+ builder. locktime ( locktime) ;
335
337
338
+ // fund outputs
339
+ builder. add_outputs ( [ ( address. script_pubkey ( ) , Amount :: from_sat ( value) ) ] ) ;
336
340
let target = Target {
337
341
outputs : TargetOutputs :: fund_outputs (
338
- outputs
339
- . iter ( )
340
- . map ( |output | ( output . weight ( ) . to_wu ( ) , output . value . to_sat ( ) ) ) ,
342
+ builder
343
+ . target_outputs ( )
344
+ . map ( |( w , v ) | ( w . to_wu ( ) , v . to_sat ( ) ) ) ,
341
345
) ,
342
346
fee : TargetFee {
343
347
rate : FeeRate :: from_sat_per_vb ( feerate) ,
344
348
..Default :: default ( )
345
349
} ,
346
350
} ;
347
351
348
- let change_policy = ChangePolicy {
349
- min_value : min_drain_value,
350
- drain_weights : DrainWeights :: TR_KEYSPEND ,
351
- } ;
352
-
353
- // run coin selection
352
+ // select coins
354
353
let mut selector = CoinSelector :: new ( & candidates) ;
355
- match cs_algorithm {
356
- CoinSelectionAlgo :: BranchAndBound => {
357
- let metric = LowestFee {
358
- target,
359
- long_term_feerate : FeeRate :: from_sat_per_vb ( 10.0 ) ,
360
- change_policy,
361
- } ;
362
- match selector. run_bnb ( metric, 10_000 ) {
363
- Ok ( _) => { }
364
- Err ( _) => selector
365
- . select_until_target_met ( target)
366
- . context ( "selecting coins" ) ?,
367
- }
354
+ if let CoinSelectionAlgo :: BranchAndBound = coin_selection {
355
+ let metric = metrics:: Changeless {
356
+ target,
357
+ change_policy,
358
+ } ;
359
+ if selector. run_bnb ( metric, 10_000 ) . is_err ( ) {
360
+ selector. select_until_target_met ( target) ?;
368
361
}
369
- _ => selector
370
- . select_until_target_met ( target)
371
- . context ( "selecting coins" ) ?,
362
+ } else {
363
+ selector. select_until_target_met ( target) ?;
372
364
}
373
365
374
- // get the selected plan utxos
375
- let selected: Vec < _ > = selector. apply_selection ( & plan_utxos) . collect ( ) ;
366
+ // apply selection
367
+ let selection = selector
368
+ . apply_selection ( & plan_utxos)
369
+ . cloned ( )
370
+ . map ( |( plan, txo) | bdk_tx:: PlanUtxo {
371
+ plan,
372
+ outpoint : txo. outpoint ,
373
+ txout : txo. txout ,
374
+ } ) ;
375
+ builder. add_inputs ( selection) ;
376
376
377
- // if the selection tells us to use change and the change value is sufficient, we add it as an output
378
- let mut change_info = Option :: < ChangeInfo > :: None ;
377
+ // if the selection tells us to use change, we add it as an output
378
+ // and fill in the change_info.
379
+ let mut change_info = None ;
379
380
let drain = selector. drain ( target, change_policy) ;
380
- if drain. value > min_drain_value {
381
- change_output. value = Amount :: from_sat ( drain. value ) ;
382
- outputs. push ( change_output) ;
381
+ if drain. is_some ( ) {
382
+ builder. add_change_output ( drain_spk, Amount :: from_sat ( drain. value ) ) ;
383
383
change_info = Some ( ChangeInfo {
384
384
change_keychain,
385
- indexer : changeset ,
386
- index : change_index ,
385
+ indexer : index_changeset ,
386
+ index : drain_index ,
387
387
} ) ;
388
- outputs. shuffle ( & mut thread_rng ( ) ) ;
389
388
}
390
389
391
- let unsigned_tx = Transaction {
392
- version : transaction:: Version :: TWO ,
393
- lock_time : assets
394
- . absolute_timelock
395
- . unwrap_or ( absolute:: LockTime :: from_height (
396
- chain. get_chain_tip ( ) ?. height ,
397
- ) ?) ,
398
- input : selected
399
- . iter ( )
400
- . map ( |( plan, utxo) | TxIn {
401
- previous_output : utxo. outpoint ,
402
- sequence : plan
403
- . relative_timelock
404
- . map_or ( Sequence :: ENABLE_RBF_NO_LOCKTIME , Sequence :: from) ,
405
- ..Default :: default ( )
406
- } )
407
- . collect ( ) ,
408
- output : outputs,
390
+ // build psbt
391
+ let mut provider = Provider {
392
+ graph,
393
+ rng : & mut thread_rng ( ) ,
409
394
} ;
410
395
411
- // update psbt with plan
412
- let mut psbt = Psbt :: from_unsigned_tx ( unsigned_tx) ?;
413
- for ( i, ( plan, utxo) ) in selected. iter ( ) . enumerate ( ) {
414
- let psbt_input = & mut psbt. inputs [ i] ;
415
- plan. update_psbt_input ( psbt_input) ;
416
- psbt_input. witness_utxo = Some ( utxo. txout . clone ( ) ) ;
417
- }
396
+ let mut updater = builder. build_psbt ( & mut provider) ?;
397
+ let opt = bdk_tx:: UpdateOptions {
398
+ only_witness_utxo,
399
+ update_with_descriptor : true ,
400
+ ..Default :: default ( )
401
+ } ;
402
+ updater. update_psbt ( & provider, opt) ?;
403
+ let ( psbt, _) = updater. into_finalizer ( ) ;
418
404
419
405
Ok ( ( psbt, change_info) )
420
406
}
@@ -684,26 +670,11 @@ pub fn handle_commands<CS: clap::Subcommand, S: clap::Args>(
684
670
let secp = Secp256k1 :: new ( ) ;
685
671
let ( _, keymap) = Descriptor :: parse_descriptor ( & secp, & desc_str) ?;
686
672
if keymap. is_empty ( ) {
687
- bail ! ( "unable to sign" )
673
+ bail ! ( "unable to sign" ) ;
688
674
}
689
-
690
- // note: we're only looking at the first entry in the keymap
691
- // the idea is to find something that impls `GetKey`
692
- let sign_res = match keymap. iter ( ) . next ( ) . expect ( "not empty" ) {
693
- ( DescriptorPublicKey :: Single ( single_pub) , DescriptorSecretKey :: Single ( prv) ) => {
694
- let pk = match single_pub. key {
695
- SinglePubKey :: FullKey ( pk) => pk,
696
- SinglePubKey :: XOnly ( _) => unimplemented ! ( "single xonly pubkey" ) ,
697
- } ;
698
- let keys: HashMap < PublicKey , PrivateKey > = [ ( pk, prv. key ) ] . into ( ) ;
699
- psbt. sign ( & keys, & secp)
700
- }
701
- ( _, DescriptorSecretKey :: XPrv ( k) ) => psbt. sign ( & k. xkey , & secp) ,
702
- _ => unimplemented ! ( "multi xkey signer" ) ,
703
- } ;
704
-
705
- let _ = sign_res
706
- . map_err ( |errors| anyhow:: anyhow!( "failed to sign PSBT {:?}" , errors) ) ?;
675
+ let _ = psbt
676
+ . sign ( & Signer ( keymap) , & secp)
677
+ . map_err ( |( _, errors) | anyhow:: anyhow!( "failed to sign PSBT {:?}" , errors) ) ?;
707
678
708
679
let mut obj = serde_json:: Map :: new ( ) ;
709
680
obj. insert ( "psbt" . to_string ( ) , json ! ( psbt. to_string( ) ) ) ;
@@ -776,6 +747,36 @@ pub fn handle_commands<CS: clap::Subcommand, S: clap::Args>(
776
747
}
777
748
}
778
749
750
+ /// Helper struct for providing transaction data to tx builder
751
+ struct Provider < ' a > {
752
+ graph : & ' a KeychainTxGraph ,
753
+ rng : & ' a mut dyn RngCore ,
754
+ }
755
+
756
+ impl DataProvider for Provider < ' _ > {
757
+ fn get_tx ( & self , txid : Txid ) -> Option < Transaction > {
758
+ self . graph
759
+ . graph ( )
760
+ . get_tx ( txid)
761
+ . map ( |tx| tx. as_ref ( ) . clone ( ) )
762
+ }
763
+
764
+ fn get_descriptor_for_txout ( & self , txout : & TxOut ) -> Option < Descriptor < DefiniteDescriptorKey > > {
765
+ let & ( keychain, index) = self . graph . index . index_of_spk ( txout. script_pubkey . clone ( ) ) ?;
766
+ let desc = self
767
+ . graph
768
+ . index
769
+ . get_descriptor ( keychain)
770
+ . expect ( "must have descriptor" ) ;
771
+ desc. at_derivation_index ( index) . ok ( )
772
+ }
773
+
774
+ fn sort_transaction ( & mut self , tx : & mut Transaction ) {
775
+ tx. input . shuffle ( self . rng ) ;
776
+ tx. output . shuffle ( self . rng ) ;
777
+ }
778
+ }
779
+
779
780
/// The initial state returned by [`init_or_load`].
780
781
pub struct Init < CS : clap:: Subcommand , S : clap:: Args > {
781
782
/// CLI args
0 commit comments