X-Git-Url: http://git.bitcoin.ninja/index.cgi?a=blobdiff_plain;f=lightning%2Fsrc%2Fchain%2Fpackage.rs;h=866ff66b48fd6d95ddc8f470782984138e2b435e;hb=40b1d4f027c8827f057d77abbc4f3b46b1b67abd;hp=c945d8909da4a61cc656a634a75f0b130eb342d2;hpb=8c6cb9953a3b00ce3da25fbdfd8ada0ec48fc63f;p=rust-lightning diff --git a/lightning/src/chain/package.rs b/lightning/src/chain/package.rs index c945d890..866ff66b 100644 --- a/lightning/src/chain/package.rs +++ b/lightning/src/chain/package.rs @@ -86,7 +86,7 @@ const HIGH_FREQUENCY_BUMP_INTERVAL: u32 = 1; /// /// CSV and pubkeys are used as part of a witnessScript redeeming a balance output, amount is used /// as part of the signature hash and revocation secret to generate a satisfying witness. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub(crate) struct RevokedOutput { per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, @@ -129,7 +129,7 @@ impl_writeable_tlv_based!(RevokedOutput, { /// /// CSV is used as part of a witnessScript redeeming a balance output, amount is used as part /// of the signature hash and revocation secret to generate a satisfying witness. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub(crate) struct RevokedHTLCOutput { per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, @@ -171,29 +171,36 @@ impl_writeable_tlv_based!(RevokedHTLCOutput, { /// witnessScript. /// /// The preimage is used as part of the witness. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub(crate) struct CounterpartyOfferedHTLCOutput { per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, preimage: PaymentPreimage, - htlc: HTLCOutputInCommitment + htlc: HTLCOutputInCommitment, + opt_anchors: Option<()>, } impl CounterpartyOfferedHTLCOutput { - pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, preimage: PaymentPreimage, htlc: HTLCOutputInCommitment) -> Self { + pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, preimage: PaymentPreimage, htlc: HTLCOutputInCommitment, opt_anchors: bool) -> Self { CounterpartyOfferedHTLCOutput { per_commitment_point, counterparty_delayed_payment_base_key, counterparty_htlc_base_key, preimage, - htlc + htlc, + opt_anchors: if opt_anchors { Some(()) } else { None }, } } + + fn opt_anchors(&self) -> bool { + self.opt_anchors.is_some() + } } impl_writeable_tlv_based!(CounterpartyOfferedHTLCOutput, { (0, per_commitment_point, required), + (1, opt_anchors, option), (2, counterparty_delayed_payment_base_key, required), (4, counterparty_htlc_base_key, required), (6, preimage, required), @@ -204,27 +211,34 @@ impl_writeable_tlv_based!(CounterpartyOfferedHTLCOutput, { /// /// HTLCOutputInCommitment (hash, timelock, directon) and pubkeys are used to generate a suitable /// witnessScript. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub(crate) struct CounterpartyReceivedHTLCOutput { per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, - htlc: HTLCOutputInCommitment + htlc: HTLCOutputInCommitment, + opt_anchors: Option<()>, } impl CounterpartyReceivedHTLCOutput { - pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, htlc: HTLCOutputInCommitment) -> Self { + pub(crate) fn build(per_commitment_point: PublicKey, counterparty_delayed_payment_base_key: PublicKey, counterparty_htlc_base_key: PublicKey, htlc: HTLCOutputInCommitment, opt_anchors: bool) -> Self { CounterpartyReceivedHTLCOutput { per_commitment_point, counterparty_delayed_payment_base_key, counterparty_htlc_base_key, - htlc + htlc, + opt_anchors: if opt_anchors { Some(()) } else { None }, } } + + fn opt_anchors(&self) -> bool { + self.opt_anchors.is_some() + } } impl_writeable_tlv_based!(CounterpartyReceivedHTLCOutput, { (0, per_commitment_point, required), + (1, opt_anchors, option), (2, counterparty_delayed_payment_base_key, required), (4, counterparty_htlc_base_key, required), (6, htlc, required), @@ -234,7 +248,7 @@ impl_writeable_tlv_based!(CounterpartyReceivedHTLCOutput, { /// /// Either offered or received, the amount is always used as part of the bip143 sighash. /// Preimage is only included as part of the witness in former case. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub(crate) struct HolderHTLCOutput { preimage: Option, amount: u64, @@ -269,28 +283,39 @@ impl_writeable_tlv_based!(HolderHTLCOutput, { /// A struct to describe the channel output on the funding transaction. /// /// witnessScript is used as part of the witness redeeming the funding utxo. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub(crate) struct HolderFundingOutput { funding_redeemscript: Script, + funding_amount: Option, + opt_anchors: Option<()>, } + impl HolderFundingOutput { - pub(crate) fn build(funding_redeemscript: Script) -> Self { + pub(crate) fn build(funding_redeemscript: Script, funding_amount: u64, opt_anchors: bool) -> Self { HolderFundingOutput { funding_redeemscript, + funding_amount: Some(funding_amount), + opt_anchors: if opt_anchors { Some(()) } else { None }, } } + + fn opt_anchors(&self) -> bool { + self.opt_anchors.is_some() + } } impl_writeable_tlv_based!(HolderFundingOutput, { (0, funding_redeemscript, required), + (1, opt_anchors, option), + (3, funding_amount, option), }); /// A wrapper encapsulating all in-protocol differing outputs types. /// /// The generic API offers access to an outputs common attributes or allow transformation such as /// finalizing an input claiming the output. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub(crate) enum PackageSolvingData { RevokedOutput(RevokedOutput), RevokedHTLCOutput(RevokedHTLCOutput), @@ -303,24 +328,27 @@ pub(crate) enum PackageSolvingData { impl PackageSolvingData { fn amount(&self) -> u64 { let amt = match self { - PackageSolvingData::RevokedOutput(ref outp) => { outp.amount }, - PackageSolvingData::RevokedHTLCOutput(ref outp) => { outp.amount }, - PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => { outp.htlc.amount_msat / 1000 }, - PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => { outp.htlc.amount_msat / 1000 }, + PackageSolvingData::RevokedOutput(ref outp) => outp.amount, + PackageSolvingData::RevokedHTLCOutput(ref outp) => outp.amount, + PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => outp.htlc.amount_msat / 1000, + PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => outp.htlc.amount_msat / 1000, // Note: Currently, amounts of holder outputs spending witnesses aren't used // as we can't malleate spending package to increase their feerate. This // should change with the remaining anchor output patchset. - PackageSolvingData::HolderHTLCOutput(..) => { unreachable!() }, - PackageSolvingData::HolderFundingOutput(..) => { unreachable!() }, + PackageSolvingData::HolderHTLCOutput(..) => unreachable!(), + PackageSolvingData::HolderFundingOutput(ref outp) => { + debug_assert!(outp.opt_anchors()); + outp.funding_amount.unwrap() + } }; amt } - fn weight(&self, opt_anchors: bool) -> usize { + fn weight(&self) -> usize { let weight = match self { PackageSolvingData::RevokedOutput(ref outp) => { outp.weight as usize }, PackageSolvingData::RevokedHTLCOutput(ref outp) => { outp.weight as usize }, - PackageSolvingData::CounterpartyOfferedHTLCOutput(..) => { weight_offered_htlc(opt_anchors) as usize }, - PackageSolvingData::CounterpartyReceivedHTLCOutput(..) => { weight_received_htlc(opt_anchors) as usize }, + PackageSolvingData::CounterpartyOfferedHTLCOutput(ref outp) => { weight_offered_htlc(outp.opt_anchors()) as usize }, + PackageSolvingData::CounterpartyReceivedHTLCOutput(ref outp) => { weight_received_htlc(outp.opt_anchors()) as usize }, // Note: Currently, weights of holder outputs spending witnesses aren't used // as we can't malleate spending package to increase their feerate. This // should change with the remaining anchor output patchset. @@ -444,7 +472,7 @@ impl_writeable_tlv_based_enum!(PackageSolvingData, ; /// A malleable package might be aggregated with other packages to save on fees. /// A untractable package has been counter-signed and aggregable will break cached counterparty /// signatures. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub(crate) enum PackageMalleability { Malleable, Untractable, @@ -459,7 +487,7 @@ pub(crate) enum PackageMalleability { /// /// As packages are time-sensitive, we fee-bump and rebroadcast them at scheduled intervals. /// Failing to confirm a package translate as a loss of funds for the user. -#[derive(Clone, PartialEq)] +#[derive(Clone, PartialEq, Eq)] pub struct PackageTemplate { // List of onchain outputs and solving data to generate satisfying witnesses. inputs: Vec<(BitcoinOutPoint, PackageSolvingData)>, @@ -594,13 +622,13 @@ impl PackageTemplate { self.inputs.iter().map(|(_, outp)| outp.absolute_tx_timelock(self.height_original)) .max().expect("There must always be at least one output to spend in a PackageTemplate") } - pub(crate) fn package_weight(&self, destination_script: &Script, opt_anchors: bool) -> usize { + pub(crate) fn package_weight(&self, destination_script: &Script) -> usize { let mut inputs_weight = 0; let mut witnesses_weight = 2; // count segwit flags for (_, outp) in self.inputs.iter() { // previous_out_point: 36 bytes ; var_int: 1 byte ; sequence: 4 bytes inputs_weight += 41 * WITNESS_SCALE_FACTOR; - witnesses_weight += outp.weight(opt_anchors); + witnesses_weight += outp.weight(); } // version: 4 bytes ; count_tx_in: 1 byte ; count_tx_out: 1 byte ; lock_time: 4 bytes let transaction_weight = 10 * WITNESS_SCALE_FACTOR; @@ -608,47 +636,46 @@ impl PackageTemplate { let output_weight = (8 + 1 + destination_script.len()) * WITNESS_SCALE_FACTOR; inputs_weight + witnesses_weight + transaction_weight + output_weight } - pub(crate) fn finalize_package(&self, onchain_handler: &mut OnchainTxHandler, value: u64, destination_script: Script, logger: &L) -> Option - where L::Target: Logger, - { - match self.malleability { - PackageMalleability::Malleable => { - let mut bumped_tx = Transaction { - version: 2, - lock_time: PackedLockTime::ZERO, - input: vec![], - output: vec![TxOut { - script_pubkey: destination_script, - value, - }], - }; - for (outpoint, _) in self.inputs.iter() { - bumped_tx.input.push(TxIn { - previous_output: *outpoint, - script_sig: Script::new(), - sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, - witness: Witness::new(), - }); - } - for (i, (outpoint, out)) in self.inputs.iter().enumerate() { - log_debug!(logger, "Adding claiming input for outpoint {}:{}", outpoint.txid, outpoint.vout); - if !out.finalize_input(&mut bumped_tx, i, onchain_handler) { return None; } - } - log_debug!(logger, "Finalized transaction {} ready to broadcast", bumped_tx.txid()); - return Some(bumped_tx); - }, - PackageMalleability::Untractable => { - debug_assert_eq!(value, 0, "value is ignored for non-malleable packages, should be zero to ensure callsites are correct"); - if let Some((outpoint, outp)) = self.inputs.first() { - if let Some(final_tx) = outp.get_finalized_tx(outpoint, onchain_handler) { - log_debug!(logger, "Adding claiming input for outpoint {}:{}", outpoint.txid, outpoint.vout); - log_debug!(logger, "Finalized transaction {} ready to broadcast", final_tx.txid()); - return Some(final_tx); - } - return None; - } else { panic!("API Error: Package must not be inputs empty"); } - }, + pub(crate) fn finalize_malleable_package( + &self, onchain_handler: &mut OnchainTxHandler, value: u64, destination_script: Script, logger: &L, + ) -> Option where L::Target: Logger { + debug_assert!(self.is_malleable()); + let mut bumped_tx = Transaction { + version: 2, + lock_time: PackedLockTime::ZERO, + input: vec![], + output: vec![TxOut { + script_pubkey: destination_script, + value, + }], + }; + for (outpoint, _) in self.inputs.iter() { + bumped_tx.input.push(TxIn { + previous_output: *outpoint, + script_sig: Script::new(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + witness: Witness::new(), + }); } + for (i, (outpoint, out)) in self.inputs.iter().enumerate() { + log_debug!(logger, "Adding claiming input for outpoint {}:{}", outpoint.txid, outpoint.vout); + if !out.finalize_input(&mut bumped_tx, i, onchain_handler) { return None; } + } + log_debug!(logger, "Finalized transaction {} ready to broadcast", bumped_tx.txid()); + Some(bumped_tx) + } + pub(crate) fn finalize_untractable_package( + &self, onchain_handler: &mut OnchainTxHandler, logger: &L, + ) -> Option where L::Target: Logger { + debug_assert!(!self.is_malleable()); + if let Some((outpoint, outp)) = self.inputs.first() { + if let Some(final_tx) = outp.get_finalized_tx(outpoint, onchain_handler) { + log_debug!(logger, "Adding claiming input for outpoint {}:{}", outpoint.txid, outpoint.vout); + log_debug!(logger, "Finalized transaction {} ready to broadcast", final_tx.txid()); + return Some(final_tx); + } + return None; + } else { panic!("API Error: Package must not be inputs empty"); } } /// In LN, output claimed are time-sensitive, which means we have to spend them before reaching some timelock expiration. At in-channel /// output detection, we generate a first version of a claim tx and associate to it a height timer. A height timer is an absolute block @@ -873,26 +900,26 @@ mod tests { } macro_rules! dumb_counterparty_output { - ($secp_ctx: expr, $amt: expr) => { + ($secp_ctx: expr, $amt: expr, $opt_anchors: expr) => { { let dumb_scalar = SecretKey::from_slice(&hex::decode("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap(); let dumb_point = PublicKey::from_secret_key(&$secp_ctx, &dumb_scalar); let hash = PaymentHash([1; 32]); let htlc = HTLCOutputInCommitment { offered: true, amount_msat: $amt, cltv_expiry: 0, payment_hash: hash, transaction_output_index: None }; - PackageSolvingData::CounterpartyReceivedHTLCOutput(CounterpartyReceivedHTLCOutput::build(dumb_point, dumb_point, dumb_point, htlc)) + PackageSolvingData::CounterpartyReceivedHTLCOutput(CounterpartyReceivedHTLCOutput::build(dumb_point, dumb_point, dumb_point, htlc, $opt_anchors)) } } } macro_rules! dumb_counterparty_offered_output { - ($secp_ctx: expr, $amt: expr) => { + ($secp_ctx: expr, $amt: expr, $opt_anchors: expr) => { { let dumb_scalar = SecretKey::from_slice(&hex::decode("0101010101010101010101010101010101010101010101010101010101010101").unwrap()[..]).unwrap(); let dumb_point = PublicKey::from_secret_key(&$secp_ctx, &dumb_scalar); let hash = PaymentHash([1; 32]); let preimage = PaymentPreimage([2;32]); let htlc = HTLCOutputInCommitment { offered: false, amount_msat: $amt, cltv_expiry: 1000, payment_hash: hash, transaction_output_index: None }; - PackageSolvingData::CounterpartyOfferedHTLCOutput(CounterpartyOfferedHTLCOutput::build(dumb_point, dumb_point, dumb_point, preimage, htlc)) + PackageSolvingData::CounterpartyOfferedHTLCOutput(CounterpartyOfferedHTLCOutput::build(dumb_point, dumb_point, dumb_point, preimage, htlc, $opt_anchors)) } } } @@ -987,7 +1014,7 @@ mod tests { let txid = Txid::from_hex("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap(); let secp_ctx = Secp256k1::new(); let revk_outp = dumb_revk_output!(secp_ctx); - let counterparty_outp = dumb_counterparty_output!(secp_ctx, 0); + let counterparty_outp = dumb_counterparty_output!(secp_ctx, 0, false); let mut revoked_package = PackageTemplate::build_package(txid, 0, revk_outp, 1000, true, 100); let counterparty_package = PackageTemplate::build_package(txid, 1, counterparty_outp, 1000, true, 100); @@ -1051,7 +1078,7 @@ mod tests { fn test_package_amounts() { let txid = Txid::from_hex("c2d4449afa8d26140898dd54d3390b057ba2a5afcf03ba29d7dc0d8b9ffe966e").unwrap(); let secp_ctx = Secp256k1::new(); - let counterparty_outp = dumb_counterparty_output!(secp_ctx, 1_000_000); + let counterparty_outp = dumb_counterparty_output!(secp_ctx, 1_000_000, false); let package = PackageTemplate::build_package(txid, 0, counterparty_outp, 1000, true, 100); assert_eq!(package.package_amount(), 1000); @@ -1068,24 +1095,22 @@ mod tests { { let revk_outp = dumb_revk_output!(secp_ctx); let package = PackageTemplate::build_package(txid, 0, revk_outp, 0, true, 100); - for &opt_anchors in [false, true].iter() { - assert_eq!(package.package_weight(&Script::new(), opt_anchors), weight_sans_output + WEIGHT_REVOKED_OUTPUT as usize); - } + assert_eq!(package.package_weight(&Script::new()), weight_sans_output + WEIGHT_REVOKED_OUTPUT as usize); } { - let counterparty_outp = dumb_counterparty_output!(secp_ctx, 1_000_000); - let package = PackageTemplate::build_package(txid, 0, counterparty_outp, 1000, true, 100); for &opt_anchors in [false, true].iter() { - assert_eq!(package.package_weight(&Script::new(), opt_anchors), weight_sans_output + weight_received_htlc(opt_anchors) as usize); + let counterparty_outp = dumb_counterparty_output!(secp_ctx, 1_000_000, opt_anchors); + let package = PackageTemplate::build_package(txid, 0, counterparty_outp, 1000, true, 100); + assert_eq!(package.package_weight(&Script::new()), weight_sans_output + weight_received_htlc(opt_anchors) as usize); } } { - let counterparty_outp = dumb_counterparty_offered_output!(secp_ctx, 1_000_000); - let package = PackageTemplate::build_package(txid, 0, counterparty_outp, 1000, true, 100); for &opt_anchors in [false, true].iter() { - assert_eq!(package.package_weight(&Script::new(), opt_anchors), weight_sans_output + weight_offered_htlc(opt_anchors) as usize); + let counterparty_outp = dumb_counterparty_offered_output!(secp_ctx, 1_000_000, opt_anchors); + let package = PackageTemplate::build_package(txid, 0, counterparty_outp, 1000, true, 100); + assert_eq!(package.package_weight(&Script::new()), weight_sans_output + weight_offered_htlc(opt_anchors) as usize); } } }