+
+ /// 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, .. } => {
+ log_trace!(self, "Is HLTC ? {}", is_htlc);
+ 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))
+ }