Skip to content
Merged
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
9 changes: 8 additions & 1 deletion bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,6 +150,10 @@ interface Node {
[Throws=NodeError]
UserChannelId open_announced_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
[Throws=NodeError]
void splice_in([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, u64 splice_amount_sats);
[Throws=NodeError]
void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats);
[Throws=NodeError]
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
[Throws=NodeError]
void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, string? reason);
Expand DownExpand Up@@ -290,6 +294,7 @@ enum NodeError {
"ProbeSendingFailed",
"ChannelCreationFailed",
"ChannelClosingFailed",
"ChannelSplicingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
Expand DownExpand Up@@ -393,8 +398,10 @@ interface Event {
PaymentForwarded(ChannelId prev_channel_id, ChannelId next_channel_id, UserChannelId?
prev_user_channel_id, UserChannelId? next_user_channel_id, PublicKey? prev_node_id, PublicKey? next_node_id, u64? total_fee_earned_msat, u64? skimmed_fee_msat, boolean claim_from_onchain_tx, u64? outbound_amount_forwarded_msat);
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, OutPoint? funding_txo);
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
SplicePending(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint new_funding_txo);
SpliceFailed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint? abandoned_funding_txo);
};

enum PaymentFailureReason {
Expand Down
1 change: 1 addition & 0 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1795,6 +1795,7 @@ fn build_with_store_internal(
wallet,
chain_source,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
1 change: 1 addition & 0 deletions src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -325,6 +325,7 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig {
user_config.manually_accept_inbound_channels = true;
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
config.anchor_channels_config.is_some();
user_config.reject_inbound_splices = false;

if may_announce_channel(config).is_err() {
user_config.accept_forwards_to_priv_channels = false;
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,8 @@ pub enum Error {
ChannelCreationFailed,
/// A channel could not be closed.
ChannelClosingFailed,
/// A channel could not be spliced.
ChannelSplicingFailed,
/// A channel configuration could not be updated.
ChannelConfigUpdateFailed,
/// Persistence failed.
Expand DownExpand Up@@ -145,6 +147,7 @@ impl fmt::Display for Error {
Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."),
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelSplicingFailed => write!(f, "Failed to splice channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
Expand Down
198 changes: 179 additions & 19 deletions src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -199,6 +199,10 @@ pub enum Event {
funding_txo: OutPoint,
},
/// A channel is ready to be used.
///
/// This event is emitted when:
/// - A new channel has been established and is ready for use
/// - An existing channel has been spliced and is ready with the new funding output
ChannelReady {
/// The `channel_id` of the channel.
channel_id: ChannelId,
Expand All@@ -208,6 +212,14 @@ pub enum Event {
///
/// This will be `None` for events serialized by LDK Node v0.1.0 and prior.
counterparty_node_id: Option<PublicKey>,
/// The outpoint of the channel's funding transaction.
///
/// This represents the channel's current funding output, which may change when the
/// channel is spliced. For spliced channels, this will contain the new funding output
/// from the confirmed splice transaction.
///
/// This will be `None` for events serialized by LDK Node v0.6.0 and prior.
funding_txo: Option<OutPoint>,
},
/// A channel has been closed.
ChannelClosed {
Expand All@@ -222,6 +234,28 @@ pub enum Event {
/// This will be `None` for events serialized by LDK Node v0.2.1 and prior.
reason: Option<ClosureReason>,
},
/// A channel splice is pending confirmation on-chain.
SplicePending {
Comment thread
jkczyz marked this conversation as resolved.
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction.
new_funding_txo: OutPoint,
},
/// A channel splice has failed.
SpliceFailed {
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction, if one was created.
abandoned_funding_txo: Option<OutPoint>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this field optional?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's optional in the LDK event because the splice may fail before the funding tx is negotiated.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Maybe it's worth adding a comment explaining this here and (more explicitly) on the LDK event field?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expanded the comment to match the LDK docs, which had more clarity.

},
}

impl_writeable_tlv_based_enum!(Event,
Expand All@@ -246,6 +280,7 @@ impl_writeable_tlv_based_enum!(Event,
(0, channel_id, required),
(1, counterparty_node_id, option),
(2, user_channel_id, required),
(3, funding_txo, option),
},
(4, ChannelPending) => {
(0, channel_id, required),
Expand DownExpand Up@@ -278,7 +313,19 @@ impl_writeable_tlv_based_enum!(Event,
(10, skimmed_fee_msat, option),
(12, claim_from_onchain_tx, required),
(14, outbound_amount_forwarded_msat, option),
}
},
(8, SplicePending) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, new_funding_txo, required),
},
(9, SpliceFailed) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, abandoned_funding_txo, option),
},
);

pub struct EventQueue<L: Deref>
Expand DownExpand Up@@ -1397,14 +1444,28 @@ where
}
},
LdkEvent::ChannelReady {
channel_id, user_channel_id, counterparty_node_id, ..
channel_id,
user_channel_id,
counterparty_node_id,
funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used.",
channel_id,
counterparty_node_id,
);
if let Some(funding_txo) = funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used with funding_txo {}",
Comment thread
tnull marked this conversation as resolved.
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used",
channel_id,
counterparty_node_id,
);
}

if let Some(liquidity_source) = self.liquidity_source.as_ref() {
liquidity_source
Expand All@@ -1416,6 +1477,7 @@ where
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id: Some(counterparty_node_id),
funding_txo,
};
match self.event_queue.add_event(event).await {
Ok(_) => {},
Expand DownExpand Up@@ -1614,20 +1676,116 @@ where
}
}
},
LdkEvent::FundingTransactionReadyForSigning { .. } => {
debug_assert!(false, "We currently don't support interactive-tx, so this event should never be emitted.");
// TODO(splicing): Revisit error handling once splicing API is settled in LDK 0.3
LdkEvent::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} => match self.wallet.sign_owned_inputs(unsigned_transaction) {
Ok(partially_signed_tx) => {
match self.channel_manager.funding_transaction_signed(
&channel_id,
&counterparty_node_id,
partially_signed_tx,
) {
Ok(()) => {
log_info!(
self.logger,
"Signed funding transaction for channel {} with counterparty {}",
channel_id,
counterparty_node_id
);
},
Err(e) => {
// TODO(splicing): Abort splice once supported in LDK 0.3
debug_assert!(false, "Failed signing funding transaction: {:?}", e);
log_error!(self.logger, "Failed signing funding transaction: {:?}", e);
Comment thread
tnull marked this conversation as resolved.
},
}
},
Err(()) => log_error!(self.logger, "Failed signing funding transaction"),
},
LdkEvent::SplicePending { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
LdkEvent::SplicePending {
channel_id,
user_channel_id,
counterparty_node_id,
new_funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} pending splice with funding_txo {}",
channel_id,
counterparty_node_id,
new_funding_txo,
);

let event = Event::SplicePending {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
new_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
LdkEvent::SpliceFailed { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
);
LdkEvent::SpliceFailed {
channel_id,
user_channel_id,
counterparty_node_id,
abandoned_funding_txo,
contributed_outputs,
..
} => {
if let Some(funding_txo) = abandoned_funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice with funding_txo {}",
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice",
channel_id,
counterparty_node_id,
);
}

let tx = bitcoin::Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![],
output: contributed_outputs,
};
if let Err(e) = self.wallet.cancel_tx(&tx) {
log_error!(self.logger, "Failed reclaiming unused addresses: {}", e);
return Err(ReplayEvent());
}

let event = Event::SpliceFailed {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
abandoned_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
}
Ok(())
Expand DownExpand Up@@ -1655,6 +1813,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};
event_queue.add_event(expected_event.clone()).await.unwrap();

Expand DownExpand Up@@ -1692,6 +1851,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};

