Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Channel splitting #11

Closed
wants to merge 6 commits into from
Closed
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
37 changes: 19 additions & 18 deletions lightning-block-sync/src/poll.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,24 +129,25 @@ impl ValidatedBlockHeader {
return Err(BlockSourceError::persistent("invalid block height"));
}

let work = self.header.work();
if self.chainwork != previous_header.chainwork + work {
return Err(BlockSourceError::persistent("invalid chainwork"));
}

if let Network::Bitcoin = network {
if self.height % 2016 == 0 {
let target = self.header.target();
let previous_target = previous_header.header.target();
let min_target = previous_target >> 2;
let max_target = previous_target << 2;
if target > max_target || target < min_target {
return Err(BlockSourceError::persistent("invalid difficulty transition"))
}
} else if self.header.bits != previous_header.header.bits {
return Err(BlockSourceError::persistent("invalid difficulty"))
}
}
// let work = self.header.work();
// if self.chainwork != previous_header.chainwork + work {
// return Err(BlockSourceError::persistent("invalid chainwork"));
// }

// TODO(Tibo): This causes issues with Esplora, temporary fix.
// if let Network::Bitcoin = network {
// if self.height % 2016 == 0 {
// let target = self.header.target();
// let previous_target = previous_header.header.target();
// let min_target = previous_target >> 2;
// let max_target = previous_target << 2;
// if target > max_target || target < min_target {
// return Err(BlockSourceError::persistent("invalid difficulty transition"))
// }
// } else if self.header.bits != previous_header.header.bits {
// return Err(BlockSourceError::persistent("invalid difficulty"))
// }
// }

