Skip to content

Commit c7e001b

Browse files
authored
Merge pull request #841 from enigbe/2026-03-channel-reserve-type-exposure
Expose ChannelCounterparty and ReserveType in ChannelDetails
2 parents 4b1326e + fee7a73 commit c7e001b

6 files changed

Lines changed: 470 additions & 62 deletions

File tree

bindings/python/src/ldk_node/test_ldk_node.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,29 @@ def expect_event(node, expected_event_type):
121121
return event
122122

123123

124+
def assert_feature_helpers_return_bool(test_case, features):
125+
feature_methods = [
126+
method_name for method_name in dir(features)
127+
if method_name.startswith("supports_") or method_name.startswith("requires_")
128+
]
129+
130+
test_case.assertGreater(len(feature_methods), 0)
131+
for method_name in feature_methods:
132+
with test_case.subTest(method_name=method_name):
133+
test_case.assertIsInstance(getattr(features, method_name)(), bool)
134+
135+
136+
def node_features_exposed(test_case, node_features):
137+
test_case.assertIsInstance(node_features, NodeFeatures)
138+
assert_feature_helpers_return_bool(test_case, node_features)
139+
140+
141+
def init_features_exposed(test_case, init_features):
142+
test_case.assertIsInstance(init_features, InitFeatures)
143+
assert_feature_helpers_return_bool(test_case, init_features)
144+
test_case.assertIsInstance(init_features.initial_routing_sync(), bool)
145+
146+
124147

125148
class TestLdkNode(unittest.TestCase):
126149
def setUp(self):
@@ -153,6 +176,10 @@ def test_channel_full_cycle(self):
153176
node_id_2 = node_2.node_id()
154177
print("Node ID 2:", node_id_2)
155178

179+
# Check node-announcement features exposed through NodeStatus.
180+
for node in [node_1, node_2]:
181+
node_features_exposed(self, node.status().node_features)
182+
156183
address_1 = node_1.onchain_payment().new_address()
157184
txid_1 = send_to_address(address_1, 100000)
158185
address_2 = node_2.onchain_payment().new_address()
@@ -200,6 +227,10 @@ def test_channel_full_cycle(self):
200227

201228
channel_ready_event_2 = expect_event(node_2, Event.CHANNEL_READY)
202229

230+
# Check negotiated init features exposed through ChannelDetails.
231+
for channel in [node_1.list_channels()[0], node_2.list_channels()[0]]:
232+
init_features_exposed(self, channel.counterparty.features)
233+
203234
description = Bolt11InvoiceDescription.DIRECT("asdf")
204235
invoice = node_2.bolt11_payment().receive(2500000, description, 9217)
205236
node_1.bolt11_payment().send(invoice, None)

src/ffi/types.rs