// Check `next_event_async` won't return if the queue is empty and always rather timeout.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Channel splicing support by jkczyz · Pull Request #677 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
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
9 changes: 8 additions & 1 deletion bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,6 +150,10 @@ interface Node {
[Throws=NodeError]
UserChannelId open_announced_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
[Throws=NodeError]
void splice_in([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, u64 splice_amount_sats);
[Throws=NodeError]
void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats);
[Throws=NodeError]
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
[Throws=NodeError]
void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, string? reason);
Expand DownExpand Up@@ -290,6 +294,7 @@ enum NodeError {
"ProbeSendingFailed",
"ChannelCreationFailed",
"ChannelClosingFailed",
"ChannelSplicingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
Expand DownExpand Up@@ -393,8 +398,10 @@ interface Event {
PaymentForwarded(ChannelId prev_channel_id, ChannelId next_channel_id, UserChannelId?
prev_user_channel_id, UserChannelId? next_user_channel_id, PublicKey? prev_node_id, PublicKey? next_node_id, u64? total_fee_earned_msat, u64? skimmed_fee_msat, boolean claim_from_onchain_tx, u64? outbound_amount_forwarded_msat);
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, OutPoint? funding_txo);
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
SplicePending(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint new_funding_txo);
SpliceFailed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint? abandoned_funding_txo);
};

enum PaymentFailureReason {
Expand Down
1 change: 1 addition & 0 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1795,6 +1795,7 @@ fn build_with_store_internal(
wallet,
chain_source,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
1 change: 1 addition & 0 deletions src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -325,6 +325,7 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig {
user_config.manually_accept_inbound_channels = true;
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
config.anchor_channels_config.is_some();
user_config.reject_inbound_splices = false;

if may_announce_channel(config).is_err() {
user_config.accept_forwards_to_priv_channels = false;
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,8 @@ pub enum Error {
ChannelCreationFailed,
/// A channel could not be closed.
ChannelClosingFailed,
/// A channel could not be spliced.
ChannelSplicingFailed,
/// A channel configuration could not be updated.
ChannelConfigUpdateFailed,
/// Persistence failed.
Expand DownExpand Up@@ -145,6 +147,7 @@ impl fmt::Display for Error {
Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."),
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelSplicingFailed => write!(f, "Failed to splice channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
Expand Down
198 changes: 179 additions & 19 deletions src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -199,6 +199,10 @@ pub enum Event {
funding_txo: OutPoint,
},
/// A channel is ready to be used.
///
/// This event is emitted when:
/// - A new channel has been established and is ready for use
/// - An existing channel has been spliced and is ready with the new funding output
ChannelReady {
/// The `channel_id` of the channel.
channel_id: ChannelId,
Expand All@@ -208,6 +212,14 @@ pub enum Event {
///
/// This will be `None` for events serialized by LDK Node v0.1.0 and prior.
counterparty_node_id: Option<PublicKey>,
/// The outpoint of the channel's funding transaction.
///
/// This represents the channel's current funding output, which may change when the
/// channel is spliced. For spliced channels, this will contain the new funding output
/// from the confirmed splice transaction.
///
/// This will be `None` for events serialized by LDK Node v0.6.0 and prior.
funding_txo: Option<OutPoint>,
},
/// A channel has been closed.
ChannelClosed {
Expand All@@ -222,6 +234,28 @@ pub enum Event {
/// This will be `None` for events serialized by LDK Node v0.2.1 and prior.
reason: Option<ClosureReason>,
},
/// A channel splice is pending confirmation on-chain.
SplicePending {
Comment thread
jkczyz marked this conversation as resolved.
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction.
new_funding_txo: OutPoint,
},
/// A channel splice has failed.
SpliceFailed {
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction, if one was created.
abandoned_funding_txo: Option<OutPoint>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this field optional?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's optional in the LDK event because the splice may fail before the funding tx is negotiated.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Maybe it's worth adding a comment explaining this here and (more explicitly) on the LDK event field?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expanded the comment to match the LDK docs, which had more clarity.

},
}

impl_writeable_tlv_based_enum!(Event,
Expand All@@ -246,6 +280,7 @@ impl_writeable_tlv_based_enum!(Event,
(0, channel_id, required),
(1, counterparty_node_id, option),
(2, user_channel_id, required),
(3, funding_txo, option),
},
(4, ChannelPending) => {
(0, channel_id, required),
Expand DownExpand Up@@ -278,7 +313,19 @@ impl_writeable_tlv_based_enum!(Event,
(10, skimmed_fee_msat, option),
(12, claim_from_onchain_tx, required),
(14, outbound_amount_forwarded_msat, option),
}
},
(8, SplicePending) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, new_funding_txo, required),
},
(9, SpliceFailed) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, abandoned_funding_txo, option),
},
);

pub struct EventQueue<L: Deref>
Expand DownExpand Up@@ -1397,14 +1444,28 @@ where
}
},
LdkEvent::ChannelReady {
channel_id, user_channel_id, counterparty_node_id, ..
channel_id,
user_channel_id,
counterparty_node_id,
funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used.",
channel_id,
counterparty_node_id,
);
if let Some(funding_txo) = funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used with funding_txo {}",
Comment thread
tnull marked this conversation as resolved.
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used",
channel_id,
counterparty_node_id,
);
}

if let Some(liquidity_source) = self.liquidity_source.as_ref() {
liquidity_source
Expand All@@ -1416,6 +1477,7 @@ where
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id: Some(counterparty_node_id),
funding_txo,
};
match self.event_queue.add_event(event).await {
Ok(_) => {},
Expand DownExpand Up@@ -1614,20 +1676,116 @@ where
}
}
},
LdkEvent::FundingTransactionReadyForSigning { .. } => {
debug_assert!(false, "We currently don't support interactive-tx, so this event should never be emitted.");
// TODO(splicing): Revisit error handling once splicing API is settled in LDK 0.3
LdkEvent::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} => match self.wallet.sign_owned_inputs(unsigned_transaction) {
Ok(partially_signed_tx) => {
match self.channel_manager.funding_transaction_signed(
&channel_id,
&counterparty_node_id,
partially_signed_tx,
) {
Ok(()) => {
log_info!(
self.logger,
"Signed funding transaction for channel {} with counterparty {}",
channel_id,
counterparty_node_id
);
},
Err(e) => {
// TODO(splicing): Abort splice once supported in LDK 0.3
debug_assert!(false, "Failed signing funding transaction: {:?}", e);
log_error!(self.logger, "Failed signing funding transaction: {:?}", e);
Comment thread
tnull marked this conversation as resolved.
},
}
},
Err(()) => log_error!(self.logger, "Failed signing funding transaction"),
},
LdkEvent::SplicePending { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
LdkEvent::SplicePending {
channel_id,
user_channel_id,
counterparty_node_id,
new_funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} pending splice with funding_txo {}",
channel_id,
counterparty_node_id,
new_funding_txo,
);

let event = Event::SplicePending {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
new_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
LdkEvent::SpliceFailed { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
);
LdkEvent::SpliceFailed {
channel_id,
user_channel_id,
counterparty_node_id,
abandoned_funding_txo,
contributed_outputs,
..
} => {
if let Some(funding_txo) = abandoned_funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice with funding_txo {}",
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice",
channel_id,
counterparty_node_id,
);
}

let tx = bitcoin::Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![],
output: contributed_outputs,
};
if let Err(e) = self.wallet.cancel_tx(&tx) {
log_error!(self.logger, "Failed reclaiming unused addresses: {}", e);
return Err(ReplayEvent());
}

let event = Event::SpliceFailed {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
abandoned_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
}
Ok(())
Expand DownExpand Up@@ -1655,6 +1813,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};
event_queue.add_event(expected_event.clone()).await.unwrap();

Expand DownExpand Up@@ -1692,6 +1851,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};

// Check `next_event_async` won't return if the queue is empty and always rather timeout.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Channel splicing support by jkczyz · Pull Request #677 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
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
9 changes: 8 additions & 1 deletion bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,6 +150,10 @@ interface Node {
[Throws=NodeError]
UserChannelId open_announced_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
[Throws=NodeError]
void splice_in([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, u64 splice_amount_sats);
[Throws=NodeError]
void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats);
[Throws=NodeError]
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
[Throws=NodeError]
void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, string? reason);
Expand DownExpand Up@@ -290,6 +294,7 @@ enum NodeError {
"ProbeSendingFailed",
"ChannelCreationFailed",
"ChannelClosingFailed",
"ChannelSplicingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
Expand DownExpand Up@@ -393,8 +398,10 @@ interface Event {
PaymentForwarded(ChannelId prev_channel_id, ChannelId next_channel_id, UserChannelId?
prev_user_channel_id, UserChannelId? next_user_channel_id, PublicKey? prev_node_id, PublicKey? next_node_id, u64? total_fee_earned_msat, u64? skimmed_fee_msat, boolean claim_from_onchain_tx, u64? outbound_amount_forwarded_msat);
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, OutPoint? funding_txo);
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
SplicePending(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint new_funding_txo);
SpliceFailed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint? abandoned_funding_txo);
};

