Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lightning/src/events/bump_transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,12 @@ pub struct Utxo {
pub satisfaction_weight: u64,
}

impl_writeable_tlv_based!(Utxo, {
(1, outpoint, required),
(3, output, required),
(5, satisfaction_weight, required),
});

impl Utxo {
/// Returns a `Utxo` with the `satisfaction_weight` estimate for a legacy P2PKH output.
pub fn new_p2pkh(outpoint: OutPoint, value: Amount, pubkey_hash: &PubkeyHash) -> Self {
Expand Down
161 changes: 127 additions & 34 deletions lightning/src/ln/channel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2448,13 +2448,46 @@ impl PendingSplice {
}
}

pub(crate) struct SpliceInstructions {
adjusted_funding_contribution: SignedAmount,
our_funding_inputs: Vec<FundingTxInput>,
our_funding_outputs: Vec<TxOut>,
change_script: Option<ScriptBuf>,
funding_feerate_per_kw: u32,
locktime: u32,
original_funding_txo: OutPoint,
}

impl_writeable_tlv_based!(SpliceInstructions, {
(1, adjusted_funding_contribution, required),
(3, our_funding_inputs, required_vec),
(5, our_funding_outputs, required_vec),
(7, change_script, option),
(9, funding_feerate_per_kw, required),
(11, locktime, required),
(13, original_funding_txo, required),
});

pub(crate) enum QuiescentAction {
// TODO: Make this test-only once we have another variant (as some code requires *a* variant).
Splice(SpliceInstructions),
#[cfg(any(test, fuzzing))]
DoNothing,
}

pub(crate) enum StfuResponse {
Stfu(msgs::Stfu),
#[cfg_attr(not(splicing), allow(unused))]
SpliceInit(msgs::SpliceInit),
}

#[cfg(any(test, fuzzing))]
impl_writeable_tlv_based_enum_upgradable!(QuiescentAction,
(99, DoNothing) => {},
(0, DoNothing) => {},
{1, Splice} => (),
);
#[cfg(not(any(test, fuzzing)))]
impl_writeable_tlv_based_enum_upgradable!(QuiescentAction,,
{1, Splice} => (),
);

/// Wrapper around a [`Transaction`] useful for caching the result of [`Transaction::compute_txid`].
Expand Down Expand Up @@ -5982,7 +6015,7 @@ fn get_v2_channel_reserve_satoshis(channel_value_satoshis: u64, dust_limit_satos
fn check_splice_contribution_sufficient(
channel_balance: Amount, contribution: &SpliceContribution, is_initiator: bool,
funding_feerate: FeeRate,
) -> Result<Amount, ChannelError> {
) -> Result<Amount, String> {
let contribution_amount = contribution.value();
if contribution_amount < SignedAmount::ZERO {
let estimated_fee = Amount::from_sat(estimate_v2_funding_transaction_fee(
Expand All @@ -5996,10 +6029,10 @@ fn check_splice_contribution_sufficient(
if channel_balance >= contribution_amount.unsigned_abs() + estimated_fee {
Ok(estimated_fee)
} else {
Err(ChannelError::Warn(format!(
"Available channel balance {} is lower than needed for splicing out {}, considering fees of {}",
channel_balance, contribution_amount.unsigned_abs(), estimated_fee,
)))
Err(format!(
"Available channel balance {channel_balance} is lower than needed for splicing out {}, considering fees of {estimated_fee}",
contribution_amount.unsigned_abs(),
))
}
} else {
check_v2_funding_inputs_sufficient(
Expand Down Expand Up @@ -6066,7 +6099,7 @@ fn estimate_v2_funding_transaction_fee(
fn check_v2_funding_inputs_sufficient(
contribution_amount: i64, funding_inputs: &[FundingTxInput], is_initiator: bool,
is_splice: bool, funding_feerate_sat_per_1000_weight: u32,
) -> Result<u64, ChannelError> {
) -> Result<u64, String> {
let estimated_fee = estimate_v2_funding_transaction_fee(
funding_inputs, &[], is_initiator, is_splice, funding_feerate_sat_per_1000_weight,
);
Expand All @@ -6089,10 +6122,9 @@ fn check_v2_funding_inputs_sufficient(

let minimal_input_amount_needed = contribution_amount.saturating_add(estimated_fee as i64);
if (total_input_sats as i64) < minimal_input_amount_needed {
Err(ChannelError::Warn(format!(
"Total input amount {} is lower than needed for contribution {}, considering fees of {}. Need more inputs.",
total_input_sats, contribution_amount, estimated_fee,
)))
Err(format!(
"Total input amount {total_input_sats} is lower than needed for contribution {contribution_amount}, considering fees of {estimated_fee}. Need more inputs.",
))
} else {
Ok(estimated_fee)
}
Expand Down Expand Up @@ -10749,9 +10781,13 @@ where
/// - `change_script`: an option change output script. If `None` and needed, one will be
/// generated by `SignerProvider::get_destination_script`.
#[cfg(splicing)]
pub fn splice_channel(
pub fn splice_channel<L: Deref>(
&mut self, contribution: SpliceContribution, funding_feerate_per_kw: u32, locktime: u32,
) -> Result<msgs::SpliceInit, APIError> {
logger: &L,
) -> Result<Option<msgs::Stfu>, APIError>
where
L::Target: Logger,
{
if self.holder_commitment_point.current_point().is_none() {
return Err(APIError::APIMisuseError {
err: format!(
Expand Down Expand Up @@ -10781,8 +10817,6 @@ where
});
}

// TODO(splicing): check for quiescence

let our_funding_contribution = contribution.value();
if our_funding_contribution == SignedAmount::ZERO {
return Err(APIError::APIMisuseError {
Expand Down Expand Up @@ -10877,8 +10911,49 @@ where
}
}

let prev_funding_input = self.funding.to_splice_funding_input();
let original_funding_txo = self.funding.get_funding_txo().ok_or_else(|| {
debug_assert!(false);
APIError::APIMisuseError {
err: "Chanel isn't yet fully funded".to_owned(),
}
})?;

let (our_funding_inputs, our_funding_outputs, change_script) = contribution.into_tx_parts();

let action = QuiescentAction::Splice(SpliceInstructions {
adjusted_funding_contribution,
our_funding_inputs,
our_funding_outputs,
change_script,
funding_feerate_per_kw,
locktime,
original_funding_txo,
});
self.propose_quiescence(logger, action)
.map_err(|e| APIError::APIMisuseError { err: e.to_owned() })
}

#[cfg(splicing)]
fn send_splice_init(
&mut self, instructions: SpliceInstructions,
) -> Result<msgs::SpliceInit, String> {
let SpliceInstructions {
adjusted_funding_contribution,
our_funding_inputs,
our_funding_outputs,
change_script,
funding_feerate_per_kw,
locktime,
original_funding_txo,
} = instructions;

if self.funding.get_funding_txo() != Some(original_funding_txo) {
// This should be unreachable once we opportunistically merge splices if the
// counterparty initializes a splice.
return Err("Funding changed out from under us".to_owned());
}

let prev_funding_input = self.funding.to_splice_funding_input();
let funding_negotiation_context = FundingNegotiationContext {
is_initiator: true,
our_funding_contribution: adjusted_funding_contribution,
Expand Down Expand Up @@ -11034,6 +11109,10 @@ where
ES::Target: EntropySource,
L::Target: Logger,
{
if !self.context.channel_state.is_quiescent() {
return Err(ChannelError::WarnAndDisconnect("Quiescence needed to splice".to_owned()));
}

let our_funding_contribution = SignedAmount::from_sat(our_funding_contribution_satoshis);
let splice_funding = self.validate_splice_init(msg, our_funding_contribution)?;

Expand Down Expand Up @@ -11073,6 +11152,11 @@ where
})?;
debug_assert!(interactive_tx_constructor.take_initiator_first_message().is_none());

// TODO(splicing): if post_quiescence_action is set, integrate what the user wants to do
// into the counterparty-initiated splice. For always-on nodes this probably isn't a useful
// optimization, but for often-offline nodes it may be, as we may connect and immediately
// go into splicing from both sides.

let funding_pubkey = splice_funding.get_holder_pubkeys().funding_pubkey;

self.pending_splice = Some(PendingSplice {
Expand Down Expand Up @@ -11821,23 +11905,21 @@ where
);
}

#[cfg(any(test, fuzzing))]
#[cfg(any(splicing, test, fuzzing))]
#[rustfmt::skip]
pub fn propose_quiescence<L: Deref>(
&mut self, logger: &L, action: QuiescentAction,
) -> Result<Option<msgs::Stfu>, ChannelError>
) -> Result<Option<msgs::Stfu>, &'static str>
where
L::Target: Logger,
{
log_debug!(logger, "Attempting to initiate quiescence");

if !self.context.is_usable() {
return Err(ChannelError::Ignore(
"Channel is not in a usable state to propose quiescence".to_owned()
));
return Err("Channel is not in a usable state to propose quiescence");
}
if self.quiescent_action.is_some() {
return Err(ChannelError::Ignore("Channel is already quiescing".to_owned()));
return Err("Channel is already quiescing");
}

self.quiescent_action = Some(action);
Expand All @@ -11858,7 +11940,7 @@ where

// Assumes we are either awaiting quiescence or our counterparty has requested quiescence.
#[rustfmt::skip]
pub fn send_stfu<L: Deref>(&mut self, logger: &L) -> Result<msgs::Stfu, ChannelError>
pub fn send_stfu<L: Deref>(&mut self, logger: &L) -> Result<msgs::Stfu, &'static str>
where
L::Target: Logger,
{
Expand All @@ -11872,9 +11954,7 @@ where
if self.context.is_waiting_on_peer_pending_channel_update()
|| self.context.is_monitor_or_signer_pending_channel_update()
{
return Err(ChannelError::Ignore(
"We cannot send `stfu` while state machine is pending".to_owned()
));
return Err("We cannot send `stfu` while state machine is pending")
}

let initiator = if self.context.channel_state.is_remote_stfu_sent() {
Expand All @@ -11900,7 +11980,7 @@ where
#[rustfmt::skip]
pub fn stfu<L: Deref>(
&mut self, msg: &msgs::Stfu, logger: &L
) -> Result<Option<msgs::Stfu>, ChannelError> where L::Target: Logger {
) -> Result<Option<StfuResponse>, ChannelError> where L::Target: Logger {
if self.context.channel_state.is_quiescent() {
return Err(ChannelError::Warn("Channel is already quiescent".to_owned()));
}
Expand Down Expand Up @@ -11931,7 +12011,10 @@ where
self.context.channel_state.set_remote_stfu_sent();

log_debug!(logger, "Received counterparty stfu proposing quiescence");
return self.send_stfu(logger).map(|stfu| Some(stfu));
return self
.send_stfu(logger)
.map(|stfu| Some(StfuResponse::Stfu(stfu)))
.map_err(|e| ChannelError::Ignore(e.to_owned()));
}

// We already sent `stfu` and are now processing theirs. It may be in response to ours, or
Expand Down Expand Up @@ -11972,6 +12055,13 @@ where
"Internal Error: Didn't have anything to do after reaching quiescence".to_owned()
));
},
Some(QuiescentAction::Splice(_instructions)) => {
#[cfg(splicing)]
return self.send_splice_init(_instructions)
.map(|splice_init| Some(StfuResponse::SpliceInit(splice_init)))
.map_err(|e| ChannelError::Ignore(e.to_owned()));
},
#[cfg(any(test, fuzzing))]
Some(QuiescentAction::DoNothing) => {
// In quiescence test we want to just hang out here, letting the test manually
// leave quiescence.
Expand Down Expand Up @@ -12004,7 +12094,10 @@ where
|| (self.context.channel_state.is_remote_stfu_sent()
&& !self.context.channel_state.is_local_stfu_sent())
{
return self.send_stfu(logger).map(|stfu| Some(stfu));
return self
.send_stfu(logger)
.map(|stfu| Some(stfu))
.map_err(|e| ChannelError::Ignore(e.to_owned()));
}

// We're either:
Expand Down Expand Up @@ -16205,8 +16298,8 @@ mod tests {
2000,
);
assert_eq!(
format!("{:?}", res.err().unwrap()),
"Warn: Total input amount 100000 is lower than needed for contribution 220000, considering fees of 1746. Need more inputs.",
res.err().unwrap(),
"Total input amount 100000 is lower than needed for contribution 220000, considering fees of 1746. Need more inputs.",
);
}

Expand Down Expand Up @@ -16241,8 +16334,8 @@ mod tests {
2200,
);
assert_eq!(
format!("{:?}", res.err().unwrap()),
"Warn: Total input amount 300000 is lower than needed for contribution 298032, considering fees of 2522. Need more inputs.",
res.err().unwrap(),
"Total input amount 300000 is lower than needed for contribution 298032, considering fees of 2522. Need more inputs.",
);
}

Expand Down
Loading
Loading