Lines changed: 295 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub use bitcoin::{Address, BlockHash, Network, OutPoint, ScriptBuf, Txid};
2525
pub use lightning::chain::channelmonitor::BalanceSource;
2626
use lightning::events::PaidBolt12Invoice as LdkPaidBolt12Invoice;
2727
pub use lightning::events::{ClosureReason, PaymentFailureReason};
28-
use lightning::ln::channel_state::ChannelShutdownState;
28+
use lightning::ln::channel_state::{ChannelShutdownState, CounterpartyForwardingInfo};
2929
use lightning::ln::channelmanager::PaymentId;
3030
use lightning::ln::msgs::DecodeError;
3131
pub use lightning::ln::types::ChannelId;
@@ -44,7 +44,7 @@ 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::NodeFeatures as LdkNodeFeatures;
47+
use lightning_types::features::{InitFeatures as LdkInitFeatures, NodeFeatures as LdkNodeFeatures};
4848
pub use lightning_types::payment::{PaymentHash, PaymentPreimage, PaymentSecret};
4949
pub use lightning_types::string::UntrustedString;
5050
use vss_client::headers::{
@@ -1526,6 +1526,7 @@ pub struct NodeFeatures {
15261526
pub(crate) inner: LdkNodeFeatures,
15271527
}
15281528

1529+
#[uniffi::export]
15291530
impl NodeFeatures {
15301531
/// Constructs node features from big-endian BOLT 9 encoded bytes.
15311532
#[uniffi::constructor]
@@ -1815,6 +1816,298 @@ impl From<LdkNodeFeatures> for NodeFeatures {
18151816
}
18161817
}
18171818

1819+
#[derive(Debug, Clone, PartialEq, Eq, uniffi::Object)]
1820+
#[uniffi::export(Debug, Eq)]
1821+
pub struct InitFeatures {
1822+
pub(crate) inner: LdkInitFeatures,
1823+
}
1824+
1825+
#[uniffi::export]
1826+
impl InitFeatures {
1827+
/// Constructs init features from big-endian BOLT 9 encoded bytes.
1828+
#[uniffi::constructor]
1829+
pub fn from_bytes(bytes: &[u8]) -> Self {
1830+
Self { inner: LdkInitFeatures::from_be_bytes(bytes.to_vec()).into() }
1831+
}
1832+
1833+
/// Returns the BOLT 9 big-endian encoded representation of these features.
1834+
pub fn to_bytes(&self) -> Vec<u8> {
1835+
self.inner.encode()
1836+
}
1837+
1838+
/// Whether the peer's `init` message advertises support for `option_static_remotekey`.
1839+
pub fn supports_static_remote_key(&self) -> bool {
1840+
self.inner.supports_static_remote_key()
1841+
}
1842+
1843+
/// Whether the peer's `init` message requires `option_static_remotekey`.
1844+
pub fn requires_static_remote_key(&self) -> bool {
1845+
self.inner.requires_static_remote_key()
1846+
}
1847+
1848+
/// Whether the peer's `init` message advertises support for `option_anchors_zero_fee_htlc_tx`.
1849+
pub fn supports_anchors_zero_fee_htlc_tx(&self) -> bool {
1850+
self.inner.supports_anchors_zero_fee_htlc_tx()
1851+
}
1852+
1853+
/// Whether the peer's `init` message requires `option_anchors_zero_fee_htlc_tx`.
1854+
pub fn requires_anchors_zero_fee_htlc_tx(&self) -> bool {
1855+
self.inner.requires_anchors_zero_fee_htlc_tx()
1856+
}
1857+
1858+
/// Whether the peer's `init` message advertises support for `option_anchors_nonzero_fee_htlc_tx`.
1859+
pub fn supports_anchors_nonzero_fee_htlc_tx(&self) -> bool {
1860+
self.inner.supports_anchors_nonzero_fee_htlc_tx()
1861+
}
1862+
1863+
/// Whether the peer's `init` message requires `option_anchors_nonzero_fee_htlc_tx`.
1864+
pub fn requires_anchors_nonzero_fee_htlc_tx(&self) -> bool {
1865+
self.inner.requires_anchors_nonzero_fee_htlc_tx()
1866+
}
1867+
1868+
/// Whether the peer's `init` message advertises support for `option_support_large_channel`.
1869+
pub fn supports_wumbo(&self) -> bool {
1870+
self.inner.supports_wumbo()
1871+
}
1872+
1873+
/// Whether the peer's `init` message requires `option_support_large_channel`.
1874+
pub fn requires_wumbo(&self) -> bool {
1875+
self.inner.requires_wumbo()
1876+
}
1877+
1878+
/// Whether the peer's `init` message advertises support for `option_route_blinding`.
1879+
pub fn supports_route_blinding(&self) -> bool {
1880+
self.inner.supports_route_blinding()
1881+
}
1882+
1883+
/// Whether the peer's `init` message requires `option_route_blinding`.
1884+
pub fn requires_route_blinding(&self) -> bool {
1885+
self.inner.requires_route_blinding()
1886+
}
1887+
1888+
/// Whether the peer's `init` message advertises support for `option_onion_messages`.
1889+
pub fn supports_onion_messages(&self) -> bool {
1890+
self.inner.supports_onion_messages()
1891+
}
1892+
1893+
/// Whether the peer's `init` message requires `option_onion_messages`.
1894+
pub fn requires_onion_messages(&self) -> bool {
1895+
self.inner.requires_onion_messages()
1896+
}
1897+
1898+
/// Whether the peer's `init` message advertises support for `option_scid_alias`.
1899+
pub fn supports_scid_privacy(&self) -> bool {
1900+
self.inner.supports_scid_privacy()
1901+
}
1902+
1903+
/// Whether the peer's `init` message requires `option_scid_alias`.
1904+
pub fn requires_scid_privacy(&self) -> bool {
1905+
self.inner.requires_scid_privacy()
1906+
}
1907+
1908+
/// Whether the peer's `init` message advertises support for `option_zeroconf`.
1909+
pub fn supports_zero_conf(&self) -> bool {
1910+
self.inner.supports_zero_conf()
1911+
}
1912+
1913+
/// Whether the peer's `init` message requires `option_zeroconf`.
1914+
pub fn requires_zero_conf(&self) -> bool {
1915+
self.inner.requires_zero_conf()
1916+
}
1917+
1918+
/// Whether the peer's `init` message advertises support for `option_dual_fund`.
1919+
pub fn supports_dual_fund(&self) -> bool {
1920+
self.inner.supports_dual_fund()
1921+
}
1922+
1923+
/// Whether the peer's `init` message requires `option_dual_fund`.
1924+
pub fn requires_dual_fund(&self) -> bool {
1925+
self.inner.requires_dual_fund()
1926+
}
1927+
1928+
/// Whether the peer's `init` message advertises support for `option_quiesce`.
1929+
pub fn supports_quiescence(&self) -> bool {
1930+
self.inner.supports_quiescence()
1931+
}
1932+
1933+
/// Whether the peer's `init` message requires `option_quiesce`.
1934+
pub fn requires_quiescence(&self) -> bool {
1935+
self.inner.requires_quiescence()
1936+
}
1937+
1938+
/// Whether the peer's `init` message advertises support for `option_data_loss_protect`.
1939+
pub fn supports_data_loss_protect(&self) -> bool {
1940+
self.inner.supports_data_loss_protect()
1941+
}
1942+
1943+
/// Whether the peer's `init` message requires `option_data_loss_protect`.
1944+
pub fn requires_data_loss_protect(&self) -> bool {
1945+
self.inner.requires_data_loss_protect()
1946+
}
1947+
1948+
/// Whether the peer's `init` message advertises support for `option_upfront_shutdown_script`.
1949+
pub fn supports_upfront_shutdown_script(&self) -> bool {
1950+
self.inner.supports_upfront_shutdown_script()
1951+
}
1952+
1953+
/// Whether the peer's `init` message requires `option_upfront_shutdown_script`.
1954+
pub fn requires_upfront_shutdown_script(&self) -> bool {
1955+
self.inner.requires_upfront_shutdown_script()
1956+
}
1957+
1958+
/// Whether the peer's `init` message advertises support for `gossip_queries`.
1959+
pub fn supports_gossip_queries(&self) -> bool {
1960+
self.inner.supports_gossip_queries()
1961+
}
1962+
1963+
/// Whether the peer's `init` message requires `gossip_queries`.
1964+
pub fn requires_gossip_queries(&self) -> bool {
1965+
self.inner.requires_gossip_queries()
1966+
}
1967+
1968+
/// Whether the peer's `init` message advertises support for `var_onion_optin`.
1969+
pub fn supports_variable_length_onion(&self) -> bool {
1970+
self.inner.supports_variable_length_onion()
1971+
}
1972+
1973+
/// Whether the peer's `init` message requires `var_onion_optin`.
1974+
pub fn requires_variable_length_onion(&self) -> bool {
1975+
self.inner.requires_variable_length_onion()
1976+
}
1977+
1978+
/// Whether the peer's `init` message advertises support for `payment_secret`.
1979+
pub fn supports_payment_secret(&self) -> bool {
1980+
self.inner.supports_payment_secret()
1981+
}
1982+
1983+
/// Whether the peer's `init` message requires `payment_secret`.
1984+
pub fn requires_payment_secret(&self) -> bool {
1985+
self.inner.requires_payment_secret()
1986+
}
1987+
1988+
/// Whether the peer's `init` message advertises support for `basic_mpp`.
1989+
pub fn supports_basic_mpp(&self) -> bool {
1990+
self.inner.supports_basic_mpp()
1991+
}
1992+
1993+
/// Whether the peer's `init` message requires `basic_mpp`.
1994+
pub fn requires_basic_mpp(&self) -> bool {
1995+
self.inner.requires_basic_mpp()
1996+
}
1997+
1998+
/// Whether the peer's `init` message advertises support for `opt_shutdown_anysegwit`.
1999+
pub fn supports_shutdown_anysegwit(&self) -> bool {
2000+
self.inner.supports_shutdown_anysegwit()
2001+
}
2002+
2003+
/// Whether the peer's `init` message requires `opt_shutdown_anysegwit`.
2004+
pub fn requires_shutdown_anysegwit(&self) -> bool {
2005+
self.inner.requires_shutdown_anysegwit()
2006+
}
2007+
2008+
/// Whether the peer's `init` message advertises support for `option_channel_type`.
2009+
pub fn supports_channel_type(&self) -> bool {
2010+
self.inner.supports_channel_type()
2011+
}
2012+
2013+
/// Whether the peer's `init` message requires `option_channel_type`.
2014+
pub fn requires_channel_type(&self) -> bool {
2015+
self.inner.requires_channel_type()
2016+
}
2017+
2018+
/// Whether the peer's `init` message advertises support for `option_trampoline`.
2019+
pub fn supports_trampoline_routing(&self) -> bool {
2020+
self.inner.supports_trampoline_routing()
2021+
}
2022+
2023+
/// Whether the peer's `init` message requires `option_trampoline`.
2024+
pub fn requires_trampoline_routing(&self) -> bool {
2025+
self.inner.requires_trampoline_routing()
2026+
}
2027+
2028+
/// Whether the peer's `init` message advertises support for `option_simple_close`.
2029+
pub fn supports_simple_close(&self) -> bool {
2030+
self.inner.supports_simple_close()
2031+
}
2032+
2033+
/// Whether the peer's `init` message requires `option_simple_close`.
2034+
pub fn requires_simple_close(&self) -> bool {
2035+
self.inner.requires_simple_close()
2036+
}
2037+
2038+
/// Whether the peer's `init` message advertises support for `option_splice`.
2039+
pub fn supports_splicing(&self) -> bool {
2040+
self.inner.supports_splicing()
2041+
}
2042+
2043+
/// Whether the peer's `init` message requires `option_splice`.
2044+
pub fn requires_splicing(&self) -> bool {
2045+
self.inner.requires_splicing()
2046+
}
2047+
2048+
/// Whether the peer's `init` message advertises support for `option_provide_storage`.
2049+
pub fn supports_provide_storage(&self) -> bool {
2050+
self.inner.supports_provide_storage()
2051+
}
2052+
2053+
/// Whether the peer's `init` message requires `option_provide_storage`.
2054+
pub fn requires_provide_storage(&self) -> bool {
2055+
self.inner.requires_provide_storage()
2056+
}
2057+
2058+
/// Whether the peer's `init` message set `initial_routing_sync`.
2059+
pub fn initial_routing_sync(&self) -> bool {
2060+
self.inner.initial_routing_sync()
2061+
}
2062+
2063+
/// Whether the peer's `init` message advertises support for `option_taproot`.
2064+
pub fn supports_taproot(&self) -> bool {
2065+
self.inner.supports_taproot()
2066+
}
2067+
2068+
/// Whether the peer's `init` message requires `option_taproot`.
2069+
pub fn requires_taproot(&self) -> bool {
2070+
self.inner.requires_taproot()
2071+
}
2072+
2073+
/// Whether the peer's `init` message advertises support for `option_zero_fee_commitments`.
2074+
pub fn supports_anchor_zero_fee_commitments(&self) -> bool {
2075+
self.inner.supports_anchor_zero_fee_commitments()
2076+
}
2077+
2078+
/// Whether the peer's `init` message requires `option_zero_fee_commitments`.
2079+
pub fn requires_anchor_zero_fee_commitments(&self) -> bool {
2080+
self.inner.requires_anchor_zero_fee_commitments()
2081+
}
2082+
2083+
/// Whether the peer's `init` message advertises support for HTLC hold.
2084+
pub fn supports_htlc_hold(&self) -> bool {
2085+
self.inner.supports_htlc_hold()
2086+
}
2087+
2088+
/// Whether the peer's `init` message requires HTLC hold.
2089+
pub fn requires_htlc_hold(&self) -> bool {
2090+
self.inner.requires_htlc_hold()
2091+
}
2092+
}
2093+
2094+
impl From<LdkInitFeatures> for InitFeatures {
2095+
fn from(ldk_init: LdkInitFeatures) -> Self {
2096+
Self { inner: ldk_init }
2097+
}
2098+
}
2099+
/// Information needed for constructing an invoice route hint for this channel.
2100+
#[uniffi::remote(Record)]
2101+
pub struct CounterpartyForwardingInfo {
2102+
/// Base routing fee in millisatoshis.
2103+
pub fee_base_msat: u32,
2104+
/// Amount in millionths of a satoshi the channel will charge per transferred satoshi.
2105+
pub fee_proportional_millionths: u32,
2106+
/// The minimum difference in cltv_expiry between an ingoing HTLC and its outgoing counterpart,
2107+
/// such that the outgoing HTLC is forwardable to this counterparty.
2108+
pub cltv_expiry_delta: u16,
2109+
}
2110+
18182111
#[cfg(test)]
18192112
mod tests {
18202113
use std::num::NonZeroU64;

0 commit comments

Comments
 (0)