enum PaymentFailureReason {
Expand Down
1 change: 1 addition & 0 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1795,6 +1795,7 @@ fn build_with_store_internal(
wallet,
chain_source,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
1 change: 1 addition & 0 deletions src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -325,6 +325,7 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig {
user_config.manually_accept_inbound_channels = true;
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
config.anchor_channels_config.is_some();
user_config.reject_inbound_splices = false;

if may_announce_channel(config).is_err() {
user_config.accept_forwards_to_priv_channels = false;
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,8 @@ pub enum Error {
ChannelCreationFailed,
/// A channel could not be closed.
ChannelClosingFailed,
/// A channel could not be spliced.
ChannelSplicingFailed,
/// A channel configuration could not be updated.
ChannelConfigUpdateFailed,
/// Persistence failed.
Expand DownExpand Up@@ -145,6 +147,7 @@ impl fmt::Display for Error {
Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."),
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelSplicingFailed => write!(f, "Failed to splice channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
Expand Down
198 changes: 179 additions & 19 deletions src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -199,6 +199,10 @@ pub enum Event {
funding_txo: OutPoint,
},
/// A channel is ready to be used.
///
/// This event is emitted when:
/// - A new channel has been established and is ready for use
/// - An existing channel has been spliced and is ready with the new funding output
ChannelReady {
/// The `channel_id` of the channel.
channel_id: ChannelId,
Expand All@@ -208,6 +212,14 @@ pub enum Event {
///
/// This will be `None` for events serialized by LDK Node v0.1.0 and prior.
counterparty_node_id: Option<PublicKey>,
/// The outpoint of the channel's funding transaction.
///
/// This represents the channel's current funding output, which may change when the
/// channel is spliced. For spliced channels, this will contain the new funding output
/// from the confirmed splice transaction.
///
/// This will be `None` for events serialized by LDK Node v0.6.0 and prior.
funding_txo: Option<OutPoint>,
},
/// A channel has been closed.
ChannelClosed {
Expand All@@ -222,6 +234,28 @@ pub enum Event {
/// This will be `None` for events serialized by LDK Node v0.2.1 and prior.
reason: Option<ClosureReason>,
},
/// A channel splice is pending confirmation on-chain.
SplicePending {
Comment thread
jkczyz marked this conversation as resolved.
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction.
new_funding_txo: OutPoint,
},
/// A channel splice has failed.
SpliceFailed {
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction, if one was created.
abandoned_funding_txo: Option<OutPoint>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this field optional?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's optional in the LDK event because the splice may fail before the funding tx is negotiated.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Maybe it's worth adding a comment explaining this here and (more explicitly) on the LDK event field?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expanded the comment to match the LDK docs, which had more clarity.

},
}

impl_writeable_tlv_based_enum!(Event,
Expand All@@ -246,6 +280,7 @@ impl_writeable_tlv_based_enum!(Event,
(0, channel_id, required),
(1, counterparty_node_id, option),
(2, user_channel_id, required),
(3, funding_txo, option),
},
(4, ChannelPending) => {
(0, channel_id, required),
Expand DownExpand Up@@ -278,7 +313,19 @@ impl_writeable_tlv_based_enum!(Event,
(10, skimmed_fee_msat, option),
(12, claim_from_onchain_tx, required),
(14, outbound_amount_forwarded_msat, option),
}
},
(8, SplicePending) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, new_funding_txo, required),
},
(9, SpliceFailed) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, abandoned_funding_txo, option),
},
);

pub struct EventQueue<L: Deref>
Expand DownExpand Up@@ -1397,14 +1444,28 @@ where
}
},
LdkEvent::ChannelReady {
channel_id, user_channel_id, counterparty_node_id, ..
channel_id,
user_channel_id,
counterparty_node_id,
funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used.",
channel_id,
counterparty_node_id,
);
if let Some(funding_txo) = funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used with funding_txo {}",
Comment thread
tnull marked this conversation as resolved.
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used",
channel_id,
counterparty_node_id,
);
}

if let Some(liquidity_source) = self.liquidity_source.as_ref() {
liquidity_source
Expand All@@ -1416,6 +1477,7 @@ where
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id: Some(counterparty_node_id),
funding_txo,
};
match self.event_queue.add_event(event).await {
Ok(_) => {},
Expand DownExpand Up@@ -1614,20 +1676,116 @@ where
}
}
},
LdkEvent::FundingTransactionReadyForSigning { .. } => {
debug_assert!(false, "We currently don't support interactive-tx, so this event should never be emitted.");
// TODO(splicing): Revisit error handling once splicing API is settled in LDK 0.3
LdkEvent::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} => match self.wallet.sign_owned_inputs(unsigned_transaction) {
Ok(partially_signed_tx) => {
match self.channel_manager.funding_transaction_signed(
&channel_id,
&counterparty_node_id,
partially_signed_tx,
) {
Ok(()) => {
log_info!(
self.logger,
"Signed funding transaction for channel {} with counterparty {}",
channel_id,
counterparty_node_id
);
},
Err(e) => {
// TODO(splicing): Abort splice once supported in LDK 0.3
debug_assert!(false, "Failed signing funding transaction: {:?}", e);
log_error!(self.logger, "Failed signing funding transaction: {:?}", e);
Comment thread
tnull marked this conversation as resolved.
},
}
},
Err(()) => log_error!(self.logger, "Failed signing funding transaction"),
},
LdkEvent::SplicePending { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
LdkEvent::SplicePending {
channel_id,
user_channel_id,
counterparty_node_id,
new_funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} pending splice with funding_txo {}",
channel_id,
counterparty_node_id,
new_funding_txo,
);

let event = Event::SplicePending {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
new_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
LdkEvent::SpliceFailed { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
);
LdkEvent::SpliceFailed {
channel_id,
user_channel_id,
counterparty_node_id,
abandoned_funding_txo,
contributed_outputs,
..
} => {
if let Some(funding_txo) = abandoned_funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice with funding_txo {}",
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice",
channel_id,
counterparty_node_id,
);
}

let tx = bitcoin::Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![],
output: contributed_outputs,
};
if let Err(e) = self.wallet.cancel_tx(&tx) {
log_error!(self.logger, "Failed reclaiming unused addresses: {}", e);
return Err(ReplayEvent());
}

let event = Event::SpliceFailed {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
abandoned_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
}
Ok(())
Expand DownExpand Up@@ -1655,6 +1813,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};
event_queue.add_event(expected_event.clone()).await.unwrap();

Expand DownExpand Up@@ -1692,6 +1851,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};

// Check `next_event_async` won't return if the queue is empty and always rather timeout.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Channel splicing support by jkczyz · Pull Request #677 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
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
9 changes: 8 additions & 1 deletion bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,6 +150,10 @@ interface Node {
[Throws=NodeError]
UserChannelId open_announced_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
[Throws=NodeError]
void splice_in([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, u64 splice_amount_sats);
[Throws=NodeError]
void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats);
[Throws=NodeError]
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
[Throws=NodeError]
void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, string? reason);
Expand DownExpand Up@@ -290,6 +294,7 @@ enum NodeError {
"ProbeSendingFailed",
"ChannelCreationFailed",
"ChannelClosingFailed",
"ChannelSplicingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
Expand DownExpand Up@@ -393,8 +398,10 @@ interface Event {
PaymentForwarded(ChannelId prev_channel_id, ChannelId next_channel_id, UserChannelId?
prev_user_channel_id, UserChannelId? next_user_channel_id, PublicKey? prev_node_id, PublicKey? next_node_id, u64? total_fee_earned_msat, u64? skimmed_fee_msat, boolean claim_from_onchain_tx, u64? outbound_amount_forwarded_msat);
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, OutPoint? funding_txo);
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
SplicePending(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint new_funding_txo);
SpliceFailed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint? abandoned_funding_txo);
};

