Skip to content

Commit 9d8bfa2

Browse files
author
Antoine Riard
committed
Implement dynamic height timer for bump candidates txn
We must adapt our delay between two bumps of claim txn in respect to the timelock encumbering the targeted outpoint. If HTLC or revoked output is near to expire, we should try to get our claim in every block. If it's reasonably in the future, we may give us more latency to bump
1 parent 7017370 commit 9d8bfa2

File tree

1 file changed

+39
-16
lines changed

1 file changed

+39
-16
lines changed

src/ln/channelmonitor.rs

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -457,8 +457,10 @@ pub struct ChannelMonitor {
457457
// Used to track outpoint in the process of being claimed by our transactions. We need to scan all transactions
458458
// for inputs spending this. If height timer (u32) is expired and claim tx hasn't reached enough confirmations
459459
// before, use TxMaterial to regenerate a new claim tx with a satoshis-per-1000-weight-units higher than last
460-
// one (u64).
461-
our_claim_txn_waiting_first_conf: HashMap<BitcoinOutPoint, (u32, TxMaterial, u64)>,
460+
// one (u64), if timelock expiration (u32) is near, decrease height timer, the in-between bumps delay.
461+
// Last field cached (u32) is height of outpoint confirmation, which is needed to flush this tracker
462+
// in case of reorgs, given block timer are scaled on timer expiration we can't deduce from it original height.
463+
our_claim_txn_waiting_first_conf: HashMap<BitcoinOutPoint, (u32, TxMaterial, u64, u32, u32)>,
462464

463465
// Used to track onchain events, i.e transactions parts of channels confirmed on chain, on which
464466
// we have to take actions once they reach enough confs. Key is a block height timer, i.e we enforce
@@ -621,6 +623,15 @@ impl ChannelMonitor {
621623
tx_weight
622624
}
623625

626+
fn get_height_timer(current_height: u32, timelock_expiration: u32) -> u32 {
627+
if timelock_expiration <= current_height || timelock_expiration - current_height <= 3 {
628+
return current_height + 1
629+
} else if timelock_expiration - current_height <= 15 {
630+
return current_height + 3
631+
}
632+
current_height + 15
633+
}
634+
624635
#[inline]
625636
fn place_secret(idx: u64) -> u8 {
626637
for i in 0..48 {
@@ -1126,6 +1137,8 @@ impl ChannelMonitor {
11261137
}
11271138
}
11281139
writer.write_all(&byte_utils::be64_to_array(claim_tx_data.2))?;
1140+
writer.write_all(&byte_utils::be32_to_array(claim_tx_data.3))?;
1141+
writer.write_all(&byte_utils::be32_to_array(claim_tx_data.4))?;
11291142
}
11301143

11311144
writer.write_all(&byte_utils::be64_to_array(self.onchain_events_waiting_threshold_conf.len() as u64))?;
@@ -1280,7 +1293,7 @@ impl ChannelMonitor {
12801293
witness: Vec::new(),
12811294
});
12821295
inputs_desc.push(InputDescriptors::RevokedOutput);
1283-
inputs_info.push((None, outp.value));
1296+
inputs_info.push((None, outp.value, self.their_to_self_delay.unwrap() as u32)); // We can safely unwrap given we are past channel opening
12841297
total_value += outp.value;
12851298
} else if Some(&outp.script_pubkey) == local_payment_p2wpkh.as_ref() {
12861299
spendable_outputs.push(SpendableOutputDescriptor::DynamicOutputP2WPKH {
@@ -1344,7 +1357,7 @@ impl ChannelMonitor {
13441357
if htlc.cltv_expiry > height + CLTV_SHARED_CLAIM_BUFFER {
13451358
inputs.push(input);
13461359
inputs_desc.push(if htlc.offered { InputDescriptors::RevokedOfferedHTLC } else { InputDescriptors::RevokedReceivedHTLC });
1347-
inputs_info.push((Some(idx), tx.output[transaction_output_index as usize].value));
1360+
inputs_info.push((Some(idx), tx.output[transaction_output_index as usize].value, htlc.cltv_expiry));
13481361
total_value += tx.output[transaction_output_index as usize].value;
13491362
} else {
13501363
let mut single_htlc_tx = Transaction {
@@ -1357,14 +1370,15 @@ impl ChannelMonitor {
13571370
}),
13581371
};
13591372
let predicted_weight = single_htlc_tx.get_weight() + Self::get_witnesses_weight(&[if htlc.offered { InputDescriptors::RevokedOfferedHTLC } else { InputDescriptors::RevokedReceivedHTLC }]);
1373+
let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
13601374
let mut used_feerate;
13611375
if subtract_high_prio_fee!(self, fee_estimator, single_htlc_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) {
13621376
let sighash_parts = bip143::SighashComponents::new(&single_htlc_tx);
13631377
let (redeemscript, revocation_key) = sign_input!(sighash_parts, single_htlc_tx.input[0], Some(idx), htlc.amount_msat / 1000);
13641378
assert!(predicted_weight >= single_htlc_tx.get_weight());
13651379
match self.our_claim_txn_waiting_first_conf.entry(single_htlc_tx.input[0].previous_output.clone()) {
13661380
hash_map::Entry::Occupied(_) => {},
1367-
hash_map::Entry::Vacant(entry) => { entry.insert((height + 3, TxMaterial::Revoked { script: redeemscript, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: true, amount: htlc.amount_msat / 1000 }, used_feerate)); }
1381+
hash_map::Entry::Vacant(entry) => { entry.insert((height_timer, TxMaterial::Revoked { script: redeemscript, pubkey: Some(revocation_pubkey), key: revocation_key, is_htlc: true, amount: htlc.amount_msat / 1000 }, used_feerate, htlc.cltv_expiry, height)); }
13681382
}
13691383
txn_to_broadcast.push(single_htlc_tx);
13701384
}
@@ -1441,9 +1455,10 @@ impl ChannelMonitor {
14411455

14421456
for (input, info) in spend_tx.input.iter_mut().zip(inputs_info.iter()) {
14431457
let (redeemscript, revocation_key) = sign_input!(sighash_parts, input, info.0, info.1);
1458+
let height_timer = Self::get_height_timer(height, info.2);
14441459
match self.our_claim_txn_waiting_first_conf.entry(input.previous_output.clone()) {
14451460
hash_map::Entry::Occupied(_) => {},
1446-
hash_map::Entry::Vacant(entry) => { entry.insert((height + 3, TxMaterial::Revoked { script: redeemscript, pubkey: if info.0.is_some() { Some(revocation_pubkey) } else { None }, key: revocation_key, is_htlc: if info.0.is_some() { true } else { false }, amount: info.1 }, used_feerate)); }
1461+
hash_map::Entry::Vacant(entry) => { entry.insert((height_timer, TxMaterial::Revoked { script: redeemscript, pubkey: if info.0.is_some() { Some(revocation_pubkey) } else { None }, key: revocation_key, is_htlc: if info.0.is_some() { true } else { false }, amount: info.1 }, used_feerate, info.2, height)); }
14471462
}
14481463
}
14491464
assert!(predicted_weight >= spend_tx.get_weight());
@@ -1607,7 +1622,7 @@ impl ChannelMonitor {
16071622
if htlc.cltv_expiry > height + CLTV_SHARED_CLAIM_BUFFER {
16081623
inputs.push(input);
16091624
inputs_desc.push(if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC });
1610-
inputs_info.push((payment_preimage, tx.output[transaction_output_index as usize].value));
1625+
inputs_info.push((payment_preimage, tx.output[transaction_output_index as usize].value, htlc.cltv_expiry));
16111626
total_value += tx.output[transaction_output_index as usize].value;
16121627
} else {
16131628
let mut single_htlc_tx = Transaction {
@@ -1620,6 +1635,7 @@ impl ChannelMonitor {
16201635
}),
16211636
};
16221637
let predicted_weight = single_htlc_tx.get_weight() + Self::get_witnesses_weight(&[if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC }]);
1638+
let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
16231639
let mut used_feerate;
16241640
if subtract_high_prio_fee!(self, fee_estimator, single_htlc_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) {
16251641
let sighash_parts = bip143::SighashComponents::new(&single_htlc_tx);
@@ -1631,7 +1647,7 @@ impl ChannelMonitor {
16311647
});
16321648
match self.our_claim_txn_waiting_first_conf.entry(single_htlc_tx.input[0].previous_output.clone()) {
16331649
hash_map::Entry::Occupied(_) => {},
1634-
hash_map::Entry::Vacant(entry) => { entry.insert((height + 3, TxMaterial::RemoteHTLC { script: redeemscript, key: htlc_key, preimage: Some(*payment_preimage), amount: htlc.amount_msat / 1000 }, used_feerate)); }
1650+
hash_map::Entry::Vacant(entry) => { entry.insert((height_timer, TxMaterial::RemoteHTLC { script: redeemscript, key: htlc_key, preimage: Some(*payment_preimage), amount: htlc.amount_msat / 1000 }, used_feerate, htlc.cltv_expiry, height)); }
16351651
}
16361652
txn_to_broadcast.push(single_htlc_tx);
16371653
}
@@ -1659,6 +1675,7 @@ impl ChannelMonitor {
16591675
}),
16601676
};
16611677
let predicted_weight = timeout_tx.get_weight() + Self::get_witnesses_weight(&[InputDescriptors::ReceivedHTLC]);
1678+
let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
16621679
let mut used_feerate;
16631680
if subtract_high_prio_fee!(self, fee_estimator, timeout_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) {
16641681
let sighash_parts = bip143::SighashComponents::new(&timeout_tx);
@@ -1667,7 +1684,7 @@ impl ChannelMonitor {
16671684
//TODO: track SpendableOutputDescriptor
16681685
match self.our_claim_txn_waiting_first_conf.entry(timeout_tx.input[0].previous_output.clone()) {
16691686
hash_map::Entry::Occupied(_) => {},
1670-
hash_map::Entry::Vacant(entry) => { entry.insert((height + 3, TxMaterial::RemoteHTLC { script : redeemscript, key: htlc_key, preimage: None, amount: htlc.amount_msat / 1000 }, used_feerate)); }
1687+
hash_map::Entry::Vacant(entry) => { entry.insert((height_timer, TxMaterial::RemoteHTLC { script : redeemscript, key: htlc_key, preimage: None, amount: htlc.amount_msat / 1000 }, used_feerate, htlc.cltv_expiry, height)); }
16711688
}
16721689
}
16731690
txn_to_broadcast.push(timeout_tx);
@@ -1699,9 +1716,10 @@ impl ChannelMonitor {
16991716

17001717
for (input, info) in spend_tx.input.iter_mut().zip(inputs_info.iter()) {
17011718
let (redeemscript, htlc_key) = sign_input!(sighash_parts, input, info.1, (info.0).0.to_vec());
1719+
let height_timer = Self::get_height_timer(height, info.2);
17021720
match self.our_claim_txn_waiting_first_conf.entry(input.previous_output.clone()) {
17031721
hash_map::Entry::Occupied(_) => {},
1704-
hash_map::Entry::Vacant(entry) => { entry.insert((height + 3, TxMaterial::RemoteHTLC { script: redeemscript, key: htlc_key, preimage: Some(*(info.0)), amount: info.1}, used_feerate)); }
1722+
hash_map::Entry::Vacant(entry) => { entry.insert((height_timer, TxMaterial::RemoteHTLC { script: redeemscript, key: htlc_key, preimage: Some(*(info.0)), amount: info.1}, used_feerate, info.2, height)); }
17051723
}
17061724
}
17071725
assert!(predicted_weight >= spend_tx.get_weight());
@@ -1805,15 +1823,16 @@ impl ChannelMonitor {
18051823
assert!(predicted_weight >= spend_tx.get_weight());
18061824
let outpoint = BitcoinOutPoint { txid: spend_tx.txid(), vout: 0 };
18071825
let output = spend_tx.output[0].clone();
1826+
let height_timer = Self::get_height_timer(height, self.their_to_self_delay.unwrap() as u32); // We can safely unwrap given we are past channel opening
18081827
match self.our_claim_txn_waiting_first_conf.entry(spend_tx.input[0].previous_output.clone()) {
18091828
hash_map::Entry::Occupied(_) => {},
1810-
hash_map::Entry::Vacant(entry) => { entry.insert((height + 3, TxMaterial::Revoked { script: redeemscript, pubkey: None, key: revocation_key, is_htlc: false, amount: tx.output[0].value }, used_feerate)); }
1829+
hash_map::Entry::Vacant(entry) => { entry.insert((height_timer, TxMaterial::Revoked { script: redeemscript, pubkey: None, key: revocation_key, is_htlc: false, amount: tx.output[0].value }, used_feerate, self.their_to_self_delay.unwrap() as u32, height)); }
18111830
}
18121831
(Some(spend_tx), Some(SpendableOutputDescriptor::StaticOutput { outpoint, output }))
18131832
} else { (None, None) }
18141833
}
18151834

1816-
fn broadcast_by_local_state(&self, local_tx: &LocalSignedTx, per_commitment_point: &Option<PublicKey>, delayed_payment_base_key: &Option<SecretKey>, height: u32) -> (Vec<Transaction>, Vec<SpendableOutputDescriptor>, Vec<TxOut>, Vec<(BitcoinOutPoint, (u32, TxMaterial, u64))>) {
1835+
fn broadcast_by_local_state(&self, local_tx: &LocalSignedTx, per_commitment_point: &Option<PublicKey>, delayed_payment_base_key: &Option<SecretKey>, height: u32) -> (Vec<Transaction>, Vec<SpendableOutputDescriptor>, Vec<TxOut>, Vec<(BitcoinOutPoint, (u32, TxMaterial, u64, u32, u32))>) {
18171836
let mut res = Vec::with_capacity(local_tx.htlc_outputs.len());
18181837
let mut spendable_outputs = Vec::with_capacity(local_tx.htlc_outputs.len());
18191838
let mut watch_outputs = Vec::with_capacity(local_tx.htlc_outputs.len());
@@ -1866,7 +1885,8 @@ impl ChannelMonitor {
18661885
htlc_timeout_tx.input[0].witness.push(htlc_script.clone().into_bytes());
18671886

18681887
add_dynamic_output!(htlc_timeout_tx, 0);
1869-
pending_claims.push((htlc_timeout_tx.input[0].previous_output.clone(), (height + 3, TxMaterial::LocalHTLC { script: htlc_script, sigs: (*their_sig, *our_sig), preimage: None, amount: htlc.amount_msat / 1000}, 0)));
1888+
let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
1889+
pending_claims.push((htlc_timeout_tx.input[0].previous_output.clone(), (height_timer, TxMaterial::LocalHTLC { script: htlc_script, sigs: (*their_sig, *our_sig), preimage: None, amount: htlc.amount_msat / 1000}, 0, htlc.cltv_expiry, height)));
18701890
res.push(htlc_timeout_tx);
18711891
} else {
18721892
if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
@@ -1885,7 +1905,8 @@ impl ChannelMonitor {
18851905
htlc_success_tx.input[0].witness.push(htlc_script.clone().into_bytes());
18861906

18871907
add_dynamic_output!(htlc_success_tx, 0);
1888-
pending_claims.push((htlc_success_tx.input[0].previous_output.clone(), (height + 3, TxMaterial::LocalHTLC { script: htlc_script, sigs: (*their_sig, *our_sig), preimage: Some(*payment_preimage), amount: htlc.amount_msat / 1000}, 0)));
1908+
let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
1909+
pending_claims.push((htlc_success_tx.input[0].previous_output.clone(), (height_timer, TxMaterial::LocalHTLC { script: htlc_script, sigs: (*their_sig, *our_sig), preimage: Some(*payment_preimage), amount: htlc.amount_msat / 1000}, 0, htlc.cltv_expiry, height)));
18891910
res.push(htlc_success_tx);
18901911
}
18911912
}
@@ -2187,7 +2208,7 @@ impl ChannelMonitor {
21872208
//- htlc update there as failure-trigger tx (revoked commitment tx, non-revoked commitment tx, HTLC-timeout tx) has been disconnected
21882209
//- our claim tx on a commitment tx output
21892210
}
2190-
self.our_claim_txn_waiting_first_conf.retain(|_, ref mut v| if v.0 == height + 3 { false } else { true });
2211+
self.our_claim_txn_waiting_first_conf.retain(|_, ref mut v| if v.3 == height { false } else { true });
21912212
self.last_block_hash = block_hash.clone();
21922213
}
21932214

@@ -2656,7 +2677,9 @@ impl<R: ::std::io::Read> ReadableArgs<R, Arc<Logger>> for (Sha256dHash, ChannelM
26562677
_ => return Err(DecodeError::InvalidValue),
26572678
};
26582679
let last_fee = Readable::read(reader)?;
2659-
our_claim_txn_waiting_first_conf.insert(outpoint, (height_target, tx_material, last_fee));
2680+
let timelock_expiration = Readable::read(reader)?;
2681+
let height = Readable::read(reader)?;
2682+
our_claim_txn_waiting_first_conf.insert(outpoint, (height_target, tx_material, last_fee, timelock_expiration, height));
26602683
}
26612684

26622685
let waiting_threshold_conf_len: u64 = Readable::read(reader)?;

0 commit comments

Comments
 (0)