Ok(())
}
Expand Down
2 changes: 1 addition & 1 deletion lightning-persister/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ impl FilesystemPersister {
let mut buffer = Cursor::new(&contents);
match <(BlockHash, ChannelMonitor<<SP::Target as SignerProvider>::Signer>)>::read(&mut buffer, (&*entropy_source, &*signer_provider)) {
Ok((blockhash, channel_monitor)) => {
if channel_monitor.get_funding_txo().0.txid != txid || channel_monitor.get_funding_txo().0.index != index {
if channel_monitor.get_original_funding_txo().0.txid != txid || channel_monitor.get_original_funding_txo().0.index != index {
return Err(std::io::Error::new(std::io::ErrorKind::InvalidData,
"ChannelMonitor was stored in the wrong file"));
}
Expand Down
1 change: 1 addition & 0 deletions lightning/rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
disable_all_formatting = true
38 changes: 36 additions & 2 deletions lightning/src/chain/chainmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ where C::Target: chain::Filter,
let monitor_states = self.monitors.read().unwrap();
for (_, monitor_state) in monitor_states.iter().filter(|(funding_outpoint, _)| {
for chan in ignored_channels {
if chan.funding_txo.as_ref() == Some(funding_outpoint) {
if chan.funding_txo.as_ref() == Some(funding_outpoint) || chan.original_funding_outpoint.as_ref() == Some(funding_outpoint) {
return false;
}
}
Expand Down Expand Up @@ -555,6 +555,15 @@ where C::Target: chain::Filter,
)
}
}

/// Retrieves the latest holder commitment transaction (and possibly HTLC transactions) for
/// the channel identified with the given `funding_txo`. Errors if no monitor is registered
/// for the given `funding_txo`.
pub fn get_latest_holder_commitment_txn(&self, funding_txo: &OutPoint) -> Result<Vec<bitcoin::Transaction>, ()> {
let monitors = self.monitors.read().unwrap();
let monitor = monitors.get(funding_txo).ok_or(())?;
Ok(monitor.monitor.get_latest_holder_commitment_txn_internal(&self.logger))
}
}

impl<ChannelSigner: WriteableEcdsaChannelSigner, C: Deref, T: Deref, F: Deref, L: Deref, P: Deref>
Expand Down Expand Up @@ -685,6 +694,31 @@ where C::Target: chain::Filter,
persist_res
}

fn update_channel_funding_txo(&self, old_funding_txo: OutPoint, new_funding_txo: OutPoint, channel_value_satoshis: u64) -> ChannelMonitorUpdateStatus {
let mut monitors = self.monitors.write().unwrap();
let monitor_opt = monitors.get_mut(&old_funding_txo);
match monitor_opt {
None => {
log_error!(self.logger, "Failed to update channel monitor funding txo: no such monitor registered");

// We should never ever trigger this from within ChannelManager. Technically a
// user could use this object with some proxying in between which makes this
// possible, but in tests and fuzzing, this should be a panic.
#[cfg(any(test, fuzzing))]
panic!("ChannelManager generated a channel update for a channel that was not yet registered!");
#[cfg(not(any(test, fuzzing)))]
return ChannelMonitorUpdateStatus::PermanentFailure;
},
Some(monitor_state) => {
let spk = monitor_state.monitor.update_funding_info(new_funding_txo, channel_value_satoshis);
if let Some(filter) = &self.chain_source {
filter.register_output(WatchedOutput { block_hash: None, outpoint: new_funding_txo, script_pubkey: spk });
}
return ChannelMonitorUpdateStatus::Completed;
}
}
}

/// Note that we persist the given `ChannelMonitor` update while holding the
/// `ChainMonitor` monitors lock.
fn update_channel(&self, funding_txo: OutPoint, update: &ChannelMonitorUpdate) -> ChannelMonitorUpdateStatus {
Expand Down Expand Up @@ -766,7 +800,7 @@ where C::Target: chain::Filter,
}
let monitor_events = monitor_state.monitor.get_and_clear_pending_monitor_events();
if monitor_events.len() > 0 {
let monitor_outpoint = monitor_state.monitor.get_funding_txo().0;
let monitor_outpoint = monitor_state.monitor.get_original_funding_txo().0;
let counterparty_node_id = monitor_state.monitor.get_counterparty_node_id();
pending_monitor_events.push((monitor_outpoint, monitor_events, counterparty_node_id));
}
Expand Down
69 changes: 66 additions & 3 deletions lightning/src/chain/channelmonitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@ pub(crate) struct ChannelMonitorImpl<Signer: WriteableEcdsaChannelSigner> {
channel_keys_id: [u8; 32],
holder_revocation_basepoint: PublicKey,
funding_info: (OutPoint, Script),
original_funding_info: Option<(OutPoint, Script)>,
current_counterparty_commitment_txid: Option<Txid>,
prev_counterparty_commitment_txid: Option<Txid>,

Expand Down Expand Up @@ -945,6 +946,13 @@ impl<Signer: WriteableEcdsaChannelSigner> Writeable for ChannelMonitorImpl<Signe
writer.write_all(&self.funding_info.0.txid[..])?;
writer.write_all(&self.funding_info.0.index.to_be_bytes())?;
self.funding_info.1.write(writer)?;
if let Some(ref original_funding_info) = self.original_funding_info {
writer.write_all(&[0; 1])?;
original_funding_info.0.write(writer)?;
original_funding_info.1.write(writer)?;
} else {
writer.write_all(&[1; 1])?;
}
self.current_counterparty_commitment_txid.write(writer)?;
self.prev_counterparty_commitment_txid.write(writer)?;

Expand Down Expand Up @@ -1187,6 +1195,7 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
channel_keys_id,
holder_revocation_basepoint,
funding_info,
original_funding_info: None,
current_counterparty_commitment_txid: None,
prev_counterparty_commitment_txid: None,

Expand Down Expand Up @@ -1252,6 +1261,23 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
txid, htlc_outputs, commitment_number, their_per_commitment_point, logger)
}

pub(crate) fn update_funding_info(&self, fund_outpoint: OutPoint, channel_value_satoshis: u64) -> Script {
let mut inner = self.inner.lock().unwrap();
let script = inner.funding_info.1.clone();
if let Some(original) = inner.original_funding_info.as_ref() {
if fund_outpoint == original.0 {
inner.original_funding_info = None;
}
} else {
inner.original_funding_info = Some((inner.funding_info.0.clone(), inner.funding_info.1.clone()));
}
inner.outputs_to_watch.insert(fund_outpoint.txid, vec![(fund_outpoint.index as u32, script.clone())]);
inner.funding_info = (fund_outpoint, script.clone());
inner.channel_value_satoshis = channel_value_satoshis;
inner.onchain_tx_handler.signer.set_channel_value_satoshis(channel_value_satoshis);
script
}

#[cfg(test)]
fn provide_latest_holder_commitment_tx(
&self, holder_commitment_tx: HolderCommitmentTransaction,
Expand Down Expand Up @@ -1308,6 +1334,11 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
self.inner.lock().unwrap().get_funding_txo().clone()
}

///
pub fn get_original_funding_txo(&self) -> (OutPoint, Script) {
self.inner.lock().unwrap().get_original_funding_txo().clone()
}

/// Gets a list of txids, with their output scripts (in the order they appear in the
/// transaction), which we must learn about spends of via block_connected().
pub fn get_outputs_to_watch(&self) -> Vec<(Txid, Vec<(u32, Script)>)> {
Expand Down Expand Up @@ -1416,6 +1447,11 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitor<Signer> {
self.inner.lock().unwrap().get_latest_holder_commitment_txn(logger)
}

pub(crate) fn get_latest_holder_commitment_txn_internal<L: Deref>(&self, logger: &L) -> Vec<Transaction>
where L::Target: Logger {
self.inner.lock().unwrap().get_latest_holder_commitment_txn_internal(logger)
}

/// Unsafe test-only version of get_latest_holder_commitment_txn used by our test framework
/// to bypass HolderCommitmentTransaction state update lockdown after signature and generate
/// revoked commitment transaction.
Expand Down Expand Up @@ -2573,6 +2609,10 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
&self.funding_info
}

pub fn get_original_funding_txo(&self) -> &(OutPoint, Script) {
&self.original_funding_info.as_ref().unwrap_or(&self.funding_info)
}

pub fn get_outputs_to_watch(&self) -> &HashMap<Txid, Vec<(u32, Script)>> {
// If we've detected a counterparty commitment tx on chain, we must include it in the set
// of outputs to watch for spends of, otherwise we're likely to lose user funds. Because
Expand Down Expand Up @@ -3018,8 +3058,12 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
}

pub fn get_latest_holder_commitment_txn<L: Deref>(&mut self, logger: &L) -> Vec<Transaction> where L::Target: Logger {
log_debug!(logger, "Getting signed latest holder commitment transaction!");
self.holder_tx_signed = true;
self.get_latest_holder_commitment_txn_internal(logger)
}

pub(crate) fn get_latest_holder_commitment_txn_internal<L: Deref>(&mut self, logger: &L) -> Vec<Transaction> where L::Target: Logger {
log_debug!(logger, "Getting signed latest holder commitment transaction!");
let commitment_tx = self.onchain_tx_handler.get_fully_signed_holder_tx(&self.funding_redeemscript);
let txid = commitment_tx.txid();
let mut holder_transactions = vec![commitment_tx];
Expand Down Expand Up @@ -3186,7 +3230,14 @@ impl<Signer: WriteableEcdsaChannelSigner> ChannelMonitorImpl<Signer> {
// (except for HTLC transactions for channels with anchor outputs), which is an easy
// way to filter out any potential non-matching txn for lazy filters.
let prevout = &tx.input[0].previous_output;
if prevout.txid == self.funding_info.0.txid && prevout.vout == self.funding_info.0.index as u32 {
let match_prevout = |outpoint: &OutPoint| {
prevout.txid == outpoint.txid && prevout.vout == outpoint.index as u32
};
let is_split = tx.output.len() == 2 && tx.output[0].script_pubkey == tx.output[1].script_pubkey;
let is_match = match_prevout(&self.funding_info.0) ||
(self.original_funding_info.is_some() && match_prevout(&self.original_funding_info.as_ref().unwrap().0) && !is_split);

if is_match {
let mut balance_spendable_csv = None;
log_info!(logger, "Channel {} closed by funding output spend in txid {}.",
log_bytes!(self.funding_info.0.to_channel_id()), txid);
Expand Down Expand Up @@ -3945,6 +3996,16 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
index: Readable::read(reader)?,
};
let funding_info = (outpoint, Readable::read(reader)?);
let original_funding_info = match <u8 as Readable>::read(reader)? {
0 => {
let outpoint = Readable::read(reader)?;
let script = Readable::read(reader)?;
Some((outpoint, script))
},
1 => { None },
_ => return Err(DecodeError::InvalidValue),
};

let current_counterparty_commitment_txid = Readable::read(reader)?;
let prev_counterparty_commitment_txid = Readable::read(reader)?;

Expand Down Expand Up @@ -4141,6 +4202,7 @@ impl<'a, 'b, ES: EntropySource, SP: SignerProvider> ReadableArgs<(&'a ES, &'b SP
channel_keys_id,
holder_revocation_basepoint,
funding_info,
original_funding_info,
current_counterparty_commitment_txid,
prev_counterparty_commitment_txid,

Expand Down Expand Up @@ -4399,7 +4461,8 @@ mod tests {
selected_contest_delay: 67,
}),
funding_outpoint: Some(funding_outpoint),
channel_type_features: ChannelTypeFeatures::only_static_remote_key()
channel_type_features: ChannelTypeFeatures::only_static_remote_key(),
original_funding_outpoint: None,
};
// Prune with one old state and a holder commitment tx holding a few overlaps with the
// old state.
Expand Down
4 changes: 4 additions & 0 deletions lightning/src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,10 @@ pub trait Watch<ChannelSigner: WriteableEcdsaChannelSigner> {
/// [`update_monitor`]: channelmonitor::ChannelMonitor::update_monitor
fn update_channel(&self, funding_txo: OutPoint, update: &ChannelMonitorUpdate) -> ChannelMonitorUpdateStatus;

/// Update the outpoint funding the channel. To be used when the channel is split into two to
/// open a DLC channel with the same funding transaction.
fn update_channel_funding_txo(&self, old_funding_txo: OutPoint, new_funding_txo: OutPoint, channel_value_satoshis: u64) -> ChannelMonitorUpdateStatus;

/// Returns any monitor events since the last call. Subsequent calls must only return new
/// events.
///
Expand Down
2 changes: 2 additions & 0 deletions lightning/src/chain/onchaintx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ impl Writeable for Option<Vec<Option<(usize, Signature)>>> {
}

/// The claim commonly referred to as the pre-signed second-stage HTLC transaction.
#[derive(PartialEq)]
pub(crate) struct ExternalHTLCClaim {
pub(crate) commitment_txid: Txid,
pub(crate) per_commitment_number: u64,
Expand All @@ -182,6 +183,7 @@ pub(crate) struct ExternalHTLCClaim {

// Represents the different types of claims for which events are yielded externally to satisfy said
// claims.
#[derive(PartialEq)]
pub(crate) enum ClaimEvent {
/// Event yielded to signal that the commitment transaction fee must be bumped to claim any
/// encumbered funds and proceed to HTLC resolution, if any HTLCs exist.
Expand Down
23 changes: 20 additions & 3 deletions lightning/src/ln/chan_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -863,11 +863,22 @@ pub struct ChannelTransactionParameters {
/// The late-bound counterparty channel transaction parameters.
/// These parameters are populated at the point in the protocol where the counterparty provides them.
pub counterparty_parameters: Option<CounterpartyChannelTransactionParameters>,
/// The late-bound funding outpoint
/// The late-bound funding outpoint.
///
/// If it's a vanilla LN channel, this value corresponds to the actual funding outpoint that
/// goes on-chain when the channel is created.
///
/// If instead we're dealing with a split channel, this value corresponds to the output of a
/// glue transaction which sits in between the funding transaction and the commitment
/// transaction.
pub funding_outpoint: Option<chain::transaction::OutPoint>,
/// This channel's type, as negotiated during channel open. For old objects where this field
/// wasn't serialized, it will default to static_remote_key at deserialization.
pub channel_type_features: ChannelTypeFeatures
pub channel_type_features: ChannelTypeFeatures,
/// This value always corresponds to the actual funding outpoint. This is different to
/// [`ChannelTransactionParameters::funding_outpoint`], which varies depending on the type
/// of Lightning channel we have.
pub original_funding_outpoint: Option<chain::transaction::OutPoint>,
}

/// Late-bound per-channel counterparty data used to build transactions.
Expand Down Expand Up @@ -926,6 +937,7 @@ impl Writeable for ChannelTransactionParameters {
(8, self.funding_outpoint, option),
(10, legacy_deserialization_prevention_marker, option),
(11, self.channel_type_features, required),
(14, self.original_funding_outpoint, option),
});
Ok(())
}
Expand All @@ -940,6 +952,7 @@ impl Readable for ChannelTransactionParameters {
let mut funding_outpoint = None;
let mut _legacy_deserialization_prevention_marker: Option<()> = None;
let mut channel_type_features = None;
let mut original_funding_outpoint = None;

read_tlv_fields!(reader, {
(0, holder_pubkeys, required),
Expand All @@ -949,6 +962,7 @@ impl Readable for ChannelTransactionParameters {
(8, funding_outpoint, option),
(10, _legacy_deserialization_prevention_marker, option),
(11, channel_type_features, option),
(14, original_funding_outpoint, option),
});

let mut additional_features = ChannelTypeFeatures::empty();
Expand All @@ -961,7 +975,8 @@ impl Readable for ChannelTransactionParameters {
is_outbound_from_holder: is_outbound_from_holder.0.unwrap(),
counterparty_parameters,
funding_outpoint,
channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key())
channel_type_features: channel_type_features.unwrap_or(ChannelTypeFeatures::only_static_remote_key()),
original_funding_outpoint,
})
}
}
Expand Down Expand Up @@ -1087,6 +1102,7 @@ impl HolderCommitmentTransaction {
counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: channel_pubkeys.clone(), selected_contest_delay: 0 }),
funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }),
channel_type_features: ChannelTypeFeatures::only_static_remote_key(),
original_funding_outpoint: None,
};
let mut counterparty_htlc_sigs = Vec::new();
for _ in 0..htlcs.len() {
Expand Down Expand Up @@ -1796,6 +1812,7 @@ mod tests {
counterparty_parameters: Some(CounterpartyChannelTransactionParameters { pubkeys: counterparty_pubkeys.clone(), selected_contest_delay: 0 }),
funding_outpoint: Some(chain::transaction::OutPoint { txid: Txid::all_zeros(), index: 0 }),
channel_type_features: ChannelTypeFeatures::only_static_remote_key(),
original_funding_outpoint: None,
};

let mut htlcs_with_aux: Vec<(_, ())> = Vec::new();
Expand Down
Loading
Loading