enum PaymentFailureReason {
Expand Down
1 change: 1 addition & 0 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1795,6 +1795,7 @@ fn build_with_store_internal(
wallet,
chain_source,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
1 change: 1 addition & 0 deletions src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -325,6 +325,7 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig {
user_config.manually_accept_inbound_channels = true;
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
config.anchor_channels_config.is_some();
user_config.reject_inbound_splices = false;

if may_announce_channel(config).is_err() {
user_config.accept_forwards_to_priv_channels = false;
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,8 @@ pub enum Error {
ChannelCreationFailed,
/// A channel could not be closed.
ChannelClosingFailed,
/// A channel could not be spliced.
ChannelSplicingFailed,
/// A channel configuration could not be updated.
ChannelConfigUpdateFailed,
/// Persistence failed.
Expand DownExpand Up@@ -145,6 +147,7 @@ impl fmt::Display for Error {
Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."),
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelSplicingFailed => write!(f, "Failed to splice channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
Expand Down
198 changes: 179 additions & 19 deletions src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -199,6 +199,10 @@ pub enum Event {
funding_txo: OutPoint,
},
/// A channel is ready to be used.
///
/// This event is emitted when:
/// - A new channel has been established and is ready for use
/// - An existing channel has been spliced and is ready with the new funding output
ChannelReady {
/// The `channel_id` of the channel.
channel_id: ChannelId,
Expand All@@ -208,6 +212,14 @@ pub enum Event {
///
/// This will be `None` for events serialized by LDK Node v0.1.0 and prior.
counterparty_node_id: Option<PublicKey>,
/// The outpoint of the channel's funding transaction.
///
/// This represents the channel's current funding output, which may change when the
/// channel is spliced. For spliced channels, this will contain the new funding output
/// from the confirmed splice transaction.
///
/// This will be `None` for events serialized by LDK Node v0.6.0 and prior.
funding_txo: Option<OutPoint>,
},
/// A channel has been closed.
ChannelClosed {
Expand All@@ -222,6 +234,28 @@ pub enum Event {
/// This will be `None` for events serialized by LDK Node v0.2.1 and prior.
reason: Option<ClosureReason>,
},
/// A channel splice is pending confirmation on-chain.
SplicePending {
Comment thread
jkczyz marked this conversation as resolved.
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction.
new_funding_txo: OutPoint,
},
/// A channel splice has failed.
SpliceFailed {
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction, if one was created.
abandoned_funding_txo: Option<OutPoint>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this field optional?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's optional in the LDK event because the splice may fail before the funding tx is negotiated.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Maybe it's worth adding a comment explaining this here and (more explicitly) on the LDK event field?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expanded the comment to match the LDK docs, which had more clarity.

},
}

impl_writeable_tlv_based_enum!(Event,
Expand All@@ -246,6 +280,7 @@ impl_writeable_tlv_based_enum!(Event,
(0, channel_id, required),
(1, counterparty_node_id, option),
(2, user_channel_id, required),
(3, funding_txo, option),
},
(4, ChannelPending) => {
(0, channel_id, required),
Expand DownExpand Up@@ -278,7 +313,19 @@ impl_writeable_tlv_based_enum!(Event,
(10, skimmed_fee_msat, option),
(12, claim_from_onchain_tx, required),
(14, outbound_amount_forwarded_msat, option),
}
},
(8, SplicePending) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, new_funding_txo, required),
},
(9, SpliceFailed) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, abandoned_funding_txo, option),
},
);

pub struct EventQueue<L: Deref>
Expand DownExpand Up@@ -1397,14 +1444,28 @@ where
}
},
LdkEvent::ChannelReady {
channel_id, user_channel_id, counterparty_node_id, ..
channel_id,
user_channel_id,
counterparty_node_id,
funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used.",
channel_id,
counterparty_node_id,
);
if let Some(funding_txo) = funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used with funding_txo {}",
Comment thread
tnull marked this conversation as resolved.
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used",
channel_id,
counterparty_node_id,
);
}

if let Some(liquidity_source) = self.liquidity_source.as_ref() {
liquidity_source
Expand All@@ -1416,6 +1477,7 @@ where
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id: Some(counterparty_node_id),
funding_txo,
};
match self.event_queue.add_event(event).await {
Ok(_) => {},
Expand DownExpand Up@@ -1614,20 +1676,116 @@ where
}
}
},
LdkEvent::FundingTransactionReadyForSigning { .. } => {
debug_assert!(false, "We currently don't support interactive-tx, so this event should never be emitted.");
// TODO(splicing): Revisit error handling once splicing API is settled in LDK 0.3
LdkEvent::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} => match self.wallet.sign_owned_inputs(unsigned_transaction) {
Ok(partially_signed_tx) => {
match self.channel_manager.funding_transaction_signed(
&channel_id,
&counterparty_node_id,
partially_signed_tx,
) {
Ok(()) => {
log_info!(
self.logger,
"Signed funding transaction for channel {} with counterparty {}",
channel_id,
counterparty_node_id
);
},
Err(e) => {
// TODO(splicing): Abort splice once supported in LDK 0.3
debug_assert!(false, "Failed signing funding transaction: {:?}", e);
log_error!(self.logger, "Failed signing funding transaction: {:?}", e);
Comment thread
tnull marked this conversation as resolved.
},
}
},
Err(()) => log_error!(self.logger, "Failed signing funding transaction"),
},
LdkEvent::SplicePending { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
LdkEvent::SplicePending {
channel_id,
user_channel_id,
counterparty_node_id,
new_funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} pending splice with funding_txo {}",
channel_id,
counterparty_node_id,
new_funding_txo,
);

let event = Event::SplicePending {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
new_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
LdkEvent::SpliceFailed { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
);
LdkEvent::SpliceFailed {
channel_id,
user_channel_id,
counterparty_node_id,
abandoned_funding_txo,
contributed_outputs,
..
} => {
if let Some(funding_txo) = abandoned_funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice with funding_txo {}",
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice",
channel_id,
counterparty_node_id,
);
}

let tx = bitcoin::Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![],
output: contributed_outputs,
};
if let Err(e) = self.wallet.cancel_tx(&tx) {
log_error!(self.logger, "Failed reclaiming unused addresses: {}", e);
return Err(ReplayEvent());
}

let event = Event::SpliceFailed {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
abandoned_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
}
Ok(())
Expand DownExpand Up@@ -1655,6 +1813,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};
event_queue.add_event(expected_event.clone()).await.unwrap();

Expand DownExpand Up@@ -1692,6 +1851,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};

// Check `next_event_async` won't return if the queue is empty and always rather timeout.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Channel splicing support by jkczyz · Pull Request #677 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
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
9 changes: 8 additions & 1 deletion bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,6 +150,10 @@ interface Node {
[Throws=NodeError]
UserChannelId open_announced_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
[Throws=NodeError]
void splice_in([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, u64 splice_amount_sats);
[Throws=NodeError]
void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats);
[Throws=NodeError]
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
[Throws=NodeError]
void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, string? reason);
Expand DownExpand Up@@ -290,6 +294,7 @@ enum NodeError {
"ProbeSendingFailed",
"ChannelCreationFailed",
"ChannelClosingFailed",
"ChannelSplicingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
Expand DownExpand Up@@ -393,8 +398,10 @@ interface Event {
PaymentForwarded(ChannelId prev_channel_id, ChannelId next_channel_id, UserChannelId?
prev_user_channel_id, UserChannelId? next_user_channel_id, PublicKey? prev_node_id, PublicKey? next_node_id, u64? total_fee_earned_msat, u64? skimmed_fee_msat, boolean claim_from_onchain_tx, u64? outbound_amount_forwarded_msat);
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, OutPoint? funding_txo);
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
SplicePending(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint new_funding_txo);
SpliceFailed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint? abandoned_funding_txo);
};

