Skip to content

Commit 5bb177b

Browse files
committed
XXX(breaks more tests): Make Channel's block connection API more electrum-friendly
Electrum clients primarily operate in a world where they query (and subscribe to notifications for) transactions by script_pubkeys. They may never learn very much about the actual blockchain and orient their events around individual transactions, not the blockchain. This makes our ChannelManager interface somewhat more amenable to such a client by splitting `block_connected` into `transactions_confirmed` and `update_best_block`. The first handles checking the funding transaction and storing its height/confirmation block, whereas the second handles funding_locked and reorg logic. Sadly, this interface is somewhat easy to misuse - XXX something about how you can miss a disconnect that un-confirms our funding transaction! This commit also corrects our "funding was reorganized out of the best chain" heuristic, instead of a flat 6 blocks, it uses half the confirmation count required as the point at which we force-close. XXX: for low confirmation counts (also on main), this may cause spurious closes in case of an ill-timed one-block reorg
1 parent 8642de5 commit 5bb177b

File tree

2 files changed

+80
-62
lines changed

2 files changed

+80
-62
lines changed

lightning/src/ln/channel.rs

Lines changed: 78 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -361,13 +361,10 @@ pub(super) struct Channel<Signer: Sign> {
361361

362362
last_sent_closing_fee: Option<(u32, u64, Signature)>, // (feerate, fee, holder_sig)
363363

364-
/// The hash of the block in which the funding transaction reached our CONF_TARGET. We use this
365-
/// to detect unconfirmation after a serialize-unserialize roundtrip where we may not see a full
366-
/// series of block_connected/block_disconnected calls. Obviously this is not a guarantee as we
367-
/// could miss the funding_tx_confirmed_in block as well, but it serves as a useful fallback.
364+
/// The hash of the block in which the funding transaction was included.
368365
funding_tx_confirmed_in: Option<BlockHash>,
366+
funding_tx_confirmation_height: u64,
369367
short_channel_id: Option<u64>,
370-
funding_tx_confirmations: u64,
371368

372369
counterparty_dust_limit_satoshis: u64,
373370
#[cfg(test)]
@@ -424,10 +421,6 @@ struct CommitmentTxInfoCached {
424421
}
425422

426423
pub const OUR_MAX_HTLCS: u16 = 50; //TODO
427-
/// Confirmation count threshold at which we close a channel. Ideally we'd keep the channel around
428-
/// on ice until the funding transaction gets more confirmations, but the LN protocol doesn't
429-
/// really allow for this, so instead we're stuck closing it out at that point.
430-
const UNCONF_THRESHOLD: u32 = 6;
431424
const SPENDING_INPUT_FOR_A_OUTPUT_WEIGHT: u64 = 79; // prevout: 36, nSequence: 4, script len: 1, witness lengths: (3+1)/4, sig: 73/4, if-selector: 1, redeemScript: (6 ops + 2*33 pubkeys + 1*2 delay)/4
432425
const B_OUTPUT_PLUS_SPENDING_INPUT_WEIGHT: u64 = 104; // prevout: 40, nSequence: 4, script len: 1, witness lengths: 3/4, sig: 73/4, pubkey: 33/4, output: 31 (TODO: Wrong? Useless?)
433426

@@ -565,7 +558,7 @@ impl<Signer: Sign> Channel<Signer> {
565558

566559
funding_tx_confirmed_in: None,
567560
short_channel_id: None,
568-
funding_tx_confirmations: 0,
561+
funding_tx_confirmation_height: 0,
569562

570563
feerate_per_kw: feerate,
571564
counterparty_dust_limit_satoshis: 0,
@@ -800,7 +793,7 @@ impl<Signer: Sign> Channel<Signer> {
800793

801794
funding_tx_confirmed_in: None,
802795
short_channel_id: None,
803-
funding_tx_confirmations: 0,
796+
funding_tx_confirmation_height: 0,
804797

805798
feerate_per_kw: msg.feerate_per_kw,
806799
channel_value_satoshis: msg.funding_satoshis,
@@ -3484,38 +3477,7 @@ impl<Signer: Sign> Channel<Signer> {
34843477
self.network_sync == UpdateStatus::DisabledMarked
34853478
}
34863479

3487-
/// When we receive a new block, we (a) check whether the block contains the funding
3488-
/// transaction (which would start us counting blocks until we send the funding_signed), and
3489-
/// (b) check the height of the block against outbound holding cell HTLCs in case we need to
3490-
/// give up on them prematurely and time them out. Everything else (e.g. commitment
3491-
/// transaction broadcasts, channel closure detection, HTLC transaction broadcasting, etc) is
3492-
/// handled by the ChannelMonitor.
3493-
///
3494-
/// If we return Err, the channel may have been closed, at which point the standard
3495-
/// requirements apply - no calls may be made except those explicitly stated to be allowed
3496-
/// post-shutdown.
3497-
/// Only returns an ErrorAction of DisconnectPeer, if Err.
3498-
///
3499-
/// May return some HTLCs (and their payment_hash) which have timed out and should be failed
3500-
/// back.
3501-
pub fn block_connected(&mut self, header: &BlockHeader, txdata: &TransactionData, height: u32) -> Result<(Option<msgs::FundingLocked>, Vec<(HTLCSource, PaymentHash)>), msgs::ErrorMessage> {
3502-
let mut timed_out_htlcs = Vec::new();
3503-
self.holding_cell_htlc_updates.retain(|htlc_update| {
3504-
match htlc_update {
3505-
&HTLCUpdateAwaitingACK::AddHTLC { ref payment_hash, ref source, ref cltv_expiry, .. } => {
3506-
if *cltv_expiry <= height + HTLC_FAIL_BACK_BUFFER {
3507-
timed_out_htlcs.push((source.clone(), payment_hash.clone()));
3508-
false
3509-
} else { true }
3510-
},
3511-
_ => true
3512-
}
3513-
});
3514-
3515-
if self.funding_tx_confirmations > 0 {
3516-
self.funding_tx_confirmations += 1;
3517-
}
3518-
3480+
pub fn transactions_confirmed(&mut self, block_hash: &BlockHash, height: u32, txdata: &TransactionData) -> Result<(), msgs::ErrorMessage> {
35193481
let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
35203482
if non_shutdown_state & !(ChannelState::TheirFundingLocked as u32) == ChannelState::FundingSent as u32 {
35213483
for &(index_in_block, tx) in txdata.iter() {
@@ -3554,18 +3516,61 @@ impl<Signer: Sign> Channel<Signer> {
35543516
panic!("Block was bogus - either height 16 million or had > 16 million transactions");
35553517
}
35563518
assert!(txo_idx <= 0xffff); // txo_idx is a (u16 as usize), so this is just listed here for completeness
3557-
self.funding_tx_confirmations = 1;
3519+
self.funding_tx_confirmation_height = height as u64;
3520+
self.funding_tx_confirmed_in = Some(*block_hash);
35583521
self.short_channel_id = Some(((height as u64) << (5*8)) |
35593522
((index_in_block as u64) << (2*8)) |
35603523
((txo_idx as u64) << (0*8)));
35613524
}
35623525
}
35633526
}
35643527
}
3528+
Ok(())
3529+
}
3530+
3531+
/// When a new block is connected, we check the height of the block against outbound holding
3532+
/// cell HTLCs in case we need to give up on them prematurely and time them out. Everything
3533+
/// else (e.g. commitment transaction broadcasts, channel closure detection, HTLC transaction
3534+
/// broadcasting, etc) is handled by the ChannelMonitor.
3535+
///
3536+
/// If we return Err, the channel may have been closed, at which point the standard
3537+
/// requirements apply - no calls may be made except those explicitly stated to be allowed
3538+
/// post-shutdown.
3539+
///
3540+
/// May return some HTLCs (and their payment_hash) which have timed out and should be failed
3541+
/// back.
3542+
pub fn update_best_block(&mut self, height: u32, highest_header_time: u32) -> Result<(Option<msgs::FundingLocked>, Vec<(HTLCSource, PaymentHash)>), msgs::ErrorMessage> {
3543+
let mut timed_out_htlcs = Vec::new();
3544+
self.holding_cell_htlc_updates.retain(|htlc_update| {
3545+
match htlc_update {
3546+
&HTLCUpdateAwaitingACK::AddHTLC { ref payment_hash, ref source, ref cltv_expiry, .. } => {
3547+
if *cltv_expiry <= height + HTLC_FAIL_BACK_BUFFER {
3548+
timed_out_htlcs.push((source.clone(), payment_hash.clone()));
3549+
false
3550+
} else { true }
3551+
},
3552+
_ => true
3553+
}
3554+
});
3555+
3556+
self.update_time_counter = cmp::max(self.update_time_counter, highest_header_time);
3557+
if self.funding_tx_confirmation_height > 0 {
3558+
let funding_tx_confirmations = height as i64 - self.funding_tx_confirmation_height as i64 + 1;
3559+
if funding_tx_confirmations <= 0 {
3560+
self.funding_tx_confirmation_height = 0;
3561+
}
35653562

3566-
self.update_time_counter = cmp::max(self.update_time_counter, header.time);
3567-
if self.funding_tx_confirmations > 0 {
3568-
if self.funding_tx_confirmations == self.minimum_depth as u64 {
3563+
let non_shutdown_state = self.channel_state & (!MULTI_STATE_FLAGS);
3564+
if (non_shutdown_state >= ChannelState::ChannelFunded as u32 ||
3565+
(non_shutdown_state & ChannelState::OurFundingLocked as u32) == ChannelState::OurFundingLocked as u32) &&
3566+
funding_tx_confirmations < self.minimum_depth as i64 / 2 {
3567+
return Err(msgs::ErrorMessage {
3568+
channel_id: self.channel_id(),
3569+
data: format!("Funding transaction was un-confirmed. Locked at {} confs, now have {} confs.", self.minimum_depth, funding_tx_confirmations),
3570+
});
3571+
}
3572+
3573+
if funding_tx_confirmations == self.minimum_depth as i64 {
35693574
let need_commitment_update = if non_shutdown_state == ChannelState::FundingSent as u32 {
35703575
self.channel_state |= ChannelState::OurFundingLocked as u32;
35713576
true
@@ -3584,7 +3589,6 @@ impl<Signer: Sign> Channel<Signer> {
35843589
// funding_tx_confirmed_in and return.
35853590
false
35863591
};
3587-
self.funding_tx_confirmed_in = Some(header.block_hash());
35883592

35893593
//TODO: Note that this must be a duplicate of the previous commitment point they sent us,
35903594
//as otherwise we will have a commitment transaction that they can't revoke (well, kinda,
@@ -3604,21 +3608,35 @@ impl<Signer: Sign> Channel<Signer> {
36043608
}
36053609
}
36063610
}
3611+
36073612
Ok((None, timed_out_htlcs))
36083613
}
36093614

3615+
/// When we receive a new block, we (a) check whether the block contains the funding
3616+
/// transaction (which would start us counting blocks until we send the funding_signed), and
3617+
/// (b) check the height of the block against outbound holding cell HTLCs in case we need to
3618+
/// give up on them prematurely and time them out. Everything else (e.g. commitment
3619+
/// transaction broadcasts, channel closure detection, HTLC transaction broadcasting, etc) is
3620+
/// handled by the ChannelMonitor.
3621+
///
3622+
/// If we return Err, the channel may have been closed, at which point the standard
3623+
/// requirements apply - no calls may be made except those explicitly stated to be allowed
3624+
/// post-shutdown.
3625+
/// Only returns an ErrorAction of DisconnectPeer, if Err.
3626+
///
3627+
/// May return some HTLCs (and their payment_hash) which have timed out and should be failed
3628+
/// back.
3629+
pub fn block_connected(&mut self, header: &BlockHeader, txdata: &TransactionData, height: u32) -> Result<(Option<msgs::FundingLocked>, Vec<(HTLCSource, PaymentHash)>), msgs::ErrorMessage> {
3630+
self.transactions_confirmed(&header.block_hash(), height, txdata)?;
3631+
self.update_best_block(height, header.time)
3632+
}
3633+
36103634
/// Called by channelmanager based on chain blocks being disconnected.
36113635
/// Returns true if we need to close the channel now due to funding transaction
36123636
/// unconfirmation/reorg.
3613-
pub fn block_disconnected(&mut self, header: &BlockHeader) -> bool {
3614-
if self.funding_tx_confirmations > 0 {
3615-
self.funding_tx_confirmations -= 1;
3616-
if self.funding_tx_confirmations == UNCONF_THRESHOLD as u64 {
3617-
return true;
3618-
}
3619-
}
3620-
if Some(header.block_hash()) == self.funding_tx_confirmed_in {
3621-
self.funding_tx_confirmations = self.minimum_depth as u64 - 1;
3637+
pub fn block_disconnected(&mut self, header: &BlockHeader, new_height: u32) -> bool {
3638+
if self.update_best_block(new_height, header.time).is_err() {
3639+
return true;
36223640
}
36233641
false
36243642
}
@@ -4426,7 +4444,7 @@ impl<Signer: Sign> Writeable for Channel<Signer> {
44264444

44274445
self.funding_tx_confirmed_in.write(writer)?;
44284446
self.short_channel_id.write(writer)?;
4429-
self.funding_tx_confirmations.write(writer)?;
4447+
self.funding_tx_confirmation_height.write(writer)?;
44304448

44314449
self.counterparty_dust_limit_satoshis.write(writer)?;
44324450
self.holder_dust_limit_satoshis.write(writer)?;
@@ -4586,7 +4604,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<&'a K> for Channel<Signer>
45864604

45874605
let funding_tx_confirmed_in = Readable::read(reader)?;
45884606
let short_channel_id = Readable::read(reader)?;
4589-
let funding_tx_confirmations = Readable::read(reader)?;
4607+
let funding_tx_confirmation_height = Readable::read(reader)?;
45904608

45914609
let counterparty_dust_limit_satoshis = Readable::read(reader)?;
45924610
let holder_dust_limit_satoshis = Readable::read(reader)?;
@@ -4656,7 +4674,7 @@ impl<'a, Signer: Sign, K: Deref> ReadableArgs<&'a K> for Channel<Signer>
46564674

46574675
funding_tx_confirmed_in,
46584676
short_channel_id,
4659-
funding_tx_confirmations,
4677+
funding_tx_confirmation_height,
46604678

46614679
counterparty_dust_limit_satoshis,
46624680
holder_dust_limit_satoshis,

lightning/src/ln/channelmanager.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3390,7 +3390,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
33903390

33913391
assert_eq!(*self.last_block_hash.read().unwrap(), header.block_hash(),
33923392
"Blocks must be disconnected in chain-order - the disconnected header must be the last connected header");
3393-
self.latest_block_height.fetch_sub(1, Ordering::AcqRel);
3393+
let new_height = self.latest_block_height.fetch_sub(1, Ordering::AcqRel) as u32 - 1;
33943394
*self.last_block_hash.write().unwrap() = header.prev_blockhash;
33953395

33963396
let mut failed_channels = Vec::new();
@@ -3400,7 +3400,7 @@ impl<Signer: Sign, M: Deref, T: Deref, K: Deref, F: Deref, L: Deref> ChannelMana
34003400
let short_to_id = &mut channel_state.short_to_id;
34013401
let pending_msg_events = &mut channel_state.pending_msg_events;
34023402
channel_state.by_id.retain(|_, v| {
3403-
if v.block_disconnected(header) {
3403+
if v.block_disconnected(header, new_height) {
34043404
if let Some(short_id) = v.get_short_channel_id() {
34053405
short_to_id.remove(&short_id);
34063406
}

0 commit comments

Comments
 (0)