Skip to content

Commit af46a83

Browse files
authored
Merge pull request #992 from tankyleo/2026-07-0fc-followups
Follow-ups to #660
2 parents abbed3e + ebef8da commit af46a83

12 files changed

Lines changed: 191 additions & 13 deletions

File tree

.github/workflows/0fc-integration.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,4 @@ jobs:
4646
echo "ELECTRS_EXE=$( pwd )/bin/electrs-${{ runner.os }}-${{ runner.arch }}" >> "$GITHUB_ENV"
4747
- name: Test with 0FC enabled
4848
run: |
49-
RUSTFLAGS="--cfg no_download --cfg cycle_tests --cfg tokio_unstable --cfg zero_fee_commitment_tests" cargo test -- --test-threads=1
49+
RUSTFLAGS="--cfg no_download --cfg cycle_tests --cfg tokio_unstable --cfg zero_fee_commitment_tests" cargo test -- --test-threads=1 --no-capture

.github/workflows/eclair-integration.yml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,18 @@ concurrency:
88

99
jobs:
1010
check-eclair:
11+
name: check-eclair (${{ matrix.name }})
1112
timeout-minutes: 60
1213
runs-on: ubuntu-latest
14+
strategy:
15+
matrix:
16+
include:
17+
- name: standard
18+
eclair_extra_java_opts: ""
19+
rustflags: "--cfg eclair_test"
20+
- name: zero-fee-commitments
21+
eclair_extra_java_opts: "-Declair.features.zero_fee_commitments=optional"
22+
rustflags: "--cfg eclair_test --cfg zero_fee_commitment_tests"
1323
steps:
1424
- name: Checkout repository
1525
uses: actions/checkout@v4
@@ -37,6 +47,8 @@ jobs:
3747
docker compose -p ldk-node -f tests/docker/docker-compose-eclair.yml exec bitcoin bitcoin-cli -regtest -rpcuser=user -rpcpassword=pass createwallet ldk_node_test
3848
3949
- name: Start Eclair
50+
env:
51+
ECLAIR_EXTRA_JAVA_OPTS: ${{ matrix.eclair_extra_java_opts }}
4052
run: docker compose -p ldk-node -f tests/docker/docker-compose-eclair.yml up -d eclair
4153

4254
- name: Wait for Eclair to be ready
@@ -54,4 +66,5 @@ jobs:
5466
exit 1
5567
5668
- name: Run Eclair integration tests
57-
run: RUSTFLAGS="--cfg eclair_test" cargo test --test integration_tests_eclair -- --show-output --test-threads=1
69+
run: |
70+
RUSTFLAGS="${{ matrix.rustflags }}" cargo test --test integration_tests_eclair -- --show-output --test-threads=1

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
- `EsploraSyncConfig` and `ElectrumSyncConfig` now support `force_wallet_full_scan`. When set,
1919
the on-chain wallet keeps using BDK `full_scan` instead of incremental sync until a full scan
2020
succeeds, allowing restored wallets to rediscover funds sent to previously-unknown addresses.
21+
- The `ChannelDetails` returned by `Node::list_channels` now exposes the negotiated
22+
`ChannelTypeFeatures`.
2123
- `Config::anchor_channels_config` is no longer optional, hence anchor channels can no longer be
2224
disabled. We still negotiate legacy channels if the peer does not support anchor channels.
2325

src/ffi/types.rs

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ pub use lightning_liquidity::lsps0::ser::LSPSDateTime;
4444
pub use lightning_liquidity::lsps1::msgs::{
4545
LSPS1ChannelInfo, LSPS1OrderId, LSPS1OrderParams, LSPS1PaymentState,
4646
};
47-
use lightning_types::features::{InitFeatures as LdkInitFeatures, NodeFeatures as LdkNodeFeatures};
47+
use lightning_types::features::{
48+
ChannelTypeFeatures as LdkChannelTypeFeatures, InitFeatures as LdkInitFeatures,
49+
NodeFeatures as LdkNodeFeatures,
50+
};
4851
pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
4952
pub use lightning_types::string::UntrustedString;
5053
use vss_client::headers::{
@@ -1817,6 +1820,102 @@ impl From<LdkNodeFeatures> for NodeFeatures {
18171820
}
18181821
}
18191822