enum PaymentFailureReason {
Expand Down
1 change: 1 addition & 0 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1795,6 +1795,7 @@ fn build_with_store_internal(
wallet,
chain_source,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
1 change: 1 addition & 0 deletions src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -325,6 +325,7 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig {
user_config.manually_accept_inbound_channels = true;
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
config.anchor_channels_config.is_some();
user_config.reject_inbound_splices = false;

if may_announce_channel(config).is_err() {
user_config.accept_forwards_to_priv_channels = false;
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,8 @@ pub enum Error {
ChannelCreationFailed,
/// A channel could not be closed.
ChannelClosingFailed,
/// A channel could not be spliced.
ChannelSplicingFailed,
/// A channel configuration could not be updated.
ChannelConfigUpdateFailed,
/// Persistence failed.
Expand DownExpand Up@@ -145,6 +147,7 @@ impl fmt::Display for Error {
Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."),
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelSplicingFailed => write!(f, "Failed to splice channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
Expand Down
198 changes: 179 additions & 19 deletions src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -199,6 +199,10 @@ pub enum Event {
funding_txo: OutPoint,
},
/// A channel is ready to be used.
///
/// This event is emitted when:
/// - A new channel has been established and is ready for use
/// - An existing channel has been spliced and is ready with the new funding output
ChannelReady {
/// The `channel_id` of the channel.
channel_id: ChannelId,
Expand All@@ -208,6 +212,14 @@ pub enum Event {
///
/// This will be `None` for events serialized by LDK Node v0.1.0 and prior.
counterparty_node_id: Option<PublicKey>,
/// The outpoint of the channel's funding transaction.
///
/// This represents the channel's current funding output, which may change when the
/// channel is spliced. For spliced channels, this will contain the new funding output
/// from the confirmed splice transaction.
///
/// This will be `None` for events serialized by LDK Node v0.6.0 and prior.
funding_txo: Option<OutPoint>,
},
/// A channel has been closed.
ChannelClosed {
Expand All@@ -222,6 +234,28 @@ pub enum Event {
/// This will be `None` for events serialized by LDK Node v0.2.1 and prior.
reason: Option<ClosureReason>,
},
/// A channel splice is pending confirmation on-chain.
SplicePending {
Comment thread
jkczyz marked this conversation as resolved.
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction.
new_funding_txo: OutPoint,
},
/// A channel splice has failed.
SpliceFailed {
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction, if one was created.
abandoned_funding_txo: Option<OutPoint>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this field optional?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's optional in the LDK event because the splice may fail before the funding tx is negotiated.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Maybe it's worth adding a comment explaining this here and (more explicitly) on the LDK event field?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expanded the comment to match the LDK docs, which had more clarity.

},
}

impl_writeable_tlv_based_enum!(Event,
Expand All@@ -246,6 +280,7 @@ impl_writeable_tlv_based_enum!(Event,
(0, channel_id, required),
(1, counterparty_node_id, option),
(2, user_channel_id, required),
(3, funding_txo, option),
},
(4, ChannelPending) => {
(0, channel_id, required),
Expand DownExpand Up@@ -278,7 +313,19 @@ impl_writeable_tlv_based_enum!(Event,
(10, skimmed_fee_msat, option),
(12, claim_from_onchain_tx, required),
(14, outbound_amount_forwarded_msat, option),
}
},
(8, SplicePending) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, new_funding_txo, required),
},
(9, SpliceFailed) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, abandoned_funding_txo, option),
},
);

pub struct EventQueue<L: Deref>
Expand DownExpand Up@@ -1397,14 +1444,28 @@ where
}
},
LdkEvent::ChannelReady {
channel_id, user_channel_id, counterparty_node_id, ..
channel_id,
user_channel_id,
counterparty_node_id,
funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used.",
channel_id,
counterparty_node_id,
);
if let Some(funding_txo) = funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used with funding_txo {}",
Comment thread
tnull marked this conversation as resolved.
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used",
channel_id,
counterparty_node_id,
);
}

if let Some(liquidity_source) = self.liquidity_source.as_ref() {
liquidity_source
Expand All@@ -1416,6 +1477,7 @@ where
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id: Some(counterparty_node_id),
funding_txo,
};
match self.event_queue.add_event(event).await {
Ok(_) => {},
Expand DownExpand Up@@ -1614,20 +1676,116 @@ where
}
}
},
LdkEvent::FundingTransactionReadyForSigning { .. } => {
debug_assert!(false, "We currently don't support interactive-tx, so this event should never be emitted.");
// TODO(splicing): Revisit error handling once splicing API is settled in LDK 0.3
LdkEvent::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} => match self.wallet.sign_owned_inputs(unsigned_transaction) {
Ok(partially_signed_tx) => {
match self.channel_manager.funding_transaction_signed(
&channel_id,
&counterparty_node_id,
partially_signed_tx,
) {
Ok(()) => {
log_info!(
self.logger,
"Signed funding transaction for channel {} with counterparty {}",
channel_id,
counterparty_node_id
);
},
Err(e) => {
// TODO(splicing): Abort splice once supported in LDK 0.3
debug_assert!(false, "Failed signing funding transaction: {:?}", e);
log_error!(self.logger, "Failed signing funding transaction: {:?}", e);
Comment thread
tnull marked this conversation as resolved.
},
}
},
Err(()) => log_error!(self.logger, "Failed signing funding transaction"),
},
LdkEvent::SplicePending { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
LdkEvent::SplicePending {
channel_id,
user_channel_id,
counterparty_node_id,
new_funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} pending splice with funding_txo {}",
channel_id,
counterparty_node_id,
new_funding_txo,
);

let event = Event::SplicePending {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
new_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
LdkEvent::SpliceFailed { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
);
LdkEvent::SpliceFailed {
channel_id,
user_channel_id,
counterparty_node_id,
abandoned_funding_txo,
contributed_outputs,
..
} => {
if let Some(funding_txo) = abandoned_funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice with funding_txo {}",
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice",
channel_id,
counterparty_node_id,
);
}

let tx = bitcoin::Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![],
output: contributed_outputs,
};
if let Err(e) = self.wallet.cancel_tx(&tx) {
log_error!(self.logger, "Failed reclaiming unused addresses: {}", e);
return Err(ReplayEvent());
}

let event = Event::SpliceFailed {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
abandoned_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
}
Ok(())
Expand DownExpand Up@@ -1655,6 +1813,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};
event_queue.add_event(expected_event.clone()).await.unwrap();

Expand DownExpand Up@@ -1692,6 +1851,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};

// Check `next_event_async` won't return if the queue is empty and always rather timeout.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Channel splicing support by jkczyz · Pull Request #677 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
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
9 changes: 8 additions & 1 deletion bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,6 +150,10 @@ interface Node {
[Throws=NodeError]
UserChannelId open_announced_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
[Throws=NodeError]
void splice_in([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, u64 splice_amount_sats);
[Throws=NodeError]
void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats);
[Throws=NodeError]
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
[Throws=NodeError]
void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, string? reason);
Expand DownExpand Up@@ -290,6 +294,7 @@ enum NodeError {
"ProbeSendingFailed",
"ChannelCreationFailed",
"ChannelClosingFailed",
"ChannelSplicingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
Expand DownExpand Up@@ -393,8 +398,10 @@ interface Event {
PaymentForwarded(ChannelId prev_channel_id, ChannelId next_channel_id, UserChannelId?
prev_user_channel_id, UserChannelId? next_user_channel_id, PublicKey? prev_node_id, PublicKey? next_node_id, u64? total_fee_earned_msat, u64? skimmed_fee_msat, boolean claim_from_onchain_tx, u64? outbound_amount_forwarded_msat);
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, OutPoint? funding_txo);
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
SplicePending(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint new_funding_txo);
SpliceFailed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint? abandoned_funding_txo);
};

