-
- /// Lightning security model (i.e being able to redeem/timeout HTLC or penalize coutnerparty onchain) lays on the assumption of claim transactions getting confirmed before timelock expiration
- /// (CSV or CLTV following cases). In case of high-fee spikes, claim tx may stuck in the mempool, so you need to bump its feerate quickly using Replace-By-Fee or Child-Pay-For-Parent.
- fn bump_claim_tx(&self, height: u32, cached_claim_datas: &ClaimTxBumpMaterial, fee_estimator: &FeeEstimator) -> Option<(u32, u64, Transaction)> {
- if cached_claim_datas.per_input_material.len() == 0 { return None } // But don't prune pending claiming request yet, we may have to resurrect HTLCs
- let mut inputs = Vec::new();
- for outp in cached_claim_datas.per_input_material.keys() {
- inputs.push(TxIn {
- previous_output: *outp,
- script_sig: Script::new(),
- sequence: 0xfffffffd,
- witness: Vec::new(),
- });
- }
- let mut bumped_tx = Transaction {
- version: 2,
- lock_time: 0,
- input: inputs,
- output: vec![TxOut {
- script_pubkey: self.destination_script.clone(),
- value: 0
- }],
- };
-
- macro_rules! RBF_bump {
- ($amount: expr, $old_feerate: expr, $fee_estimator: expr, $predicted_weight: expr) => {
- {
- let mut used_feerate;
- // If old feerate inferior to actual one given back by Fee Estimator, use it to compute new fee...
- let new_fee = if $old_feerate < $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) {
- let mut value = $amount;
- if subtract_high_prio_fee!(self, $fee_estimator, value, $predicted_weight, used_feerate) {
- // Overflow check is done in subtract_high_prio_fee
- $amount - value
- } else {
- log_trace!(self, "Can't new-estimation bump new claiming tx, amount {} is too small", $amount);
- return None;
- }
- // ...else just increase the previous feerate by 25% (because that's a nice number)
- } else {
- let fee = $old_feerate * $predicted_weight / 750;
- if $amount <= fee {
- log_trace!(self, "Can't 25% bump new claiming tx, amount {} is too small", $amount);
- return None;
- }
- fee
- };
-
- let previous_fee = $old_feerate * $predicted_weight / 1000;
- let min_relay_fee = MIN_RELAY_FEE_SAT_PER_1000_WEIGHT * $predicted_weight / 1000;
- // BIP 125 Opt-in Full Replace-by-Fee Signaling
- // * 3. The replacement transaction pays an absolute fee of at least the sum paid by the original transactions.
- // * 4. The replacement transaction must also pay for its own bandwidth at or above the rate set by the node's minimum relay fee setting.
- let new_fee = if new_fee < previous_fee + min_relay_fee {
- new_fee + previous_fee + min_relay_fee - new_fee
- } else {
- new_fee
- };
- Some((new_fee, new_fee * 1000 / $predicted_weight))
- }
- }
- }
-
- let new_timer = Self::get_height_timer(height, cached_claim_datas.soonest_timelock);
- let mut inputs_witnesses_weight = 0;
- let mut amt = 0;
- for per_outp_material in cached_claim_datas.per_input_material.values() {
- match per_outp_material {
- &InputMaterial::Revoked { ref script, ref is_htlc, ref amount, .. } => {
- inputs_witnesses_weight += Self::get_witnesses_weight(if !is_htlc { &[InputDescriptors::RevokedOutput] } else if HTLCType::scriptlen_to_htlctype(script.len()) == Some(HTLCType::OfferedHTLC) { &[InputDescriptors::RevokedOfferedHTLC] } else if HTLCType::scriptlen_to_htlctype(script.len()) == Some(HTLCType::AcceptedHTLC) { &[InputDescriptors::RevokedReceivedHTLC] } else { unreachable!() });
- amt += *amount;
- },
- &InputMaterial::RemoteHTLC { ref preimage, ref amount, .. } => {
- inputs_witnesses_weight += Self::get_witnesses_weight(if preimage.is_some() { &[InputDescriptors::OfferedHTLC] } else { &[InputDescriptors::ReceivedHTLC] });
- amt += *amount;
- },
- &InputMaterial::LocalHTLC { .. } => { return None; }
- }
- }
-
- let predicted_weight = bumped_tx.get_weight() + inputs_witnesses_weight;
- let new_feerate;
- if let Some((new_fee, feerate)) = RBF_bump!(amt, cached_claim_datas.feerate_previous, fee_estimator, predicted_weight as u64) {
- // If new computed fee is superior at the whole claimable amount burn all in fees
- if new_fee > amt {
- bumped_tx.output[0].value = 0;
- } else {
- bumped_tx.output[0].value = amt - new_fee;
- }
- new_feerate = feerate;
- } else {
- return None;
- }
- assert!(new_feerate != 0);
-
- for (i, (outp, per_outp_material)) in cached_claim_datas.per_input_material.iter().enumerate() {
- match per_outp_material {
- &InputMaterial::Revoked { ref script, ref pubkey, ref key, ref is_htlc, ref amount } => {
- let sighash_parts = bip143::SighashComponents::new(&bumped_tx);
- let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &script, *amount)[..]);
- let sig = self.secp_ctx.sign(&sighash, &key);
- bumped_tx.input[i].witness.push(sig.serialize_der().to_vec());
- bumped_tx.input[i].witness[0].push(SigHashType::All as u8);
- if *is_htlc {
- bumped_tx.input[i].witness.push(pubkey.unwrap().clone().serialize().to_vec());
- } else {
- bumped_tx.input[i].witness.push(vec!(1));
- }
- bumped_tx.input[i].witness.push(script.clone().into_bytes());
- log_trace!(self, "Going to broadcast bumped Penalty Transaction {} claiming revoked {} output {} from {} with new feerate {}", bumped_tx.txid(), if !is_htlc { "to_local" } else if HTLCType::scriptlen_to_htlctype(script.len()) == Some(HTLCType::OfferedHTLC) { "offered" } else if HTLCType::scriptlen_to_htlctype(script.len()) == Some(HTLCType::AcceptedHTLC) { "received" } else { "" }, outp.vout, outp.txid, new_feerate);
- },
- &InputMaterial::RemoteHTLC { ref script, ref key, ref preimage, ref amount, ref locktime } => {
- if !preimage.is_some() { bumped_tx.lock_time = *locktime };
- let sighash_parts = bip143::SighashComponents::new(&bumped_tx);
- let sighash = hash_to_message!(&sighash_parts.sighash_all(&bumped_tx.input[i], &script, *amount)[..]);
- let sig = self.secp_ctx.sign(&sighash, &key);
- bumped_tx.input[i].witness.push(sig.serialize_der().to_vec());
- bumped_tx.input[i].witness[0].push(SigHashType::All as u8);
- if let &Some(preimage) = preimage {
- bumped_tx.input[i].witness.push(preimage.clone().0.to_vec());
- } else {
- bumped_tx.input[i].witness.push(vec![0]);
- }
- bumped_tx.input[i].witness.push(script.clone().into_bytes());
- log_trace!(self, "Going to broadcast bumped Claim Transaction {} claiming remote {} htlc output {} from {} with new feerate {}", bumped_tx.txid(), if preimage.is_some() { "offered" } else { "received" }, outp.vout, outp.txid, new_feerate);
- },
- &InputMaterial::LocalHTLC { .. } => {
- //TODO : Given that Local Commitment Transaction and HTLC-Timeout/HTLC-Success are counter-signed by peer, we can't
- // RBF them. Need a Lightning specs change and package relay modification :
- // https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2018-November/016518.html
- return None;
- }
- }
- }
- assert!(predicted_weight >= bumped_tx.get_weight());
- Some((new_timer, new_feerate, bumped_tx))
- }