1
1
use std:: { collections:: BTreeMap , str:: FromStr , time:: Duration } ;
2
-
3
2
use anyhow:: anyhow;
4
3
use clap:: ValueEnum ;
5
4
use futures:: { stream:: FuturesUnordered , StreamExt } ;
6
5
use log:: { info, warn} ;
7
- use protocol:: {
8
- bitcoin:: Txid ,
9
- constants:: ChainAnchor ,
10
- hasher:: { KeyHasher , SpaceKey } ,
11
- prepare:: DataSource ,
12
- script:: SpaceScript ,
13
- slabel:: SLabel ,
14
- Space ,
15
- } ;
6
+ use protocol:: { bitcoin:: Txid , constants:: ChainAnchor , hasher:: { KeyHasher , SpaceKey } , prepare:: DataSource , script:: SpaceScript , slabel:: SLabel , FullSpaceOut , Space } ;
16
7
use serde:: { Deserialize , Serialize } ;
17
8
use serde_json:: json;
18
9
use tokio:: {
@@ -34,6 +25,7 @@ use wallet::{
34
25
DoubleUtxo , SpacesWallet , WalletInfo ,
35
26
} ;
36
27
use wallet:: bdk_wallet:: wallet:: tx_builder:: TxOrdering ;
28
+ use wallet:: builder:: SelectionOutput ;
37
29
use crate :: {
38
30
config:: ExtendedNetwork ,
39
31
node:: BlockSource ,
@@ -46,17 +38,18 @@ use crate::{
46
38
47
39
#[ derive( Debug , Clone , Serialize , Deserialize ) ]
48
40
pub struct TxResponse {
41
+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
42
+ pub error : Option < BTreeMap < String , String > > ,
49
43
pub txid : Txid ,
50
44
pub tags : Vec < TransactionTag > ,
51
45
#[ serde( skip_serializing_if = "Option::is_none" ) ]
52
- pub error : Option < BTreeMap < String , String > > ,
46
+ pub raw : Option < String > ,
53
47
}
54
48
49
+
55
50
#[ derive( Debug , Clone , Serialize , Deserialize ) ]
56
51
pub struct WalletResponse {
57
- pub sent : Vec < TxResponse > ,
58
- #[ serde( skip_serializing_if = "Option::is_none" ) ]
59
- pub raw : Option < Vec < String > > ,
52
+ pub result : Vec < TxResponse > ,
60
53
}
61
54
62
55
#[ derive( Debug , Clone , Serialize , Deserialize ) ]
@@ -87,7 +80,7 @@ pub enum WalletCommand {
87
80
ListSpaces {
88
81
resp : crate :: rpc:: Responder < anyhow:: Result < Vec < WalletOutput > > > ,
89
82
} ,
90
- ListAuctionOutputs {
83
+ ListBidouts {
91
84
resp : crate :: rpc:: Responder < anyhow:: Result < Vec < DoubleUtxo > > > ,
92
85
} ,
93
86
ListUnspent {
@@ -182,7 +175,11 @@ impl RpcWallet {
182
175
txid : Txid ,
183
176
fee_rate : FeeRate ,
184
177
) -> anyhow:: Result < Vec < TxResponse > > {
185
- let coin_selection = Self :: get_spaces_coin_selection ( wallet, state) ?;
178
+ let coin_selection = Self :: get_spaces_coin_selection (
179
+ wallet,
180
+ state,
181
+ false /* generally bdk won't use unconfirmed for replacements anyways */
182
+ ) ?;
186
183
let mut builder = wallet. spaces
187
184
. build_fee_bump ( txid) ?
188
185
. coin_selection ( coin_selection) ;
@@ -204,6 +201,7 @@ impl RpcWallet {
204
201
txid: new_txid,
205
202
tags: vec![ TransactionTag :: FeeBump ] ,
206
203
error: None ,
204
+ raw: None ,
207
205
} ] )
208
206
}
209
207
@@ -214,7 +212,8 @@ impl RpcWallet {
214
212
output : OutPoint ,
215
213
fee_rate : FeeRate ,
216
214
) -> anyhow:: Result < TxResponse > {
217
- let coin_selection = Self :: get_spaces_coin_selection ( wallet, state) ?;
215
+ let coin_selection =
216
+ Self :: get_spaces_coin_selection ( wallet, state, true ) ?;
218
217
let addre = wallet. spaces . next_unused_address ( KeychainKind :: External ) ;
219
218
let mut builder = wallet
220
219
. spaces
@@ -239,6 +238,7 @@ impl RpcWallet {
239
238
txid,
240
239
tags : vec ! [ TransactionTag :: ForceSpendTestOnly ] ,
241
240
error : None ,
241
+ raw : None ,
242
242
} )
243
243
}
244
244
@@ -299,8 +299,10 @@ impl RpcWallet {
299
299
}
300
300
}
301
301
}
302
- WalletCommand :: ListAuctionOutputs { resp } => {
303
- let result = wallet. list_auction_outputs ( ) ;
302
+ WalletCommand :: ListBidouts { resp } => {
303
+ let sel =
304
+ Self :: get_spaces_coin_selection ( wallet, state, false ) ?;
305
+ let result = wallet. list_bidouts ( & sel) ;
304
306
_ = resp. send ( result) ;
305
307
}
306
308
WalletCommand :: GetBalance { resp } => {
@@ -422,6 +424,7 @@ impl RpcWallet {
422
424
fn get_spaces_coin_selection (
423
425
wallet : & mut SpacesWallet ,
424
426
state : & mut LiveSnapshot ,
427
+ confirmed_only : bool ,
425
428
) -> anyhow:: Result < SpacesAwareCoinSelection > {
426
429
// Filters out all "space outs" from the selection.
427
430
// Note: This exclusion only applies to confirmed space outs; unconfirmed ones are not excluded.
@@ -430,10 +433,14 @@ impl RpcWallet {
430
433
let excluded = Self :: list_unspent ( wallet, state) ?
431
434
. into_iter ( )
432
435
. filter ( |out| out. is_spaceout )
433
- . map ( |out| out. output . outpoint )
436
+ . map ( |out| SelectionOutput {
437
+ outpoint : out. output . outpoint ,
438
+ is_space : out. space . is_some ( ) ,
439
+ is_spaceout : true ,
440
+ } )
434
441
. collect :: < Vec < _ > > ( ) ;
435
442
436
- Ok ( SpacesAwareCoinSelection :: new ( excluded) )
443
+ Ok ( SpacesAwareCoinSelection :: new ( excluded, confirmed_only ) )
437
444
}
438
445
439
446
fn list_unspent (
@@ -496,6 +503,17 @@ impl RpcWallet {
496
503
) ?) )
497
504
}
498
505
506
+ fn replaces_unconfirmed_bid ( wallet : & SpacesWallet , bid_spaceout : & FullSpaceOut ) -> bool {
507
+ let outpoint = bid_spaceout. outpoint ( ) ;
508
+ wallet. spaces . transactions ( ) . filter (
509
+ |tx|
510
+ !tx. chain_position . is_confirmed ( )
511
+ ) . any (
512
+ |tx|
513
+ tx. tx_node . input . iter ( ) . any ( |input| input. previous_output == outpoint)
514
+ )
515
+ }
516
+
499
517
fn batch_tx (
500
518
network : ExtendedNetwork ,
501
519
source : & BitcoinBlockSource ,
@@ -526,11 +544,13 @@ impl RpcWallet {
526
544
let mut builder = wallet:: builder:: Builder :: new ( ) ;
527
545
builder = builder. fee_rate ( fee_rate) ;
528
546
529
- if tx. auction_outputs . is_some ( ) {
530
- builder = builder. auction_outputs ( tx. auction_outputs . unwrap ( ) ) ;
547
+ if tx. bidouts . is_some ( ) {
548
+ builder = builder. bidouts ( tx. bidouts . unwrap ( ) ) ;
531
549
}
532
550
builder = builder. force ( tx. force ) ;
533
551
552
+ let mut bid_replacement = false ;
553
+
534
554
for req in tx. requests {
535
555
match req {
536
556
RpcWalletRequest :: SendCoins ( params) => {
@@ -603,7 +623,13 @@ impl RpcWallet {
603
623
if spaceout. is_none ( ) {
604
624
return Err ( anyhow ! ( "bid '{}': space does not exist" , params. name) ) ;
605
625
}
606
- builder = builder. add_bid ( spaceout. unwrap ( ) , Amount :: from_sat ( params. amount ) ) ;
626
+
627
+ let spaceout = spaceout. unwrap ( ) ;
628
+ if Self :: replaces_unconfirmed_bid ( wallet, & spaceout) {
629
+ bid_replacement = true ;
630
+ }
631
+
632
+ builder = builder. add_bid ( spaceout, Amount :: from_sat ( params. amount ) ) ;
607
633
}
608
634
RpcWalletRequest :: Register ( params) => {
609
635
let name = SLabel :: from_str ( & params. name ) ?;
@@ -686,13 +712,11 @@ impl RpcWallet {
686
712
}
687
713
688
714
let median_time = source. get_median_time ( ) ?;
689
- let coin_selection = Self :: get_spaces_coin_selection ( wallet, store) ?;
715
+ let coin_selection = Self :: get_spaces_coin_selection ( wallet, store, bid_replacement ) ?;
690
716
691
717
let mut tx_iter = builder. build_iter ( tx. dust , median_time, wallet, coin_selection) ?;
692
718
693
719
let mut result_set = Vec :: new ( ) ;
694
- let mut raw_set = Vec :: new ( ) ;
695
- let mut has_errors = false ;
696
720
while let Some ( tx_result) = tx_iter. next ( ) {
697
721
let tagged = tx_result?;
698
722
@@ -701,27 +725,27 @@ impl RpcWallet {
701
725
txid : tagged. tx . compute_txid ( ) ,
702
726
tags : tagged. tags ,
703
727
error : None ,
728
+ raw : None ,
704
729
} ) ;
705
730
706
731
let raw = bitcoin:: consensus:: encode:: serialize_hex ( & tagged. tx ) ;
707
- raw_set. push ( raw) ;
708
732
let result = source. rpc . broadcast_tx ( & source. client , & tagged. tx ) ;
709
733
match result {
710
734
Ok ( confirmation) => {
711
735
tx_iter. wallet . insert_tx ( tagged. tx , confirmation) ?;
712
736
tx_iter. wallet . commit ( ) ?;
713
737
}
714
738
Err ( e) => {
715
- has_errors = true ;
739
+ result_set. last_mut ( ) . unwrap ( ) . raw = Some ( raw) ;
740
+
716
741
let mut error_data = BTreeMap :: new ( ) ;
717
742
if let BitcoinRpcError :: Rpc ( rpc) = e {
718
743
if is_bid {
719
744
if rpc. message . contains ( "replacement-adds-unconfirmed" ) {
720
745
error_data. insert (
721
746
"hint" . to_string ( ) ,
722
- "If you have don't have confirmed auction outputs, you cannot \
723
- replace bids in the mempool."
724
- . to_string ( ) ,
747
+ "Competing bid in mempool but wallet has no confirmed bid \
748
+ outputs (required to replace mempool bids)". to_string ( ) ,
725
749
) ;
726
750
}
727
751
@@ -750,8 +774,7 @@ impl RpcWallet {
750
774
}
751
775
752
776
Ok ( WalletResponse {
753
- sent : result_set,
754
- raw : if has_errors { Some ( raw_set) } else { None } ,
777
+ result : result_set,
755
778
} )
756
779
}
757
780
@@ -876,10 +899,10 @@ impl RpcWallet {
876
899
resp_rx. await ?
877
900
}
878
901
879
- pub async fn send_list_auction_outputs ( & self ) -> anyhow:: Result < Vec < DoubleUtxo > > {
902
+ pub async fn send_list_bidouts ( & self ) -> anyhow:: Result < Vec < DoubleUtxo > > {
880
903
let ( resp, resp_rx) = oneshot:: channel ( ) ;
881
904
self . sender
882
- . send ( WalletCommand :: ListAuctionOutputs { resp } )
905
+ . send ( WalletCommand :: ListBidouts { resp } )
883
906
. await ?;
884
907
resp_rx. await ?
885
908
}
0 commit comments