enum PaymentFailureReason {
Expand Down
1 change: 1 addition & 0 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1795,6 +1795,7 @@ fn build_with_store_internal(
wallet,
chain_source,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
1 change: 1 addition & 0 deletions src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -325,6 +325,7 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig {
user_config.manually_accept_inbound_channels = true;
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
config.anchor_channels_config.is_some();
user_config.reject_inbound_splices = false;

if may_announce_channel(config).is_err() {
user_config.accept_forwards_to_priv_channels = false;
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,8 @@ pub enum Error {
ChannelCreationFailed,
/// A channel could not be closed.
ChannelClosingFailed,
/// A channel could not be spliced.
ChannelSplicingFailed,
/// A channel configuration could not be updated.
ChannelConfigUpdateFailed,
/// Persistence failed.
Expand DownExpand Up@@ -145,6 +147,7 @@ impl fmt::Display for Error {
Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."),
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelSplicingFailed => write!(f, "Failed to splice channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
Expand Down
198 changes: 179 additions & 19 deletions src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -199,6 +199,10 @@ pub enum Event {
funding_txo: OutPoint,
},
/// A channel is ready to be used.
///
/// This event is emitted when:
/// - A new channel has been established and is ready for use
/// - An existing channel has been spliced and is ready with the new funding output
ChannelReady {
/// The `channel_id` of the channel.
channel_id: ChannelId,
Expand All@@ -208,6 +212,14 @@ pub enum Event {
///
/// This will be `None` for events serialized by LDK Node v0.1.0 and prior.
counterparty_node_id: Option<PublicKey>,
/// The outpoint of the channel's funding transaction.
///
/// This represents the channel's current funding output, which may change when the
/// channel is spliced. For spliced channels, this will contain the new funding output
/// from the confirmed splice transaction.
///
/// This will be `None` for events serialized by LDK Node v0.6.0 and prior.
funding_txo: Option<OutPoint>,
},
/// A channel has been closed.
ChannelClosed {
Expand All@@ -222,6 +234,28 @@ pub enum Event {
/// This will be `None` for events serialized by LDK Node v0.2.1 and prior.
reason: Option<ClosureReason>,
},
/// A channel splice is pending confirmation on-chain.
SplicePending {
Comment thread
jkczyz marked this conversation as resolved.
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction.
new_funding_txo: OutPoint,
},
/// A channel splice has failed.
SpliceFailed {
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction, if one was created.
abandoned_funding_txo: Option<OutPoint>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this field optional?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's optional in the LDK event because the splice may fail before the funding tx is negotiated.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Maybe it's worth adding a comment explaining this here and (more explicitly) on the LDK event field?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expanded the comment to match the LDK docs, which had more clarity.

},
}

impl_writeable_tlv_based_enum!(Event,
Expand All@@ -246,6 +280,7 @@ impl_writeable_tlv_based_enum!(Event,
(0, channel_id, required),
(1, counterparty_node_id, option),
(2, user_channel_id, required),
(3, funding_txo, option),
},
(4, ChannelPending) => {
(0, channel_id, required),
Expand DownExpand Up@@ -278,7 +313,19 @@ impl_writeable_tlv_based_enum!(Event,
(10, skimmed_fee_msat, option),
(12, claim_from_onchain_tx, required),
(14, outbound_amount_forwarded_msat, option),
}
},
(8, SplicePending) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, new_funding_txo, required),
},
(9, SpliceFailed) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, abandoned_funding_txo, option),
},
);

pub struct EventQueue<L: Deref>
Expand DownExpand Up@@ -1397,14 +1444,28 @@ where
}
},
LdkEvent::ChannelReady {
channel_id, user_channel_id, counterparty_node_id, ..
channel_id,
user_channel_id,
counterparty_node_id,
funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used.",
channel_id,
counterparty_node_id,
);
if let Some(funding_txo) = funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used with funding_txo {}",
Comment thread
tnull marked this conversation as resolved.
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used",
channel_id,
counterparty_node_id,
);
}

if let Some(liquidity_source) = self.liquidity_source.as_ref() {
liquidity_source
Expand All@@ -1416,6 +1477,7 @@ where
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id: Some(counterparty_node_id),
funding_txo,
};
match self.event_queue.add_event(event).await {
Ok(_) => {},
Expand DownExpand Up@@ -1614,20 +1676,116 @@ where
}
}
},
LdkEvent::FundingTransactionReadyForSigning { .. } => {
debug_assert!(false, "We currently don't support interactive-tx, so this event should never be emitted.");
// TODO(splicing): Revisit error handling once splicing API is settled in LDK 0.3
LdkEvent::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} => match self.wallet.sign_owned_inputs(unsigned_transaction) {
Ok(partially_signed_tx) => {
match self.channel_manager.funding_transaction_signed(
&channel_id,
&counterparty_node_id,
partially_signed_tx,
) {
Ok(()) => {
log_info!(
self.logger,
"Signed funding transaction for channel {} with counterparty {}",
channel_id,
counterparty_node_id
);
},
Err(e) => {
// TODO(splicing): Abort splice once supported in LDK 0.3
debug_assert!(false, "Failed signing funding transaction: {:?}", e);
log_error!(self.logger, "Failed signing funding transaction: {:?}", e);
Comment thread
tnull marked this conversation as resolved.
},
}
},
Err(()) => log_error!(self.logger, "Failed signing funding transaction"),
},
LdkEvent::SplicePending { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
LdkEvent::SplicePending {
channel_id,
user_channel_id,
counterparty_node_id,
new_funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} pending splice with funding_txo {}",
channel_id,
counterparty_node_id,
new_funding_txo,
);

let event = Event::SplicePending {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
new_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
LdkEvent::SpliceFailed { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
);
LdkEvent::SpliceFailed {
channel_id,
user_channel_id,
counterparty_node_id,
abandoned_funding_txo,
contributed_outputs,
..
} => {
if let Some(funding_txo) = abandoned_funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice with funding_txo {}",
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice",
channel_id,
counterparty_node_id,
);
}

let tx = bitcoin::Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![],
output: contributed_outputs,
};
if let Err(e) = self.wallet.cancel_tx(&tx) {
log_error!(self.logger, "Failed reclaiming unused addresses: {}", e);
return Err(ReplayEvent());
}

let event = Event::SpliceFailed {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
abandoned_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
}
Ok(())
Expand DownExpand Up@@ -1655,6 +1813,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};
event_queue.add_event(expected_event.clone()).await.unwrap();

Expand DownExpand Up@@ -1692,6 +1851,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};

// Check `next_event_async` won't return if the queue is empty and always rather timeout.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Channel splicing support by jkczyz · Pull Request #677 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
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
9 changes: 8 additions & 1 deletion bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,6 +150,10 @@ interface Node {
[Throws=NodeError]
UserChannelId open_announced_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
[Throws=NodeError]
void splice_in([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, u64 splice_amount_sats);
[Throws=NodeError]
void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats);
[Throws=NodeError]
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
[Throws=NodeError]
void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, string? reason);
Expand DownExpand Up@@ -290,6 +294,7 @@ enum NodeError {
"ProbeSendingFailed",
"ChannelCreationFailed",
"ChannelClosingFailed",
"ChannelSplicingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
Expand DownExpand Up@@ -393,8 +398,10 @@ interface Event {
PaymentForwarded(ChannelId prev_channel_id, ChannelId next_channel_id, UserChannelId?
prev_user_channel_id, UserChannelId? next_user_channel_id, PublicKey? prev_node_id, PublicKey? next_node_id, u64? total_fee_earned_msat, u64? skimmed_fee_msat, boolean claim_from_onchain_tx, u64? outbound_amount_forwarded_msat);
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, OutPoint? funding_txo);
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
SplicePending(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint new_funding_txo);
SpliceFailed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint? abandoned_funding_txo);
};

