Skip to content

Commit 757bcc2

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 81cea88 commit 757bcc2

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
@@ -460,8 +460,10 @@ pub struct ChannelMonitor {
460460
// Used to track outpoint in the process of being claimed by our transactions. We need to scan all transactions
461461
// for inputs spending this. If height timer (u32) is expired and claim tx hasn't reached enough confirmations
462462
// before, use TxMaterial to regenerate a new claim tx with a satoshis-per-1000-weight-units higher than last
463-
// one (u64).
464-
our_claim_txn_waiting_first_conf: HashMap<BitcoinOutPoint, (u32, TxMaterial, u64)>,
463+
// one (u64), if timelock expiration (u32) is near, decrease height timer, the in-between bumps delay.
464+
// Last field cached (u32) is height of outpoint confirmation, which is needed to flush this tracker
465+
// in case of reorgs, given block timer are scaled on timer expiration we can't deduce from it original height.
466+
our_claim_txn_waiting_first_conf: HashMap<BitcoinOutPoint, (u32, TxMaterial, u64, u32, u32)>,
465467

466468
// Used to track onchain events, i.e transactions parts of channels confirmed on chain, on which
467469
// we have to take actions once they reach enough confs. Key is a block height timer, i.e we enforce
@@ -624,6 +626,15 @@ impl ChannelMonitor {
624626
tx_weight
625627
}
626628

629+
fn get_height_timer(current_height: u32, timelock_expiration: u32) -> u32 {
630+
if timelock_expiration <= current_height || timelock_expiration - current_height <= 3 {
631+
return current_height + 1
632+
} else if timelock_expiration - current_height <= 15 {
633+
return current_height + 3
634+
}
635+
current_height + 15
636+
}
637+
627638
#[inline]
628639
fn place_secret(idx: u64) -> u8 {
629640
for i in 0..48 {
@@ -1129,6 +1140,8 @@ impl ChannelMonitor {
11291140
}
11301141
}
11311142
writer.write_all(&byte_utils::be64_to_array(claim_tx_data.2))?;
1143+
writer.write_all(&byte_utils::be32_to_array(claim_tx_data.3))?;
1144+
writer.write_all(&byte_utils::be32_to_array(claim_tx_data.4))?;
11321145
}
11331146

11341147
writer.write_all(&byte_utils::be64_to_array(self.onchain_events_waiting_threshold_conf.len() as u64))?;
@@ -1283,7 +1296,7 @@ impl ChannelMonitor {
12831296
witness: Vec::new(),
12841297
});
12851298
inputs_desc.push(InputDescriptors::RevokedOutput);
1286-
inputs_info.push((None, outp.value));
1299+
inputs_info.push((None, outp.value, self.our_to_self_delay as u32));
12871300
total_value += outp.value;
12881301
} else if Some(&outp.script_pubkey) == local_payment_p2wpkh.as_ref() {
12891302
spendable_outputs.push(SpendableOutputDescriptor::DynamicOutputP2WPKH {
@@ -1347,7 +1360,7 @@ impl ChannelMonitor {
13471360
if htlc.cltv_expiry > height + CLTV_SHARED_CLAIM_BUFFER {
13481361
inputs.push(input);
13491362
inputs_desc.push(if htlc.offered { InputDescriptors::RevokedOfferedHTLC } else { InputDescriptors::RevokedReceivedHTLC });
1350-
inputs_info.push((Some(idx), tx.output[transaction_output_index as usize].value));
1363+
inputs_info.push((Some(idx), tx.output[transaction_output_index as usize].value, htlc.cltv_expiry));
13511364
total_value += tx.output[transaction_output_index as usize].value;
13521365
} else {
13531366
let mut single_htlc_tx = Transaction {
@@ -1360,14 +1373,15 @@ impl ChannelMonitor {
13601373
}),
13611374
};
13621375
let predicted_weight = single_htlc_tx.get_weight() + Self::get_witnesses_weight(&[if htlc.offered { InputDescriptors::RevokedOfferedHTLC } else { InputDescriptors::RevokedReceivedHTLC }]);
1376+
let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
13631377
let mut used_feerate;
13641378
if subtract_high_prio_fee!(self, fee_estimator, single_htlc_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) {
13651379
let sighash_parts = bip143::SighashComponents::new(&single_htlc_tx);
13661380
let (redeemscript, revocation_key) = sign_input!(sighash_parts, single_htlc_tx.input[0], Some(idx), htlc.amount_msat / 1000);
13671381
assert!(predicted_weight >= single_htlc_tx.get_weight());
13681382
match self.our_claim_txn_waiting_first_conf.entry(single_htlc_tx.input[0].previous_output.clone()) {
13691383
hash_map::Entry::Occupied(_) => {},
1370-
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)); }
1384+
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)); }
13711385
}
13721386
txn_to_broadcast.push(single_htlc_tx);
13731387
}
@@ -1444,9 +1458,10 @@ impl ChannelMonitor {
14441458

14451459
for (input, info) in spend_tx.input.iter_mut().zip(inputs_info.iter()) {
14461460
let (redeemscript, revocation_key) = sign_input!(sighash_parts, input, info.0, info.1);
1461+
let height_timer = Self::get_height_timer(height, info.2);
14471462
match self.our_claim_txn_waiting_first_conf.entry(input.previous_output.clone()) {
14481463
hash_map::Entry::Occupied(_) => {},
1449-
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)); }
1464+
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, if !info.0.is_some() { height + info.2 } else { info.2 }, height)); }
14501465
}
14511466
}
14521467
assert!(predicted_weight >= spend_tx.get_weight());
@@ -1610,7 +1625,7 @@ impl ChannelMonitor {
16101625
if htlc.cltv_expiry > height + CLTV_SHARED_CLAIM_BUFFER {
16111626
inputs.push(input);
16121627
inputs_desc.push(if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC });
1613-
inputs_info.push((payment_preimage, tx.output[transaction_output_index as usize].value));
1628+
inputs_info.push((payment_preimage, tx.output[transaction_output_index as usize].value, htlc.cltv_expiry));
16141629
total_value += tx.output[transaction_output_index as usize].value;
16151630
} else {
16161631
let mut single_htlc_tx = Transaction {
@@ -1623,6 +1638,7 @@ impl ChannelMonitor {
16231638
}),
16241639
};
16251640
let predicted_weight = single_htlc_tx.get_weight() + Self::get_witnesses_weight(&[if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC }]);
1641+
let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
16261642
let mut used_feerate;
16271643
if subtract_high_prio_fee!(self, fee_estimator, single_htlc_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) {
16281644
let sighash_parts = bip143::SighashComponents::new(&single_htlc_tx);
@@ -1634,7 +1650,7 @@ impl ChannelMonitor {
16341650
});
16351651
match self.our_claim_txn_waiting_first_conf.entry(single_htlc_tx.input[0].previous_output.clone()) {
16361652
hash_map::Entry::Occupied(_) => {},
1637-
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)); }
1653+
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)); }
16381654
}
16391655
txn_to_broadcast.push(single_htlc_tx);
16401656
}
@@ -1662,6 +1678,7 @@ impl ChannelMonitor {
16621678
}),
16631679
};
16641680
let predicted_weight = timeout_tx.get_weight() + Self::get_witnesses_weight(&[InputDescriptors::ReceivedHTLC]);
1681+
let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
16651682
let mut used_feerate;
16661683
if subtract_high_prio_fee!(self, fee_estimator, timeout_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) {
16671684
let sighash_parts = bip143::SighashComponents::new(&timeout_tx);
@@ -1670,7 +1687,7 @@ impl ChannelMonitor {
16701687
//TODO: track SpendableOutputDescriptor
16711688
match self.our_claim_txn_waiting_first_conf.entry(timeout_tx.input[0].previous_output.clone()) {
16721689
hash_map::Entry::Occupied(_) => {},
1673-
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)); }
1690+
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)); }
16741691
}
16751692
}
16761693
txn_to_broadcast.push(timeout_tx);
@@ -1702,9 +1719,10 @@ impl ChannelMonitor {
17021719

17031720
for (input, info) in spend_tx.input.iter_mut().zip(inputs_info.iter()) {
17041721
let (redeemscript, htlc_key) = sign_input!(sighash_parts, input, info.1, (info.0).0.to_vec());
1722+
let height_timer = Self::get_height_timer(height, info.2);
17051723
match self.our_claim_txn_waiting_first_conf.entry(input.previous_output.clone()) {
17061724
hash_map::Entry::Occupied(_) => {},
1707-
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)); }
1725+
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)); }
17081726
}
17091727
}
17101728
assert!(predicted_weight >= spend_tx.get_weight());
@@ -1808,15 +1826,16 @@ impl ChannelMonitor {
18081826
assert!(predicted_weight >= spend_tx.get_weight());
18091827
let outpoint = BitcoinOutPoint { txid: spend_tx.txid(), vout: 0 };
18101828
let output = spend_tx.output[0].clone();
1829+
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
18111830
match self.our_claim_txn_waiting_first_conf.entry(spend_tx.input[0].previous_output.clone()) {
18121831
hash_map::Entry::Occupied(_) => {},
1813-
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)); }
1832+
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, height + self.our_to_self_delay as u32, height)); }
18141833
}
18151834
(Some(spend_tx), Some(SpendableOutputDescriptor::StaticOutput { outpoint, output }))
18161835
} else { (None, None) }
18171836
}
18181837

