Skip to content

Commit 705d55f

Browse files
committed
Don't fail send_all retaining reserves for 0 channels
Previouly, `OnchainPayment::send_all_to_address` would fail in the `retain_reserves` mode if the maintained reserves were below the dust limit. Most notably this would happen if we had no channels open at all. Here, we fix this by simply falling back to the draining case (not considering reserves) if the anchor reserves are below dust. We also add a unit test that would have caught this regression in the first place.
1 parent 5586b69 commit 705d55f

File tree

2 files changed

+89
-2
lines changed

2 files changed

+89
-2
lines changed

src/wallet/mod.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,14 +355,17 @@ where
355355
let mut locked_wallet = self.inner.lock().unwrap();
356356

357357
// Prepare the tx_builder. We properly check the reserve requirements (again) further down.
358+
const DUST_LIMIT_SATS: u64 = 546;
358359
let tx_builder = match send_amount {
359360
OnchainSendAmount::ExactRetainingReserve { amount_sats, .. } => {
360361
let mut tx_builder = locked_wallet.build_tx();
361362
let amount = Amount::from_sat(amount_sats);
362363
tx_builder.add_recipient(address.script_pubkey(), amount).fee_rate(fee_rate);
363364
tx_builder
364365
},
365-
OnchainSendAmount::AllRetainingReserve { cur_anchor_reserve_sats } => {
366+
OnchainSendAmount::AllRetainingReserve { cur_anchor_reserve_sats }
367+
if cur_anchor_reserve_sats > DUST_LIMIT_SATS =>
368+
{
366369
let change_address_info = locked_wallet.peek_address(KeychainKind::Internal, 0);
367370
let balance = locked_wallet.balance();
368371
let spendable_amount_sats = self
@@ -419,7 +422,8 @@ where
419422
.fee_absolute(estimated_tx_fee);
420423
tx_builder
421424
},
422-
OnchainSendAmount::AllDrainingReserve => {
425+
OnchainSendAmount::AllDrainingReserve
426+
| OnchainSendAmount::AllRetainingReserve { cur_anchor_reserve_sats: _ } => {
423427
let mut tx_builder = locked_wallet.build_tx();
424428
tx_builder.drain_wallet().drain_to(address.script_pubkey()).fee_rate(fee_rate);
425429
tx_builder

tests/integration_tests_rust.rs

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,89 @@ fn onchain_send_receive() {
496496
assert_eq!(node_b_payments.len(), 5);
497497
}
498498

499+
#[test]
500+
fn onchain_send_all_retains_reserve() {
501+
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();
502+
let chain_source = TestChainSource::Esplora(&electrsd);
503+
let (node_a, node_b) = setup_two_nodes(&chain_source, false, true, false);
504+
505+
// Setup nodes
506+
let addr_a = node_a.onchain_payment().new_address().unwrap();
507+
let addr_b = node_b.onchain_payment().new_address().unwrap();
508+
509+
let premine_amount_sat = 1_000_000;
510+
let reserve_amount_sat = 25_000;
511+
let onchain_fee_buffer_sat = 1000;
512+
premine_and_distribute_funds(
513+
&bitcoind.client,
514+
&electrsd.client,
515+
vec![addr_a.clone(), addr_b.clone()],
516+
Amount::from_sat(premine_amount_sat),
517+
);
518+
519+
node_a.sync_wallets().unwrap();
520+
node_b.sync_wallets().unwrap();
521+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
522+
assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, premine_amount_sat);
523+
524+
// Send all over, with 0 reserve as we don't have any channels open.
525+
let txid = node_a.onchain_payment().send_all_to_address(&addr_b, true, None).unwrap();
526+
527+
wait_for_tx(&electrsd.client, txid);
528+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6);
529+
530+
node_a.sync_wallets().unwrap();
531+
node_b.sync_wallets().unwrap();
532+
// Check node a sent all and node b received it
533+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, 0);
534+
assert!(((premine_amount_sat * 2 - onchain_fee_buffer_sat)..=(premine_amount_sat * 2))
535+
.contains(&node_b.list_balances().spendable_onchain_balance_sats));
536+
537+
// Refill to make sure we have enough reserve for the channel open.
538+
let txid = bitcoind
539+
.client
540+
.send_to_address(&addr_a, Amount::from_sat(reserve_amount_sat))
541+
.unwrap()
542+
.0
543+
.parse()
544+
.unwrap();
545+
wait_for_tx(&electrsd.client, txid);
546+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6);
547+
node_a.sync_wallets().unwrap();
548+
node_b.sync_wallets().unwrap();
549+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, reserve_amount_sat);
550+
551+
// Open a channel.
552+
open_channel(&node_b, &node_a, premine_amount_sat, false, &electrsd);
553+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6);
554+
node_a.sync_wallets().unwrap();
555+
node_b.sync_wallets().unwrap();
556+
expect_channel_ready_event!(node_a, node_b.node_id());
557+
expect_channel_ready_event!(node_b, node_a.node_id());
558+
559+
// Check node a sent all and node b received it
560+
assert_eq!(node_a.list_balances().spendable_onchain_balance_sats, 0);
561+
assert!(((premine_amount_sat - reserve_amount_sat - onchain_fee_buffer_sat)
562+
..=premine_amount_sat)
563+
.contains(&node_b.list_balances().spendable_onchain_balance_sats));
564+
565+
// Send all over again, this time ensuring the reserve is accounted for
566+
let txid = node_b.onchain_payment().send_all_to_address(&addr_a, true, None).unwrap();
567+
568+
wait_for_tx(&electrsd.client, txid);
569+
generate_blocks_and_wait(&bitcoind.client, &electrsd.client, 6);
570+
571+
node_a.sync_wallets().unwrap();
572+
node_b.sync_wallets().unwrap();
573+
574+
// Check node b sent all and node a received it
575+
assert_eq!(node_b.list_balances().total_onchain_balance_sats, reserve_amount_sat);
576+
assert_eq!(node_b.list_balances().spendable_onchain_balance_sats, 0);
577+
assert!(((premine_amount_sat - reserve_amount_sat - onchain_fee_buffer_sat)
578+
..=premine_amount_sat)
579+
.contains(&node_a.list_balances().spendable_onchain_balance_sats));
580+
}
581+
499582
#[test]
500583
fn onchain_wallet_recovery() {
501584
let (bitcoind, electrsd) = setup_bitcoind_and_electrsd();

0 commit comments

Comments
 (0)