enum PaymentFailureReason {
Expand Down
1 change: 1 addition & 0 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1795,6 +1795,7 @@ fn build_with_store_internal(
wallet,
chain_source,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
1 change: 1 addition & 0 deletions src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -325,6 +325,7 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig {
user_config.manually_accept_inbound_channels = true;
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
config.anchor_channels_config.is_some();
user_config.reject_inbound_splices = false;

if may_announce_channel(config).is_err() {
user_config.accept_forwards_to_priv_channels = false;
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,8 @@ pub enum Error {
ChannelCreationFailed,
/// A channel could not be closed.
ChannelClosingFailed,
/// A channel could not be spliced.
ChannelSplicingFailed,
/// A channel configuration could not be updated.
ChannelConfigUpdateFailed,
/// Persistence failed.
Expand DownExpand Up@@ -145,6 +147,7 @@ impl fmt::Display for Error {
Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."),
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelSplicingFailed => write!(f, "Failed to splice channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
Expand Down
198 changes: 179 additions & 19 deletions src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -199,6 +199,10 @@ pub enum Event {
funding_txo: OutPoint,
},
/// A channel is ready to be used.
///
/// This event is emitted when:
/// - A new channel has been established and is ready for use
/// - An existing channel has been spliced and is ready with the new funding output
ChannelReady {
/// The `channel_id` of the channel.
channel_id: ChannelId,
Expand All@@ -208,6 +212,14 @@ pub enum Event {
///
/// This will be `None` for events serialized by LDK Node v0.1.0 and prior.
counterparty_node_id: Option<PublicKey>,
/// The outpoint of the channel's funding transaction.
///
/// This represents the channel's current funding output, which may change when the
/// channel is spliced. For spliced channels, this will contain the new funding output
/// from the confirmed splice transaction.
///
/// This will be `None` for events serialized by LDK Node v0.6.0 and prior.
funding_txo: Option<OutPoint>,
},
/// A channel has been closed.
ChannelClosed {
Expand All@@ -222,6 +234,28 @@ pub enum Event {
/// This will be `None` for events serialized by LDK Node v0.2.1 and prior.
reason: Option<ClosureReason>,
},
/// A channel splice is pending confirmation on-chain.
SplicePending {
Comment thread
jkczyz marked this conversation as resolved.
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction.
new_funding_txo: OutPoint,
},
/// A channel splice has failed.
SpliceFailed {
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction, if one was created.
abandoned_funding_txo: Option<OutPoint>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this field optional?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's optional in the LDK event because the splice may fail before the funding tx is negotiated.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Maybe it's worth adding a comment explaining this here and (more explicitly) on the LDK event field?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expanded the comment to match the LDK docs, which had more clarity.

},
}

impl_writeable_tlv_based_enum!(Event,
Expand All@@ -246,6 +280,7 @@ impl_writeable_tlv_based_enum!(Event,
(0, channel_id, required),
(1, counterparty_node_id, option),
(2, user_channel_id, required),
(3, funding_txo, option),
},
(4, ChannelPending) => {
(0, channel_id, required),
Expand DownExpand Up@@ -278,7 +313,19 @@ impl_writeable_tlv_based_enum!(Event,
(10, skimmed_fee_msat, option),
(12, claim_from_onchain_tx, required),
(14, outbound_amount_forwarded_msat, option),
}
},
(8, SplicePending) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, new_funding_txo, required),
},
(9, SpliceFailed) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, abandoned_funding_txo, option),
},
);

pub struct EventQueue<L: Deref>
Expand DownExpand Up@@ -1397,14 +1444,28 @@ where
}
},
LdkEvent::ChannelReady {
channel_id, user_channel_id, counterparty_node_id, ..
channel_id,
user_channel_id,
counterparty_node_id,
funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used.",
channel_id,
counterparty_node_id,
);
if let Some(funding_txo) = funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used with funding_txo {}",
Comment thread
tnull marked this conversation as resolved.
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used",
channel_id,
counterparty_node_id,
);
}

if let Some(liquidity_source) = self.liquidity_source.as_ref() {
liquidity_source
Expand All@@ -1416,6 +1477,7 @@ where
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id: Some(counterparty_node_id),
funding_txo,
};
match self.event_queue.add_event(event).await {
Ok(_) => {},
Expand DownExpand Up@@ -1614,20 +1676,116 @@ where
}
}
},
LdkEvent::FundingTransactionReadyForSigning { .. } => {
debug_assert!(false, "We currently don't support interactive-tx, so this event should never be emitted.");
// TODO(splicing): Revisit error handling once splicing API is settled in LDK 0.3
LdkEvent::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} => match self.wallet.sign_owned_inputs(unsigned_transaction) {
Ok(partially_signed_tx) => {
match self.channel_manager.funding_transaction_signed(
&channel_id,
&counterparty_node_id,
partially_signed_tx,
) {
Ok(()) => {
log_info!(
self.logger,
"Signed funding transaction for channel {} with counterparty {}",
channel_id,
counterparty_node_id
);
},
Err(e) => {
// TODO(splicing): Abort splice once supported in LDK 0.3
debug_assert!(false, "Failed signing funding transaction: {:?}", e);
log_error!(self.logger, "Failed signing funding transaction: {:?}", e);
Comment thread
tnull marked this conversation as resolved.
},
}
},
Err(()) => log_error!(self.logger, "Failed signing funding transaction"),
},
LdkEvent::SplicePending { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
LdkEvent::SplicePending {
channel_id,
user_channel_id,
counterparty_node_id,
new_funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} pending splice with funding_txo {}",
channel_id,
counterparty_node_id,
new_funding_txo,
);

let event = Event::SplicePending {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
new_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
LdkEvent::SpliceFailed { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
);
LdkEvent::SpliceFailed {
channel_id,
user_channel_id,
counterparty_node_id,
abandoned_funding_txo,
contributed_outputs,
..
} => {
if let Some(funding_txo) = abandoned_funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice with funding_txo {}",
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice",
channel_id,
counterparty_node_id,
);
}

let tx = bitcoin::Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![],
output: contributed_outputs,
};
if let Err(e) = self.wallet.cancel_tx(&tx) {
log_error!(self.logger, "Failed reclaiming unused addresses: {}", e);
return Err(ReplayEvent());
}

let event = Event::SpliceFailed {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
abandoned_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
}
Ok(())
Expand DownExpand Up@@ -1655,6 +1813,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};
event_queue.add_event(expected_event.clone()).await.unwrap();

Expand DownExpand Up@@ -1692,6 +1851,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};

