From: Antoine Riard Date: Sun, 31 Mar 2019 02:12:55 +0000 (-0400) Subject: Add in-flight claim-tx tracking X-Git-Tag: v0.0.12~206^2~1 X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=commitdiff_plain;h=81cea88d38c58595a12182a63abe7af090a374b5;p=rust-lightning Add in-flight claim-tx tracking When we generate a justice tx, a htlc tx on remote commitment or a htlc tx on local commitment we track them until first conf. --- diff --git a/src/ln/channelmonitor.rs b/src/ln/channelmonitor.rs index 6e1b212ea..f6d741afa 100644 --- a/src/ln/channelmonitor.rs +++ b/src/ln/channelmonitor.rs @@ -364,6 +364,32 @@ enum InputDescriptors { RevokedOutput, // either a revoked to_local output on commitment tx, a revoked HTLC-Timeout output or a revoked HTLC-Success output } +/// When ChannelMonitor discovers an onchain outpoint being a step of a channel and that it needs +/// to generate a tx to push channel state forward, we cache outpoint-solving tx material to build +/// a new bumped one in case of lenghty confirmation delay +#[derive(Clone, PartialEq)] +enum TxMaterial { + Revoked { + script: Script, + pubkey: Option, + key: SecretKey, + is_htlc: bool, + amount: u64, + }, + RemoteHTLC { + script: Script, + key: SecretKey, + preimage: Option, + amount: u64, + }, + LocalHTLC { + script: Script, + sigs: (Signature, Signature), + preimage: Option, + amount: u64, + } +} + /// Upon discovering of some classes of onchain tx by ChannelMonitor, we may have to take actions on it /// once they mature to enough confirmations (ANTI_REORG_DELAY) #[derive(Clone, PartialEq)] @@ -431,6 +457,12 @@ pub struct ChannelMonitor { destination_script: Script, + // Used to track outpoint in the process of being claimed by our transactions. We need to scan all transactions + // for inputs spending this. If height timer (u32) is expired and claim tx hasn't reached enough confirmations + // before, use TxMaterial to regenerate a new claim tx with a satoshis-per-1000-weight-units higher than last + // one (u64). + our_claim_txn_waiting_first_conf: HashMap, + // Used to track onchain events, i.e transactions parts of channels confirmed on chain, on which // we have to take actions once they reach enough confs. Key is a block height timer, i.e we enforce // actions when we receive a block with given height. Actions depend on OnchainEvent type. @@ -447,13 +479,16 @@ pub struct ChannelMonitor { } macro_rules! subtract_high_prio_fee { - ($self: ident, $fee_estimator: expr, $value: expr, $predicted_weight: expr, $spent_txid: expr) => { + ($self: ident, $fee_estimator: expr, $value: expr, $predicted_weight: expr, $spent_txid: expr, $used_feerate: expr) => { { - let mut fee = $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority) * $predicted_weight / 1000; + $used_feerate = $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::HighPriority); + let mut fee = $used_feerate * $predicted_weight / 1000; if $value <= fee { - fee = $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal) * $predicted_weight / 1000; + $used_feerate = $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Normal); + fee = $used_feerate * $predicted_weight / 1000; if $value <= fee { - fee = $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Background) * $predicted_weight / 1000; + $used_feerate = $fee_estimator.get_est_sat_per_1000_weight(ConfirmationTarget::Background); + fee = $used_feerate * $predicted_weight / 1000; if $value <= fee { log_error!($self, "Failed to generate an on-chain punishment tx spending {} as even low priority fee ({} sat) was more than the entire claim balance ({} sat)", $spent_txid, fee, $value); @@ -498,6 +533,7 @@ impl PartialEq for ChannelMonitor { self.current_local_signed_commitment_tx != other.current_local_signed_commitment_tx || self.payment_preimages != other.payment_preimages || self.destination_script != other.destination_script || + self.our_claim_txn_waiting_first_conf != other.our_claim_txn_waiting_first_conf || self.onchain_events_waiting_threshold_conf != other.onchain_events_waiting_threshold_conf { false @@ -548,6 +584,8 @@ impl ChannelMonitor { payment_preimages: HashMap::new(), destination_script: destination_script, + our_claim_txn_waiting_first_conf: HashMap::new(), + onchain_events_waiting_threshold_conf: HashMap::new(), last_block_hash: Default::default(), @@ -1057,6 +1095,42 @@ impl ChannelMonitor { self.last_block_hash.write(writer)?; self.destination_script.write(writer)?; + writer.write_all(&byte_utils::be64_to_array(self.our_claim_txn_waiting_first_conf.len() as u64))?; + for (ref outpoint, claim_tx_data) in self.our_claim_txn_waiting_first_conf.iter() { + outpoint.write(writer)?; + writer.write_all(&byte_utils::be32_to_array(claim_tx_data.0))?; + match claim_tx_data.1 { + TxMaterial::Revoked { ref script, ref pubkey, ref key, ref is_htlc, ref amount} => { + writer.write_all(&[0; 1])?; + script.write(writer)?; + pubkey.write(writer)?; + writer.write_all(&key[..])?; + if *is_htlc { + writer.write_all(&[0; 1])?; + } else { + writer.write_all(&[1; 1])?; + } + writer.write_all(&byte_utils::be64_to_array(*amount))?; + }, + TxMaterial::RemoteHTLC { ref script, ref key, ref preimage, ref amount } => { + writer.write_all(&[1; 1])?; + script.write(writer)?; + key.write(writer)?; + preimage.write(writer)?; + writer.write_all(&byte_utils::be64_to_array(*amount))?; + }, + TxMaterial::LocalHTLC { ref script, ref sigs, ref preimage, ref amount } => { + writer.write_all(&[2; 1])?; + script.write(writer)?; + sigs.0.write(writer)?; + sigs.1.write(writer)?; + preimage.write(writer)?; + writer.write_all(&byte_utils::be64_to_array(*amount))?; + } + } + writer.write_all(&byte_utils::be64_to_array(claim_tx_data.2))?; + } + writer.write_all(&byte_utils::be64_to_array(self.onchain_events_waiting_threshold_conf.len() as u64))?; for (ref target, ref events) in self.onchain_events_waiting_threshold_conf.iter() { writer.write_all(&byte_utils::be32_to_array(**target))?; @@ -1193,10 +1267,9 @@ impl ChannelMonitor { } else { None }; let mut total_value = 0; - let mut values = Vec::new(); let mut inputs = Vec::new(); - let mut htlc_idxs = Vec::new(); - let mut input_descriptors = Vec::new(); + let mut inputs_info = Vec::new(); + let mut inputs_desc = Vec::new(); for (idx, outp) in tx.output.iter().enumerate() { if outp.script_pubkey == revokeable_p2wsh { @@ -1209,10 +1282,9 @@ impl ChannelMonitor { sequence: 0xfffffffd, witness: Vec::new(), }); - htlc_idxs.push(None); - values.push(outp.value); + inputs_desc.push(InputDescriptors::RevokedOutput); + inputs_info.push((None, outp.value)); total_value += outp.value; - input_descriptors.push(InputDescriptors::RevokedOutput); } else if Some(&outp.script_pubkey) == local_payment_p2wpkh.as_ref() { spendable_outputs.push(SpendableOutputDescriptor::DynamicOutputP2WPKH { outpoint: BitcoinOutPoint { txid: commitment_txid, vout: idx as u32 }, @@ -1225,7 +1297,7 @@ impl ChannelMonitor { macro_rules! sign_input { ($sighash_parts: expr, $input: expr, $htlc_idx: expr, $amount: expr) => { { - let (sig, redeemscript) = match self.key_storage { + let (sig, redeemscript, revocation_key) = match self.key_storage { Storage::Local { ref revocation_base_key, .. } => { let redeemscript = if $htlc_idx.is_none() { revokeable_redeemscript.clone() } else { let htlc = &per_commitment_option.unwrap()[$htlc_idx.unwrap()].0; @@ -1233,7 +1305,7 @@ impl ChannelMonitor { }; let sighash = hash_to_message!(&$sighash_parts.sighash_all(&$input, &redeemscript, $amount)[..]); let revocation_key = ignore_error!(chan_utils::derive_private_revocation_key(&self.secp_ctx, &per_commitment_key, &revocation_base_key)); - (self.secp_ctx.sign(&sighash, &revocation_key), redeemscript) + (self.secp_ctx.sign(&sighash, &revocation_key), redeemscript, revocation_key) }, Storage::Watchtower { .. } => { unimplemented!(); @@ -1246,7 +1318,8 @@ impl ChannelMonitor { } else { $input.witness.push(revocation_pubkey.serialize().to_vec()); } - $input.witness.push(redeemscript.into_bytes()); + $input.witness.push(redeemscript.clone().into_bytes()); + (redeemscript, revocation_key) } } } @@ -1273,10 +1346,9 @@ impl ChannelMonitor { }; if htlc.cltv_expiry > height + CLTV_SHARED_CLAIM_BUFFER { inputs.push(input); - htlc_idxs.push(Some(idx)); - values.push(tx.output[transaction_output_index as usize].value); - total_value += htlc.amount_msat / 1000; - input_descriptors.push(if htlc.offered { InputDescriptors::RevokedOfferedHTLC } else { InputDescriptors::RevokedReceivedHTLC }); + inputs_desc.push(if htlc.offered { InputDescriptors::RevokedOfferedHTLC } else { InputDescriptors::RevokedReceivedHTLC }); + inputs_info.push((Some(idx), tx.output[transaction_output_index as usize].value)); + total_value += tx.output[transaction_output_index as usize].value; } else { let mut single_htlc_tx = Transaction { version: 2, @@ -1288,10 +1360,15 @@ impl ChannelMonitor { }), }; let predicted_weight = single_htlc_tx.get_weight() + Self::get_witnesses_weight(&[if htlc.offered { InputDescriptors::RevokedOfferedHTLC } else { InputDescriptors::RevokedReceivedHTLC }]); - if subtract_high_prio_fee!(self, fee_estimator, single_htlc_tx.output[0].value, predicted_weight, tx.txid()) { + let mut used_feerate; + if subtract_high_prio_fee!(self, fee_estimator, single_htlc_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) { let sighash_parts = bip143::SighashComponents::new(&single_htlc_tx); - sign_input!(sighash_parts, single_htlc_tx.input[0], Some(idx), htlc.amount_msat / 1000); + let (redeemscript, revocation_key) = sign_input!(sighash_parts, single_htlc_tx.input[0], Some(idx), htlc.amount_msat / 1000); assert!(predicted_weight >= single_htlc_tx.get_weight()); + match self.our_claim_txn_waiting_first_conf.entry(single_htlc_tx.input[0].previous_output.clone()) { + hash_map::Entry::Occupied(_) => {}, + 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)); } + } txn_to_broadcast.push(single_htlc_tx); } } @@ -1355,18 +1432,22 @@ impl ChannelMonitor { input: inputs, output: outputs, }; - let predicted_weight = spend_tx.get_weight() + Self::get_witnesses_weight(&input_descriptors[..]); - if !subtract_high_prio_fee!(self, fee_estimator, spend_tx.output[0].value, predicted_weight, tx.txid()) { + let predicted_weight = spend_tx.get_weight() + Self::get_witnesses_weight(&inputs_desc[..]); + + let mut used_feerate; + if !subtract_high_prio_fee!(self, fee_estimator, spend_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) { return (txn_to_broadcast, (commitment_txid, watch_outputs), spendable_outputs); } - let mut values_drain = values.drain(..); let sighash_parts = bip143::SighashComponents::new(&spend_tx); - for (input, htlc_idx) in spend_tx.input.iter_mut().zip(htlc_idxs.iter()) { - let value = values_drain.next().unwrap(); - sign_input!(sighash_parts, input, htlc_idx, value); + for (input, info) in spend_tx.input.iter_mut().zip(inputs_info.iter()) { + let (redeemscript, revocation_key) = sign_input!(sighash_parts, input, info.0, info.1); + match self.our_claim_txn_waiting_first_conf.entry(input.previous_output.clone()) { + hash_map::Entry::Occupied(_) => {}, + 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)); } + } } assert!(predicted_weight >= spend_tx.get_weight()); @@ -1480,20 +1561,20 @@ impl ChannelMonitor { } let mut total_value = 0; - let mut values = Vec::new(); let mut inputs = Vec::new(); - let mut input_descriptors = Vec::new(); + let mut inputs_desc = Vec::new(); + let mut inputs_info = Vec::new(); macro_rules! sign_input { ($sighash_parts: expr, $input: expr, $amount: expr, $preimage: expr) => { { - let (sig, redeemscript) = match self.key_storage { + let (sig, redeemscript, htlc_key) = match self.key_storage { Storage::Local { ref htlc_base_key, .. } => { let htlc = &per_commitment_option.unwrap()[$input.sequence as usize].0; let redeemscript = chan_utils::get_htlc_redeemscript_with_explicit_keys(htlc, &a_htlc_key, &b_htlc_key, &revocation_pubkey); let sighash = hash_to_message!(&$sighash_parts.sighash_all(&$input, &redeemscript, $amount)[..]); let htlc_key = ignore_error!(chan_utils::derive_private_key(&self.secp_ctx, revocation_point, &htlc_base_key)); - (self.secp_ctx.sign(&sighash, &htlc_key), redeemscript) + (self.secp_ctx.sign(&sighash, &htlc_key), redeemscript, htlc_key) }, Storage::Watchtower { .. } => { unimplemented!(); @@ -1502,7 +1583,8 @@ impl ChannelMonitor { $input.witness.push(sig.serialize_der().to_vec()); $input.witness[0].push(SigHashType::All as u8); $input.witness.push($preimage); - $input.witness.push(redeemscript.into_bytes()); + $input.witness.push(redeemscript.clone().into_bytes()); + (redeemscript, htlc_key) } } } @@ -1527,9 +1609,9 @@ impl ChannelMonitor { }; if htlc.cltv_expiry > height + CLTV_SHARED_CLAIM_BUFFER { inputs.push(input); - values.push((tx.output[transaction_output_index as usize].value, payment_preimage)); - total_value += htlc.amount_msat / 1000; - input_descriptors.push(if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC }); + inputs_desc.push(if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC }); + inputs_info.push((payment_preimage, tx.output[transaction_output_index as usize].value)); + total_value += tx.output[transaction_output_index as usize].value; } else { let mut single_htlc_tx = Transaction { version: 2, @@ -1541,14 +1623,19 @@ impl ChannelMonitor { }), }; let predicted_weight = single_htlc_tx.get_weight() + Self::get_witnesses_weight(&[if htlc.offered { InputDescriptors::OfferedHTLC } else { InputDescriptors::ReceivedHTLC }]); - if subtract_high_prio_fee!(self, fee_estimator, single_htlc_tx.output[0].value, predicted_weight, tx.txid()) { + let mut used_feerate; + if subtract_high_prio_fee!(self, fee_estimator, single_htlc_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) { let sighash_parts = bip143::SighashComponents::new(&single_htlc_tx); - sign_input!(sighash_parts, single_htlc_tx.input[0], htlc.amount_msat / 1000, payment_preimage.0.to_vec()); + let (redeemscript, htlc_key) = sign_input!(sighash_parts, single_htlc_tx.input[0], htlc.amount_msat / 1000, payment_preimage.0.to_vec()); assert!(predicted_weight >= single_htlc_tx.get_weight()); spendable_outputs.push(SpendableOutputDescriptor::StaticOutput { outpoint: BitcoinOutPoint { txid: single_htlc_tx.txid(), vout: 0 }, output: single_htlc_tx.output[0].clone(), }); + match self.our_claim_txn_waiting_first_conf.entry(single_htlc_tx.input[0].previous_output.clone()) { + hash_map::Entry::Occupied(_) => {}, + 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)); } + } txn_to_broadcast.push(single_htlc_tx); } } @@ -1574,8 +1661,18 @@ impl ChannelMonitor { value: htlc.amount_msat / 1000, }), }; - let sighash_parts = bip143::SighashComponents::new(&timeout_tx); - sign_input!(sighash_parts, timeout_tx.input[0], htlc.amount_msat / 1000, vec![0]); + let predicted_weight = timeout_tx.get_weight() + Self::get_witnesses_weight(&[InputDescriptors::ReceivedHTLC]); + let mut used_feerate; + if subtract_high_prio_fee!(self, fee_estimator, timeout_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) { + let sighash_parts = bip143::SighashComponents::new(&timeout_tx); + let (redeemscript, htlc_key) = sign_input!(sighash_parts, timeout_tx.input[0], htlc.amount_msat / 1000, vec![0]); + assert!(predicted_weight >= timeout_tx.get_weight()); + //TODO: track SpendableOutputDescriptor + match self.our_claim_txn_waiting_first_conf.entry(timeout_tx.input[0].previous_output.clone()) { + hash_map::Entry::Occupied(_) => {}, + 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)); } + } + } txn_to_broadcast.push(timeout_tx); } } @@ -1593,19 +1690,23 @@ impl ChannelMonitor { input: inputs, output: outputs, }; - let predicted_weight = spend_tx.get_weight() + Self::get_witnesses_weight(&input_descriptors[..]); - if !subtract_high_prio_fee!(self, fee_estimator, spend_tx.output[0].value, predicted_weight, tx.txid()) { + + let mut predicted_weight = spend_tx.get_weight() + Self::get_witnesses_weight(&inputs_desc[..]); + + let mut used_feerate; + if !subtract_high_prio_fee!(self, fee_estimator, spend_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) { return (txn_to_broadcast, (commitment_txid, watch_outputs), spendable_outputs); } - let mut values_drain = values.drain(..); let sighash_parts = bip143::SighashComponents::new(&spend_tx); - for input in spend_tx.input.iter_mut() { - let value = values_drain.next().unwrap(); - sign_input!(sighash_parts, input, value.0, (value.1).0.to_vec()); + for (input, info) in spend_tx.input.iter_mut().zip(inputs_info.iter()) { + let (redeemscript, htlc_key) = sign_input!(sighash_parts, input, info.1, (info.0).0.to_vec()); + match self.our_claim_txn_waiting_first_conf.entry(input.previous_output.clone()) { + hash_map::Entry::Occupied(_) => {}, + 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)); } + } } - assert!(predicted_weight >= spend_tx.get_weight()); spendable_outputs.push(SpendableOutputDescriptor::StaticOutput { outpoint: BitcoinOutPoint { txid: spend_tx.txid(), vout: 0 }, @@ -1620,7 +1721,7 @@ impl ChannelMonitor { } /// Attempts to claim a remote HTLC-Success/HTLC-Timeout's outputs using the revocation key - fn check_spend_remote_htlc(&self, tx: &Transaction, commitment_number: u64, fee_estimator: &FeeEstimator) -> (Option, Option) { + fn check_spend_remote_htlc(&mut self, tx: &Transaction, commitment_number: u64, height: u32, fee_estimator: &FeeEstimator) -> (Option, Option) { if tx.input.len() != 1 || tx.output.len() != 1 { return (None, None) } @@ -1682,17 +1783,18 @@ impl ChannelMonitor { output: outputs, }; let predicted_weight = spend_tx.get_weight() + Self::get_witnesses_weight(&[InputDescriptors::RevokedOutput]); - if !subtract_high_prio_fee!(self, fee_estimator, spend_tx.output[0].value, predicted_weight, tx.txid()) { + let mut used_feerate; + if !subtract_high_prio_fee!(self, fee_estimator, spend_tx.output[0].value, predicted_weight, tx.txid(), used_feerate) { return (None, None); } let sighash_parts = bip143::SighashComponents::new(&spend_tx); - let sig = match self.key_storage { + let (sig, revocation_key) = match self.key_storage { Storage::Local { ref revocation_base_key, .. } => { let sighash = hash_to_message!(&sighash_parts.sighash_all(&spend_tx.input[0], &redeemscript, amount)[..]); let revocation_key = ignore_error!(chan_utils::derive_private_revocation_key(&self.secp_ctx, &per_commitment_key, &revocation_base_key)); - self.secp_ctx.sign(&sighash, &revocation_key) + (self.secp_ctx.sign(&sighash, &revocation_key), revocation_key) } Storage::Watchtower { .. } => { unimplemented!(); @@ -1701,19 +1803,24 @@ impl ChannelMonitor { spend_tx.input[0].witness.push(sig.serialize_der().to_vec()); spend_tx.input[0].witness[0].push(SigHashType::All as u8); spend_tx.input[0].witness.push(vec!(1)); - spend_tx.input[0].witness.push(redeemscript.into_bytes()); + spend_tx.input[0].witness.push(redeemscript.clone().into_bytes()); assert!(predicted_weight >= spend_tx.get_weight()); let outpoint = BitcoinOutPoint { txid: spend_tx.txid(), vout: 0 }; let output = spend_tx.output[0].clone(); + match self.our_claim_txn_waiting_first_conf.entry(spend_tx.input[0].previous_output.clone()) { + hash_map::Entry::Occupied(_) => {}, + 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)); } + } (Some(spend_tx), Some(SpendableOutputDescriptor::StaticOutput { outpoint, output })) } else { (None, None) } } - fn broadcast_by_local_state(&self, local_tx: &LocalSignedTx, per_commitment_point: &Option, delayed_payment_base_key: &Option) -> (Vec, Vec, Vec) { + fn broadcast_by_local_state(&self, local_tx: &LocalSignedTx, per_commitment_point: &Option, delayed_payment_base_key: &Option, height: u32) -> (Vec, Vec, Vec, Vec<(BitcoinOutPoint, (u32, TxMaterial, u64))>) { let mut res = Vec::with_capacity(local_tx.htlc_outputs.len()); let mut spendable_outputs = Vec::with_capacity(local_tx.htlc_outputs.len()); let mut watch_outputs = Vec::with_capacity(local_tx.htlc_outputs.len()); + let mut pending_claims = Vec::with_capacity(local_tx.htlc_outputs.len()); macro_rules! add_dynamic_output { ($father_tx: expr, $vout: expr) => { @@ -1758,9 +1865,11 @@ impl ChannelMonitor { htlc_timeout_tx.input[0].witness[2].push(SigHashType::All as u8); htlc_timeout_tx.input[0].witness.push(Vec::new()); - htlc_timeout_tx.input[0].witness.push(chan_utils::get_htlc_redeemscript_with_explicit_keys(htlc, &local_tx.a_htlc_key, &local_tx.b_htlc_key, &local_tx.revocation_key).into_bytes()); + let htlc_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(htlc, &local_tx.a_htlc_key, &local_tx.b_htlc_key, &local_tx.revocation_key); + htlc_timeout_tx.input[0].witness.push(htlc_script.clone().into_bytes()); add_dynamic_output!(htlc_timeout_tx, 0); + 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))); res.push(htlc_timeout_tx); } else { if let Some(payment_preimage) = self.payment_preimages.get(&htlc.payment_hash) { @@ -1775,9 +1884,11 @@ impl ChannelMonitor { htlc_success_tx.input[0].witness[2].push(SigHashType::All as u8); htlc_success_tx.input[0].witness.push(payment_preimage.0.to_vec()); - htlc_success_tx.input[0].witness.push(chan_utils::get_htlc_redeemscript_with_explicit_keys(htlc, &local_tx.a_htlc_key, &local_tx.b_htlc_key, &local_tx.revocation_key).into_bytes()); + let htlc_script = chan_utils::get_htlc_redeemscript_with_explicit_keys(htlc, &local_tx.a_htlc_key, &local_tx.b_htlc_key, &local_tx.revocation_key); + htlc_success_tx.input[0].witness.push(htlc_script.clone().into_bytes()); add_dynamic_output!(htlc_success_tx, 0); + 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))); res.push(htlc_success_tx); } } @@ -1786,7 +1897,7 @@ impl ChannelMonitor { } } - (res, spendable_outputs, watch_outputs) + (res, spendable_outputs, watch_outputs, pending_claims) } /// Attempts to claim any claimable HTLCs in a commitment transaction which was not (yet) @@ -1826,6 +1937,12 @@ impl ChannelMonitor { local_txn.append(&mut $updates.0); spendable_outputs.append(&mut $updates.1); watch_outputs.append(&mut $updates.2); + for claim in $updates.3 { + match self.our_claim_txn_waiting_first_conf.entry(claim.0) { + hash_map::Entry::Occupied(_) => {}, + hash_map::Entry::Vacant(entry) => { entry.insert(claim.1); } + } + } } } @@ -1838,10 +1955,10 @@ impl ChannelMonitor { log_trace!(self, "Got latest local commitment tx broadcast, searching for available HTLCs to claim"); match self.key_storage { Storage::Local { ref delayed_payment_base_key, ref latest_per_commitment_point, .. } => { - append_onchain_update!(self.broadcast_by_local_state(local_tx, latest_per_commitment_point, &Some(*delayed_payment_base_key))); + append_onchain_update!(self.broadcast_by_local_state(local_tx, latest_per_commitment_point, &Some(*delayed_payment_base_key), height)); }, Storage::Watchtower { .. } => { - append_onchain_update!(self.broadcast_by_local_state(local_tx, &None, &None)); + append_onchain_update!(self.broadcast_by_local_state(local_tx, &None, &None, height)); } } } @@ -1852,10 +1969,10 @@ impl ChannelMonitor { log_trace!(self, "Got previous local commitment tx broadcast, searching for available HTLCs to claim"); match self.key_storage { Storage::Local { ref delayed_payment_base_key, ref prev_latest_per_commitment_point, .. } => { - append_onchain_update!(self.broadcast_by_local_state(local_tx, prev_latest_per_commitment_point, &Some(*delayed_payment_base_key))); + append_onchain_update!(self.broadcast_by_local_state(local_tx, prev_latest_per_commitment_point, &Some(*delayed_payment_base_key), height)); }, Storage::Watchtower { .. } => { - append_onchain_update!(self.broadcast_by_local_state(local_tx, &None, &None)); + append_onchain_update!(self.broadcast_by_local_state(local_tx, &None, &None, height)); } } } @@ -1917,7 +2034,9 @@ impl ChannelMonitor { let mut res = vec![local_tx.tx.clone()]; match self.key_storage { Storage::Local { ref delayed_payment_base_key, ref prev_latest_per_commitment_point, .. } => { - res.append(&mut self.broadcast_by_local_state(local_tx, prev_latest_per_commitment_point, &Some(*delayed_payment_base_key)).0); + res.append(&mut self.broadcast_by_local_state(local_tx, prev_latest_per_commitment_point, &Some(*delayed_payment_base_key), 0).0); + // We throw away the generated waiting_first_conf data as we aren't (yet) confirmed and we don't actually know what the caller wants to do. + // The data will be re-generated and tracked in check_spend_local_transaction if we get a confirmation. }, _ => panic!("Can only broadcast by local channelmonitor"), }; @@ -1969,7 +2088,7 @@ impl ChannelMonitor { } } else { if let Some(&(commitment_number, _)) = self.remote_commitment_txn_on_chain.get(&prevout.txid) { - let (tx, spendable_output) = self.check_spend_remote_htlc(tx, commitment_number, fee_estimator); + let (tx, spendable_output) = self.check_spend_remote_htlc(tx, commitment_number, height, fee_estimator); if let Some(tx) = tx { txn.push(tx); } @@ -1989,14 +2108,37 @@ impl ChannelMonitor { if updated.len() > 0 { htlc_updated.append(&mut updated); } + for inp in &tx.input { + if self.our_claim_txn_waiting_first_conf.contains_key(&inp.previous_output) { + match self.onchain_events_waiting_threshold_conf.entry(height + ANTI_REORG_DELAY - 1) { + hash_map::Entry::Occupied(mut entry) => { + let e = entry.get_mut(); + e.retain(|ref event| { + match **event { + OnchainEvent::Claim { outpoint } => { + return outpoint != inp.previous_output + }, + _ => return true + } + }); + e.push(OnchainEvent::Claim { outpoint: inp.previous_output.clone()}); + } + hash_map::Entry::Vacant(entry) => { + entry.insert(vec![OnchainEvent::Claim { outpoint: inp.previous_output.clone()}]); + } + } + } + } } + let mut pending_claims = Vec::new(); if let Some(ref cur_local_tx) = self.current_local_signed_commitment_tx { if self.would_broadcast_at_height(height) { broadcaster.broadcast_transaction(&cur_local_tx.tx); match self.key_storage { Storage::Local { ref delayed_payment_base_key, ref latest_per_commitment_point, .. } => { - let (txs, mut spendable_output, new_outputs) = self.broadcast_by_local_state(&cur_local_tx, latest_per_commitment_point, &Some(*delayed_payment_base_key)); + let (txs, mut spendable_output, new_outputs, mut pending_txn) = self.broadcast_by_local_state(&cur_local_tx, latest_per_commitment_point, &Some(*delayed_payment_base_key), height); spendable_outputs.append(&mut spendable_output); + pending_claims.append(&mut pending_txn); if !new_outputs.is_empty() { watch_outputs.push((cur_local_tx.txid.clone(), new_outputs)); } @@ -2005,8 +2147,9 @@ impl ChannelMonitor { } }, Storage::Watchtower { .. } => { - let (txs, mut spendable_output, new_outputs) = self.broadcast_by_local_state(&cur_local_tx, &None, &None); + let (txs, mut spendable_output, new_outputs, mut pending_txn) = self.broadcast_by_local_state(&cur_local_tx, &None, &None, height); spendable_outputs.append(&mut spendable_output); + pending_claims.append(&mut pending_txn); if !new_outputs.is_empty() { watch_outputs.push((cur_local_tx.txid.clone(), new_outputs)); } @@ -2017,10 +2160,17 @@ impl ChannelMonitor { } } } + for claim in pending_claims { + match self.our_claim_txn_waiting_first_conf.entry(claim.0) { + hash_map::Entry::Occupied(_) => {}, + hash_map::Entry::Vacant(entry) => { entry.insert(claim.1); } + } + } if let Some(events) = self.onchain_events_waiting_threshold_conf.remove(&height) { for ev in events { match ev { - OnchainEvent::Claim { outpoint: _ } => { + OnchainEvent::Claim { outpoint } => { + self.our_claim_txn_waiting_first_conf.remove(&outpoint); }, OnchainEvent::HTLCUpdate { htlc_update } => { log_trace!(self, "HTLC {} failure update has got enough confirmations to be passed upstream", log_bytes!((htlc_update.1).0)); @@ -2029,6 +2179,7 @@ impl ChannelMonitor { } } } + //TODO: iter on buffered TxMaterial in our_claim_txn_waiting_first_conf, if block timer is expired generate a bumped claim tx (RBF or CPFP accordingly) self.last_block_hash = block_hash.clone(); (watch_outputs, spendable_outputs, htlc_updated) } @@ -2039,6 +2190,7 @@ impl ChannelMonitor { //- htlc update there as failure-trigger tx (revoked commitment tx, non-revoked commitment tx, HTLC-timeout tx) has been disconnected //- our claim tx on a commitment tx output } + self.our_claim_txn_waiting_first_conf.retain(|_, ref mut v| if v.0 == height + 3 { false } else { true }); self.last_block_hash = block_hash.clone(); } @@ -2455,6 +2607,61 @@ impl ReadableArgs> for (Sha256dHash, ChannelM let last_block_hash: Sha256dHash = Readable::read(reader)?; let destination_script = Readable::read(reader)?; + let our_claim_txn_waiting_first_conf_len: u64 = Readable::read(reader)?; + let mut our_claim_txn_waiting_first_conf = HashMap::with_capacity(cmp::min(our_claim_txn_waiting_first_conf_len as usize, MAX_ALLOC_SIZE / 128)); + for _ in 0..our_claim_txn_waiting_first_conf_len { + let outpoint = Readable::read(reader)?; + let height_target = Readable::read(reader)?; + let tx_material = match >::read(reader)? { + 0 => { + let script = Readable::read(reader)?; + let pubkey = Readable::read(reader)?; + let key = Readable::read(reader)?; + let is_htlc = match >::read(reader)? { + 0 => true, + 1 => false, + _ => return Err(DecodeError::InvalidValue), + }; + let amount = Readable::read(reader)?; + TxMaterial::Revoked { + script, + pubkey, + key, + is_htlc, + amount + } + }, + 1 => { + let script = Readable::read(reader)?; + let key = Readable::read(reader)?; + let preimage = Readable::read(reader)?; + let amount = Readable::read(reader)?; + TxMaterial::RemoteHTLC { + script, + key, + preimage, + amount + } + }, + 2 => { + let script = Readable::read(reader)?; + let their_sig = Readable::read(reader)?; + let our_sig = Readable::read(reader)?; + let preimage = Readable::read(reader)?; + let amount = Readable::read(reader)?; + TxMaterial::LocalHTLC { + script, + sigs: (their_sig, our_sig), + preimage, + amount + } + } + _ => return Err(DecodeError::InvalidValue), + }; + let last_fee = Readable::read(reader)?; + our_claim_txn_waiting_first_conf.insert(outpoint, (height_target, tx_material, last_fee)); + } + let waiting_threshold_conf_len: u64 = Readable::read(reader)?; let mut onchain_events_waiting_threshold_conf = HashMap::with_capacity(cmp::min(waiting_threshold_conf_len as usize, MAX_ALLOC_SIZE / 128)); for _ in 0..waiting_threshold_conf_len { @@ -2507,6 +2714,8 @@ impl ReadableArgs> for (Sha256dHash, ChannelM destination_script, + our_claim_txn_waiting_first_conf, + onchain_events_waiting_threshold_conf, last_block_hash,