1823+
#[derive(Debug, Clone, PartialEq, Eq, uniffi::Object)]
1824+
#[uniffi::export(Debug, Eq)]
1825+
pub struct ChannelTypeFeatures {
1826+
pub(crate) inner: LdkChannelTypeFeatures,
1827+
}
1828+
1829+
#[uniffi::export]
1830+
impl ChannelTypeFeatures {
1831+
/// Constructs channel type features from big-endian BOLT 9 encoded bytes.
1832+
#[uniffi::constructor]
1833+
pub fn from_bytes(bytes: &[u8]) -> Self {
1834+
Self { inner: LdkChannelTypeFeatures::from_be_bytes(bytes.to_vec()) }
1835+
}
1836+
1837+
/// Returns the BOLT 9 big-endian encoded representation of these features.
1838+
pub fn to_bytes(&self) -> Vec<u8> {
1839+
self.inner.encode()
1840+
}
1841+
1842+
/// Whether this channel type advertises support for `option_static_remotekey`.
1843+
pub fn supports_static_remote_key(&self) -> bool {
1844+
self.inner.supports_static_remote_key()
1845+
}
1846+
1847+
/// Whether this channel type requires `option_static_remotekey`.
1848+
pub fn requires_static_remote_key(&self) -> bool {
1849+
self.inner.requires_static_remote_key()
1850+
}
1851+
1852+
/// Whether this channel type advertises support for `option_anchors_zero_fee_htlc_tx`.
1853+
pub fn supports_anchors_zero_fee_htlc_tx(&self) -> bool {
1854+
self.inner.supports_anchors_zero_fee_htlc_tx()
1855+
}
1856+
1857+
/// Whether this channel type requires `option_anchors_zero_fee_htlc_tx`.
1858+
pub fn requires_anchors_zero_fee_htlc_tx(&self) -> bool {
1859+
self.inner.requires_anchors_zero_fee_htlc_tx()
1860+
}
1861+
1862+
/// Whether this channel type advertises support for `option_anchors_nonzero_fee_htlc_tx`.
1863+
pub fn supports_anchors_nonzero_fee_htlc_tx(&self) -> bool {
1864+
self.inner.supports_anchors_nonzero_fee_htlc_tx()
1865+
}
1866+
1867+
/// Whether this channel type requires `option_anchors_nonzero_fee_htlc_tx`.
1868+
pub fn requires_anchors_nonzero_fee_htlc_tx(&self) -> bool {
1869+
self.inner.requires_anchors_nonzero_fee_htlc_tx()
1870+
}
1871+
1872+
/// Whether this channel type advertises support for `option_taproot`.
1873+
pub fn supports_taproot(&self) -> bool {
1874+
self.inner.supports_taproot()
1875+
}
1876+
1877+
/// Whether this channel type requires `option_taproot`.
1878+
pub fn requires_taproot(&self) -> bool {
1879+
self.inner.requires_taproot()
1880+
}
1881+
1882+
/// Whether this channel type advertises support for `option_scid_alias`.
1883+
pub fn supports_scid_privacy(&self) -> bool {
1884+
self.inner.supports_scid_privacy()
1885+
}
1886+
1887+
/// Whether this channel type requires `option_scid_alias`.
1888+
pub fn requires_scid_privacy(&self) -> bool {
1889+
self.inner.requires_scid_privacy()
1890+
}
1891+
1892+
/// Whether this channel type advertises support for `option_zeroconf`.
1893+
pub fn supports_zero_conf(&self) -> bool {
1894+
self.inner.supports_zero_conf()
1895+
}
1896+
1897+
/// Whether this channel type requires `option_zeroconf`.
1898+
pub fn requires_zero_conf(&self) -> bool {
1899+
self.inner.requires_zero_conf()
1900+
}
1901+
1902+
/// Whether this channel type advertises support for `option_zero_fee_commitments`.
1903+
pub fn supports_anchor_zero_fee_commitments(&self) -> bool {
1904+
self.inner.supports_anchor_zero_fee_commitments()
1905+
}
1906+
1907+
/// Whether this channel type requires `option_zero_fee_commitments`.
1908+
pub fn requires_anchor_zero_fee_commitments(&self) -> bool {
1909+
self.inner.requires_anchor_zero_fee_commitments()
1910+
}
1911+
}
1912+
1913+
impl From<LdkChannelTypeFeatures> for ChannelTypeFeatures {
1914+
fn from(features: LdkChannelTypeFeatures) -> Self {
1915+
Self { inner: features }
1916+
}
1917+
}
1918+
18201919
#[derive(Debug, Clone, PartialEq, Eq, uniffi::Object)]
18211920
#[uniffi::export(Debug, Eq)]
18221921
pub struct InitFeatures {
@@ -2191,6 +2290,27 @@ mod tests {
21912290
(ldk_invoice, wrapped_invoice)
21922291
}
21932292

2293+
#[test]
2294+
fn test_channel_type_feature_accessors() {
2295+
let mut optional = LdkChannelTypeFeatures::empty();
2296+
optional.set_scid_privacy_optional();
2297+
optional.set_zero_conf_optional();
2298+
let optional = ChannelTypeFeatures::from(optional);
2299+
assert!(optional.supports_scid_privacy());
2300+
assert!(!optional.requires_scid_privacy());
2301+
assert!(optional.supports_zero_conf());
2302+
assert!(!optional.requires_zero_conf());
2303+
2304+
let mut required = LdkChannelTypeFeatures::empty();
2305+
required.set_scid_privacy_required();
2306+
required.set_zero_conf_required();
2307+
let required = ChannelTypeFeatures::from(required);
2308+
assert!(required.supports_scid_privacy());
2309+
assert!(required.requires_scid_privacy());
2310+
assert!(required.supports_zero_conf());
2311+
assert!(required.requires_zero_conf());
2312+
}
2313+
21942314
#[test]
21952315
fn test_invoice_description_conversion() {
21962316
let hash = "09d08d4865e8af9266f6cc7c0ae23a1d6bf868207cf8f7c5979b9f6ed850dfb0".to_string();

src/lib.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,16 @@ impl Node {
292292
return Err(Error::AlreadyRunning);
293293
}
294294

295+
match self.start_inner(&mut is_running_lock) {
296+
Ok(()) => Ok(()),
297+
Err(e) => {
298+
self.chain_source.stop();
299+
Err(e)
300+
},
301+
}
302+
}
303+
304+
fn start_inner(&self, is_running_lock: &mut bool) -> Result<(), Error> {
295305
log_info!(
296306
self.logger,
297307
"Starting up LDK Node with node ID {} on network: {}",

src/tx_broadcaster.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use crate::logger::{log_error, LdkLogger};
1818
use crate::types::Wallet;
1919
use crate::Error;
2020

21-
const BCAST_PACKAGE_QUEUE_SIZE: usize = 50;
21+
const BCAST_PACKAGE_QUEUE_SIZE: usize = 256;
2222

2323
/// A package of transactions that LDK handed to the broadcaster in one `broadcast_transactions`
2424
/// call, along with each transaction's type. Queued until the background task classifies and

src/types.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ use lightning::util::sweep::OutputSweeper;
3939
use lightning_block_sync::gossip::GossipVerifier;
4040
use lightning_liquidity::utils::time::DefaultTimeProvider;
4141
use lightning_net_tokio::SocketDescriptor;
42+
#[cfg(not(feature = "uniffi"))]
43+
use lightning_types::features::ChannelTypeFeatures;
4244

4345
use crate::chain::bitcoind::UtxoSourceClient;
4446
use crate::chain::ChainSource;
@@ -51,6 +53,8 @@ use crate::message_handler::NodeCustomMessageHandler;
5153
use crate::payment::{PaymentDetails, PendingPaymentDetails};
5254
use crate::runtime::RuntimeSpawner;
5355

56+
#[cfg(feature = "uniffi")]
57+
type ChannelTypeFeatures = Arc<crate::ffi::ChannelTypeFeatures>;
5458
#[cfg(not(feature = "uniffi"))]
5559
type InitFeatures = lightning::types::features::InitFeatures;
5660
#[cfg(feature = "uniffi")]
@@ -642,6 +646,11 @@ pub struct ChannelDetails {
642646
///
643647
/// See [`ReserveType`] for details on how reserves differ between anchor and legacy channels.
644648
pub reserve_type: Option<ReserveType>,
649+
/// The negotiated channel type features.
650+
///
651+
/// Will be `None` until channel negotiation has completed and the channel type has been
652+
/// determined.
653+
pub channel_type: Option<ChannelTypeFeatures>,
645654
}
646655

647656
impl ChannelDetails {
@@ -706,6 +715,7 @@ impl ChannelDetails {
706715
.expect("value is set for objects serialized with LDK v0.0.109+"),
707716
channel_shutdown_state: value.channel_shutdown_state,
708717
reserve_type,
718+
channel_type: value.channel_type.map(maybe_wrap),
709719
}
710720
}
711721
}

tests/common/mod.rs

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,12 +1578,15 @@ pub(crate) async fn do_channel_full_cycle<E: ElectrumApi>(
15781578
);
15791579

15801580
if disable_node_b_reserve {
1581-
let node_a_outbound_capacity_msat = node_a.list_channels()[0].outbound_capacity_msat;
1582-
let node_a_reserve_msat =
1583-
node_a.list_channels()[0].unspendable_punishment_reserve.unwrap() * 1000;
1584-
let zero_fee_commitments = node_a.list_channels()[0].feerate_sat_per_1000_weight == 0;
1581+
let node_a_channel = node_a.list_channels().into_iter().next().unwrap();
1582+
let node_a_outbound_capacity_msat = node_a_channel.outbound_capacity_msat;
1583+
let node_a_reserve_msat = node_a_channel.unspendable_punishment_reserve.unwrap() * 1000;
1584+
let zero_fee_commitments = node_a_channel
1585+
.channel_type
1586+
.as_ref()
1587+
.map_or(false, |c| c.requires_anchor_zero_fee_commitments());
15851588
let node_a_anchors_msat = if zero_fee_commitments { 0 } else { 2 * 330 * 1000 };
1586-
let funding_amount_msat = node_a.list_channels()[0].channel_value_sats * 1000;
1589+
let funding_amount_msat = node_a_channel.channel_value_sats * 1000;
15871590
// Node B does not have any reserve, so we only subtract a few items on node A's
15881591
// side to arrive at node B's capacity
15891592
let node_b_capacity_msat = funding_amount_msat

tests/common/scenarios/channel.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ use std::time::Duration;
99

1010
use electrsd::corepc_node::Client as BitcoindClient;
1111
use electrsd::electrum_client::ElectrumApi;
12+
#[cfg(all(eclair_test, zero_fee_commitment_tests))]
13+
use ldk_node::ReserveType;
1214
use ldk_node::{Event, Node};
1315

1416
use super::super::external_node::ExternalNode;
@@ -41,6 +43,21 @@ pub(crate) async fn open_channel_to_external<E: ElectrumApi>(
4143
.map(|ch| ch.channel_id.clone())
4244
.unwrap_or_else(|| panic!("Could not find channel on external node {}", peer.name()));
4345

46+
#[cfg(all(eclair_test, zero_fee_commitment_tests))]
47+
{
48+
let channel = node
49+
.list_channels()
50+
.into_iter()
51+
.find(|channel| channel.user_channel_id == user_channel_id)
52+
.expect("opened channel should be listed");
53+
let channel_type = channel.channel_type.as_ref().expect("channel type should be set");
54+
assert_eq!(channel.counterparty.node_id, ext_node_id);
55+
assert!(channel.counterparty.features.supports_anchor_zero_fee_commitments());
56+
assert!(channel_type.requires_anchor_zero_fee_commitments());
57+
assert_eq!(channel.feerate_sat_per_1000_weight, 0);
58+
assert_eq!(channel.reserve_type, Some(ReserveType::Adaptive));
59+
}
60+
4461
(user_channel_id, ext_channel_id)
4562
}
4663

tests/common/scenarios/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,15 +87,15 @@ pub(crate) async fn wait_for_htlcs_settled(
8787
panic!("HTLCs did not settle on {} channel {} within 15s", peer.name(), ext_channel_id);
8888
}
8989

90-
/// Build a fresh LDK node configured for interop tests. Uses electrum at the
90+
/// Build a fresh LDK node configured for interop tests. Uses esplora at the
9191
/// docker-compose default port and bumps sync timeouts for combo stress.
9292
pub(crate) fn setup_ldk_node() -> Node {
9393
let config = crate::common::random_config();
9494
let mut builder = ldk_node::Builder::from_config(config.node_config);
95-
let mut sync_config = ldk_node::config::ElectrumSyncConfig::default();
95+
let mut sync_config = ldk_node::config::EsploraSyncConfig::default();
9696
sync_config.timeouts_config.onchain_wallet_sync_timeout_secs = 180;
9797
sync_config.timeouts_config.lightning_wallet_sync_timeout_secs = 120;
98-
builder.set_chain_source_electrum("tcp://127.0.0.1:50001".to_string(), Some(sync_config));
98+
builder.set_chain_source_esplora("http://127.0.0.1:3002".to_string(), Some(sync_config));
9999
let node = builder.build(config.node_entropy).unwrap();
100100
node.start().unwrap();
101101
node

0 commit comments

Comments
 (0)