// Check `next_event_async` won't return if the queue is empty and always rather timeout.
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Channel splicing support by jkczyz · Pull Request #677 · lightningdevkit/ldk-node · GitHub
Skip to content
Merged
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
9 changes: 8 additions & 1 deletion bindings/ldk_node.udl
Original file line numberDiff line numberDiff line change
Expand Up@@ -150,6 +150,10 @@ interface Node {
[Throws=NodeError]
UserChannelId open_announced_channel(PublicKey node_id, SocketAddress address, u64 channel_amount_sats, u64? push_to_counterparty_msat, ChannelConfig? channel_config);
[Throws=NodeError]
void splice_in([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, u64 splice_amount_sats);
[Throws=NodeError]
void splice_out([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, [ByRef]Address address, u64 splice_amount_sats);
[Throws=NodeError]
void close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id);
[Throws=NodeError]
void force_close_channel([ByRef]UserChannelId user_channel_id, PublicKey counterparty_node_id, string? reason);
Expand DownExpand Up@@ -290,6 +294,7 @@ enum NodeError {
"ProbeSendingFailed",
"ChannelCreationFailed",
"ChannelClosingFailed",
"ChannelSplicingFailed",
"ChannelConfigUpdateFailed",
"PersistenceFailed",
"FeerateEstimationUpdateFailed",
Expand DownExpand Up@@ -393,8 +398,10 @@ interface Event {
PaymentForwarded(ChannelId prev_channel_id, ChannelId next_channel_id, UserChannelId?
prev_user_channel_id, UserChannelId? next_user_channel_id, PublicKey? prev_node_id, PublicKey? next_node_id, u64? total_fee_earned_msat, u64? skimmed_fee_msat, boolean claim_from_onchain_tx, u64? outbound_amount_forwarded_msat);
ChannelPending(ChannelId channel_id, UserChannelId user_channel_id, ChannelId former_temporary_channel_id, PublicKey counterparty_node_id, OutPoint funding_txo);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id);
ChannelReady(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, OutPoint? funding_txo);
ChannelClosed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey? counterparty_node_id, ClosureReason? reason);
SplicePending(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint new_funding_txo);
SpliceFailed(ChannelId channel_id, UserChannelId user_channel_id, PublicKey counterparty_node_id, OutPoint? abandoned_funding_txo);
};

enum PaymentFailureReason {
Expand Down
1 change: 1 addition & 0 deletions src/builder.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -1795,6 +1795,7 @@ fn build_with_store_internal(
wallet,
chain_source,
tx_broadcaster,
fee_estimator,
event_queue,
channel_manager,
chain_monitor,
Expand Down
1 change: 1 addition & 0 deletions src/config.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -325,6 +325,7 @@ pub(crate) fn default_user_config(config: &Config) -> UserConfig {
user_config.manually_accept_inbound_channels = true;
user_config.channel_handshake_config.negotiate_anchors_zero_fee_htlc_tx =
config.anchor_channels_config.is_some();
user_config.reject_inbound_splices = false;

if may_announce_channel(config).is_err() {
user_config.accept_forwards_to_priv_channels = false;
Expand Down
3 changes: 3 additions & 0 deletions src/error.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -43,6 +43,8 @@ pub enum Error {
ChannelCreationFailed,
/// A channel could not be closed.
ChannelClosingFailed,
/// A channel could not be spliced.
ChannelSplicingFailed,
/// A channel configuration could not be updated.
ChannelConfigUpdateFailed,
/// Persistence failed.
Expand DownExpand Up@@ -145,6 +147,7 @@ impl fmt::Display for Error {
Self::ProbeSendingFailed => write!(f, "Failed to send the given payment probe."),
Self::ChannelCreationFailed => write!(f, "Failed to create channel."),
Self::ChannelClosingFailed => write!(f, "Failed to close channel."),
Self::ChannelSplicingFailed => write!(f, "Failed to splice channel."),
Self::ChannelConfigUpdateFailed => write!(f, "Failed to update channel config."),
Self::PersistenceFailed => write!(f, "Failed to persist data."),
Self::FeerateEstimationUpdateFailed => {
Expand Down
198 changes: 179 additions & 19 deletions src/event.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -199,6 +199,10 @@ pub enum Event {
funding_txo: OutPoint,
},
/// A channel is ready to be used.
///
/// This event is emitted when:
/// - A new channel has been established and is ready for use
/// - An existing channel has been spliced and is ready with the new funding output
ChannelReady {
/// The `channel_id` of the channel.
channel_id: ChannelId,
Expand All@@ -208,6 +212,14 @@ pub enum Event {
///
/// This will be `None` for events serialized by LDK Node v0.1.0 and prior.
counterparty_node_id: Option<PublicKey>,
/// The outpoint of the channel's funding transaction.
///
/// This represents the channel's current funding output, which may change when the
/// channel is spliced. For spliced channels, this will contain the new funding output
/// from the confirmed splice transaction.
///
/// This will be `None` for events serialized by LDK Node v0.6.0 and prior.
funding_txo: Option<OutPoint>,
},
/// A channel has been closed.
ChannelClosed {
Expand All@@ -222,6 +234,28 @@ pub enum Event {
/// This will be `None` for events serialized by LDK Node v0.2.1 and prior.
reason: Option<ClosureReason>,
},
/// A channel splice is pending confirmation on-chain.
SplicePending {
Comment thread
jkczyz marked this conversation as resolved.
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction.
new_funding_txo: OutPoint,
},
/// A channel splice has failed.
SpliceFailed {
/// The `channel_id` of the channel.
channel_id: ChannelId,
/// The `user_channel_id` of the channel.
user_channel_id: UserChannelId,
/// The `node_id` of the channel counterparty.
counterparty_node_id: PublicKey,
/// The outpoint of the channel's splice funding transaction, if one was created.
abandoned_funding_txo: Option<OutPoint>,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this field optional?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's optional in the LDK event because the splice may fail before the funding tx is negotiated.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah. Maybe it's worth adding a comment explaining this here and (more explicitly) on the LDK event field?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Expanded the comment to match the LDK docs, which had more clarity.

},
}

impl_writeable_tlv_based_enum!(Event,
Expand All@@ -246,6 +280,7 @@ impl_writeable_tlv_based_enum!(Event,
(0, channel_id, required),
(1, counterparty_node_id, option),
(2, user_channel_id, required),
(3, funding_txo, option),
},
(4, ChannelPending) => {
(0, channel_id, required),
Expand DownExpand Up@@ -278,7 +313,19 @@ impl_writeable_tlv_based_enum!(Event,
(10, skimmed_fee_msat, option),
(12, claim_from_onchain_tx, required),
(14, outbound_amount_forwarded_msat, option),
}
},
(8, SplicePending) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, new_funding_txo, required),
},
(9, SpliceFailed) => {
(1, channel_id, required),
(3, counterparty_node_id, required),
(5, user_channel_id, required),
(7, abandoned_funding_txo, option),
},
);

pub struct EventQueue<L: Deref>
Expand DownExpand Up@@ -1397,14 +1444,28 @@ where
}
},
LdkEvent::ChannelReady {
channel_id, user_channel_id, counterparty_node_id, ..
channel_id,
user_channel_id,
counterparty_node_id,
funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used.",
channel_id,
counterparty_node_id,
);
if let Some(funding_txo) = funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used with funding_txo {}",
Comment thread
tnull marked this conversation as resolved.
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} ready to be used",
channel_id,
counterparty_node_id,
);
}

if let Some(liquidity_source) = self.liquidity_source.as_ref() {
liquidity_source
Expand All@@ -1416,6 +1477,7 @@ where
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id: Some(counterparty_node_id),
funding_txo,
};
match self.event_queue.add_event(event).await {
Ok(_) => {},
Expand DownExpand Up@@ -1614,20 +1676,116 @@ where
}
}
},
LdkEvent::FundingTransactionReadyForSigning { .. } => {
debug_assert!(false, "We currently don't support interactive-tx, so this event should never be emitted.");
// TODO(splicing): Revisit error handling once splicing API is settled in LDK 0.3
LdkEvent::FundingTransactionReadyForSigning {
channel_id,
counterparty_node_id,
unsigned_transaction,
..
} => match self.wallet.sign_owned_inputs(unsigned_transaction) {
Ok(partially_signed_tx) => {
match self.channel_manager.funding_transaction_signed(
&channel_id,
&counterparty_node_id,
partially_signed_tx,
) {
Ok(()) => {
log_info!(
self.logger,
"Signed funding transaction for channel {} with counterparty {}",
channel_id,
counterparty_node_id
);
},
Err(e) => {
// TODO(splicing): Abort splice once supported in LDK 0.3
debug_assert!(false, "Failed signing funding transaction: {:?}", e);
log_error!(self.logger, "Failed signing funding transaction: {:?}", e);
Comment thread
tnull marked this conversation as resolved.
},
}
},
Err(()) => log_error!(self.logger, "Failed signing funding transaction"),
},
LdkEvent::SplicePending { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
LdkEvent::SplicePending {
channel_id,
user_channel_id,
counterparty_node_id,
new_funding_txo,
..
} => {
log_info!(
self.logger,
"Channel {} with counterparty {} pending splice with funding_txo {}",
channel_id,
counterparty_node_id,
new_funding_txo,
);

let event = Event::SplicePending {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
new_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
LdkEvent::SpliceFailed { .. } => {
debug_assert!(
false,
"We currently don't support splicing, so this event should never be emitted."
);
LdkEvent::SpliceFailed {
channel_id,
user_channel_id,
counterparty_node_id,
abandoned_funding_txo,
contributed_outputs,
..
} => {
if let Some(funding_txo) = abandoned_funding_txo {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice with funding_txo {}",
channel_id,
counterparty_node_id,
funding_txo,
);
} else {
log_info!(
self.logger,
"Channel {} with counterparty {} failed splice",
channel_id,
counterparty_node_id,
);
}

let tx = bitcoin::Transaction {
version: bitcoin::transaction::Version::TWO,
lock_time: bitcoin::absolute::LockTime::ZERO,
input: vec![],
output: contributed_outputs,
};
if let Err(e) = self.wallet.cancel_tx(&tx) {
log_error!(self.logger, "Failed reclaiming unused addresses: {}", e);
return Err(ReplayEvent());
}

let event = Event::SpliceFailed {
channel_id,
user_channel_id: UserChannelId(user_channel_id),
counterparty_node_id,
abandoned_funding_txo,
};

match self.event_queue.add_event(event).await {
Ok(_) => {},
Err(e) => {
log_error!(self.logger, "Failed to push to event queue: {}", e);
return Err(ReplayEvent());
},
};
},
}
Ok(())
Expand DownExpand Up@@ -1655,6 +1813,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};
event_queue.add_event(expected_event.clone()).await.unwrap();

Expand DownExpand Up@@ -1692,6 +1851,7 @@ mod tests {
channel_id: ChannelId([23u8; 32]),
user_channel_id: UserChannelId(2323),
counterparty_node_id: None,
funding_txo: None,
};

// Check `next_event_async` won't return if the queue is empty and always rather timeout.
Expand Down
Loading