Skip to content

Commit 387cc2d

Browse files
authored
Merge pull request #540 from tnull/2025-05-fix-sendall-retaining-reserve-for-dust
Don't fail `send_all` retaining reserves for 0 channels
2 parents 9151340 + 79eebfe commit 387cc2d

File tree

3 files changed

+96
-3
lines changed

3 files changed

+96
-3
lines changed

src/event.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1122,8 +1122,10 @@ where
11221122
if spendable_amount_sats < required_amount_sats {
11231123
log_error!(
11241124
self.logger,
1125-
"Rejecting inbound Anchor channel from peer {} due to insufficient available on-chain reserves.",
1125+
"Rejecting inbound Anchor channel from peer {} due to insufficient available on-chain reserves. Available: {}/{}sats",
11261126
counterparty_node_id,
1127+
spendable_amount_sats,
1128+
required_amount_sats,
11271129
);
11281130
self.channel_manager
11291131
.force_close_without_broadcasting_txn(

src/wallet/mod.rs

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

358358
// Prepare the tx_builder. We properly check the reserve requirements (again) further down.
359+
const DUST_LIMIT_SATS: u64 = 546;
359360
let tx_builder = match send_amount {
360361
OnchainSendAmount::ExactRetainingReserve { amount_sats, .. } => {
361362
let mut tx_builder = locked_wallet.build_tx();
362363
let amount = Amount::from_sat(amount_sats);
363364
tx_builder.add_recipient(address.script_pubkey(), amount).fee_rate(fee_rate);
364365
tx_builder
365366
},
366-
OnchainSendAmount::AllRetainingReserve { cur_anchor_reserve_sats } => {
367+
OnchainSendAmount::AllRetainingReserve { cur_anchor_reserve_sats }
368+
if cur_anchor_reserve_sats > DUST_LIMIT_SATS =>
369+
{
367370
let change_address_info = locked_wallet.peek_address(KeychainKind::Internal, 0);
368371
let balance = locked_wallet.balance();
369372
let spendable_amount_sats = self
@@ -401,6 +404,10 @@ where
401404
);
402405
e
403406
})?;
407+
408+
// 'cancel' the transaction to free up any used change addresses
409+
locked_wallet.cancel_tx(&tmp_tx);
410+
404411
let estimated_spendable_amount = Amount::from_sat(
405412
spendable_amount_sats.saturating_sub(estimated_tx_fee.to_sat()),
406413
);
@@ -420,7 +427,8 @@ where
420427
.fee_absolute(estimated_tx_fee);
421428
tx_builder
422429
},
423-
OnchainSendAmount::AllDrainingReserve => {
430+
OnchainSendAmount::AllDrainingReserve
431+
| OnchainSendAmount::AllRetainingReserve { cur_anchor_reserve_sats: _ } => {
424432
let mut tx_builder = locked_wallet.build_tx();
425433
tx_builder.drain_wallet().drain_to(address.script_pubkey()).fee_rate(fee_rate);
426434
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)