1819-
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))>) {
1838+
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))>) {
18201839
let mut res = Vec::with_capacity(local_tx.htlc_outputs.len());
18211840
let mut spendable_outputs = Vec::with_capacity(local_tx.htlc_outputs.len());
18221841
let mut watch_outputs = Vec::with_capacity(local_tx.htlc_outputs.len());
@@ -1869,7 +1888,8 @@ impl ChannelMonitor {
18691888
htlc_timeout_tx.input[0].witness.push(htlc_script.clone().into_bytes());
18701889

18711890
add_dynamic_output!(htlc_timeout_tx, 0);
1872-
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)));
1891+
let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
1892+
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)));
18731893
res.push(htlc_timeout_tx);
18741894
} else {
18751895
if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) {
@@ -1888,7 +1908,8 @@ impl ChannelMonitor {
18881908
htlc_success_tx.input[0].witness.push(htlc_script.clone().into_bytes());
18891909

18901910
add_dynamic_output!(htlc_success_tx, 0);
1891-
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)));
1911+
let height_timer = Self::get_height_timer(height, htlc.cltv_expiry);
1912+
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)));
18921913
res.push(htlc_success_tx);
18931914
}
18941915
}
@@ -2190,7 +2211,7 @@ impl ChannelMonitor {
21902211
//- htlc update there as failure-trigger tx (revoked commitment tx, non-revoked commitment tx, HTLC-timeout tx) has been disconnected
21912212
//- our claim tx on a commitment tx output
21922213
}
2193-
self.our_claim_txn_waiting_first_conf.retain(|_, ref mut v| if v.0 == height + 3 { false } else { true });
2214+
self.our_claim_txn_waiting_first_conf.retain(|_, ref mut v| if v.3 == height { false } else { true });
21942215
self.last_block_hash = block_hash.clone();
21952216
}
21962217

@@ -2659,7 +2680,9 @@ impl<R: ::std::io::Read> ReadableArgs<R, Arc<Logger>> for (Sha256dHash, ChannelM
26592680
_ => return Err(DecodeError::InvalidValue),
26602681
};
26612682
let last_fee = Readable::read(reader)?;
2662-
our_claim_txn_waiting_first_conf.insert(outpoint, (height_target, tx_material, last_fee));
2683+
let timelock_expiration = Readable::read(reader)?;
2684+
let height = Readable::read(reader)?;
2685+
our_claim_txn_waiting_first_conf.insert(outpoint, (height_target, tx_material, last_fee, timelock_expiration, height));
26632686
}
26642687

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

0 commit comments

Comments
 (0)