Skip to content

Commit 264c809

Browse files
committed
Add ExternalNode trait and supporting types
- Add ExternalNode async trait with channel, payment, and node management methods - Add wait_for_block_sync() default no-op for modular chain sync handling - Add ExternalChannel, TestFailure types in external_node.rs
1 parent 9e0a812 commit 264c809

2 files changed

Lines changed: 142 additions & 0 deletions

File tree

tests/common/external_node.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// This file is Copyright its original authors, visible in version control history.
2+
//
3+
// This file is licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
6+
// accordance with one or both of these licenses.
7+
8+
use std::fmt;
9+
use std::time::Duration;
10+
11+
use async_trait::async_trait;
12+
use ldk_node::bitcoin::secp256k1::PublicKey;
13+
use ldk_node::lightning::ln::msgs::SocketAddress;
14+
15+
/// Represents a channel opened to or from an external Lightning node.
16+
#[derive(Debug, Clone)]
17+
pub(crate) struct ExternalChannel {
18+
/// Implementation-specific channel identifier.
19+
/// LND uses `txid:vout` (channel point), CLN uses a hex channel ID,
20+
/// and Eclair uses its own hex format.
21+
pub channel_id: String,
22+
pub peer_id: PublicKey,
23+
pub capacity_sat: u64,
24+
pub local_balance_msat: u64,
25+
pub remote_balance_msat: u64,
26+
pub funding_txid: Option<String>,
27+
pub is_active: bool,
28+
}
29+
30+
/// Errors that can occur during interop test operations.
31+
#[derive(Debug)]
32+
pub(crate) enum TestFailure {
33+
Timeout { operation: String, duration: Duration },
34+
ExternalNodeError { node: String, detail: String },
35+
}
36+
37+
impl fmt::Display for TestFailure {
38+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
39+
match self {
40+
TestFailure::Timeout { operation, duration } => {
41+
write!(f, "Timeout waiting for '{}' after {:?}", operation, duration)
42+
},
43+
TestFailure::ExternalNodeError { node, detail } => {
44+
write!(f, "External node '{}' error: {}", node, detail)
45+
},
46+
}
47+
}
48+
}
49+
50+
impl std::error::Error for TestFailure {}
51+
52+
/// Abstraction over an external Lightning node used in interop tests.
53+
#[async_trait]
54+
pub(crate) trait ExternalNode: Send + Sync {
55+
/// Human-readable name for this node (e.g. "eclair", "lnd", "cln").
56+
fn name(&self) -> &str;
57+
58+
/// Returns the node's public key.
59+
async fn get_node_id(&self) -> Result<PublicKey, TestFailure>;
60+
61+
/// Returns an address on which this node is listening.
62+
async fn get_listening_address(&self) -> Result<SocketAddress, TestFailure>;
63+
64+
/// Connect to a peer by public key and address.
65+
async fn connect_peer(
66+
&self, peer_id: PublicKey, addr: SocketAddress,
67+
) -> Result<(), TestFailure>;
68+
69+
/// Disconnect from a peer by public key.
70+
async fn disconnect_peer(&self, peer_id: PublicKey) -> Result<(), TestFailure>;
71+
72+
/// Open a channel to a peer.
73+
///
74+
/// Returns a channel id string that the implementation may use
75+
/// to correlate with subsequent close/query calls.
76+
async fn open_channel(
77+
&self, peer_id: PublicKey, addr: SocketAddress, capacity_sat: u64, push_msat: Option<u64>,
78+
) -> Result<String, TestFailure>;
79+
80+
/// Cooperatively close a channel by its implementation-defined channel id.
81+
async fn close_channel(&self, channel_id: &str) -> Result<(), TestFailure>;
82+
83+
/// Force-close a channel by its implementation-defined channel id.
84+
async fn force_close_channel(&self, channel_id: &str) -> Result<(), TestFailure>;
85+
86+
/// Create a BOLT11 invoice for the given amount.
87+
async fn create_invoice(
88+
&self, amount_msat: u64, description: &str,
89+
) -> Result<String, TestFailure>;
90+
91+
/// Pay a BOLT11 invoice; returns a payment identifier on success
92+
/// (preimage for LND/CLN, payment UUID for Eclair).
93+
async fn pay_invoice(&self, invoice: &str) -> Result<String, TestFailure>;
94+
95+
/// Send a keysend payment to a peer.
96+
async fn send_keysend(
97+
&self, peer_id: PublicKey, amount_msat: u64,
98+
) -> Result<String, TestFailure>;
99+
100+
/// Get an on-chain address that can be used to fund this node.
101+
async fn get_funding_address(&self) -> Result<String, TestFailure>;
102+
103+
/// Returns the current blockchain height as seen by this node.
104+
async fn get_block_height(&self) -> Result<u64, TestFailure>;
105+
106+
/// List all channels known to this node.
107+
async fn list_channels(&self) -> Result<Vec<ExternalChannel>, TestFailure>;
108+
109+
/// Wait until this node has synced to at least `min_height`.
110+
///
111+
/// The default is a no-op — most implementations (LND, CLN) sync blocks
112+
/// fast enough that explicit waiting is unnecessary. Override this for
113+
/// implementations like Eclair that may lag behind the chain tip.
114+
async fn wait_for_block_sync(&self, _min_height: u64) -> Result<(), TestFailure> {
115+
Ok(())
116+
}
117+
118+
/// Splice additional funds into an existing channel.
119+
///
120+
/// Not all implementations support splicing. The default returns an error.
121+
async fn splice_in(&self, _channel_id: &str, _amount_sat: u64) -> Result<(), TestFailure> {
122+
Err(TestFailure::ExternalNodeError {
123+
node: self.name().to_string(),
124+
detail: "splice_in not supported".to_string(),
125+
})
126+
}
127+
128+
/// Splice funds out of an existing channel.
129+
///
130+
/// If `address` is provided, funds are sent to that on-chain address;
131+
/// otherwise the implementation decides the destination (e.g. own wallet).
132+
/// Not all implementations support splicing. The default returns an error.
133+
async fn splice_out(
134+
&self, _channel_id: &str, _amount_sat: u64, _address: Option<&str>,
135+
) -> Result<(), TestFailure> {
136+
Err(TestFailure::ExternalNodeError {
137+
node: self.name().to_string(),
138+
detail: "splice_out not supported".to_string(),
139+
})
140+
}
141+
}

tests/common/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#![cfg(any(test, cln_test, lnd_test, vss_test))]
99
#![allow(dead_code)]
1010

11+
pub(crate) mod external_node;
1112
pub(crate) mod logging;
1213

1314
use std::collections::{HashMap, HashSet};

0 commit comments

Comments
 (0)