Skip to content

Commit 83ef28f

Browse files
Improve tests with external Lightning implementations
- Add `ExternalLightningNode ` trait to standardize methods for external Lightning nodes in tests. - Create a generic test function that handles the test logic, requiring only an initialized external node. - This simplifies adding tests for other implementations by just initializing the node and passing it to the test. - Update CLN version for testing(new version v25.05). - Remove unnecessary tokio dependency: `tokio = { version = "1.37", features = ["fs"] }`
1 parent 7cef820 commit 83ef28f

File tree

7 files changed

+523
-293
lines changed

7 files changed

+523
-293
lines changed

.github/workflows/cln-integration.yml

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,13 @@ jobs:
99
- name: Checkout repository
1010
uses: actions/checkout@v4
1111

12-
- name: Install dependencies
13-
run: |
14-
sudo apt-get update -y
15-
sudo apt-get install -y socat
12+
- name: Create temporary directory for CLN data
13+
id: create-temp-dir
14+
run: echo "CLN_DATA_DIR=$(mktemp -d)" >> $GITHUB_ENV
1615

1716
- name: Start bitcoind, electrs, and lightningd
1817
run: docker compose -f docker-compose-cln.yml up -d
1918

20-
- name: Forward lightningd RPC socket
21-
run: |
22-
docker exec ldk-node-cln-1 sh -c "socat -d -d TCP-LISTEN:9937,fork,reuseaddr UNIX-CONNECT:/root/.lightning/regtest/lightning-rpc&"
23-
socat -d -d UNIX-LISTEN:/tmp/lightning-rpc,reuseaddr,fork TCP:127.0.0.1:9937&
24-
2519
- name: Run CLN integration tests
26-
run: RUSTFLAGS="--cfg cln_test" cargo test --test integration_tests_cln
20+
run: |
21+
CLN_SOCK=$CLN_DATA_DIR/lightning-rpc RUSTFLAGS="--cfg cln_test" cargo test --test integration_tests_cln

Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,6 @@ clightningrpc = { version = "0.3.0-beta.8", default-features = false }
126126

127127
[target.'cfg(lnd_test)'.dev-dependencies]
128128
lnd_grpc_rust = { version = "2.10.0", default-features = false }
129-
tokio = { version = "1.37", features = ["fs"] }
130129

131130
[build-dependencies]
132131
uniffi = { version = "0.28.3", features = ["build"], optional = true }

docker-compose-cln.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,20 +48,24 @@ services:
4848
- bitcoin-electrs
4949

5050
cln:
51-
image: blockstream/lightningd:v23.08
51+
image: blockstream/lightningd:v25.05
5252
platform: linux/amd64
5353
depends_on:
5454
bitcoin:
5555
condition: service_healthy
5656
command:
5757
[
58+
"--network=regtest",
5859
"--bitcoin-rpcconnect=bitcoin",
5960
"--bitcoin-rpcport=18443",
6061
"--bitcoin-rpcuser=user",
6162
"--bitcoin-rpcpassword=pass",
6263
"--regtest",
63-
"--experimental-anchors",
64+
"--rpc-file=/tmp/lightning-rpc",
65+
"--rpc-file-mode=0666",
6466
]
67+
volumes:
68+
- ${CLN_DATA_DIR}:/tmp
6569
ports:
6670
- "19846:19846"
6771
- "9937:9937"

tests/common/external_node.rs

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
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+
#![cfg(any(cln_test, lnd_test))]
9+
#![allow(dead_code)]
10+
11+
use electrsd::corepc_client::client_sync::Auth;
12+
use lightning::offers::offer;
13+
14+
use ldk_node::bitcoin::secp256k1::PublicKey;
15+
use ldk_node::{Builder, Event, Node, NodeError, UserChannelId};
16+
17+
use lightning::ln::msgs::SocketAddress;
18+
19+
use lightning_invoice::{Bolt11Invoice, Bolt11InvoiceDescription, Description};
20+
21+
use bitcoin::{Amount, OutPoint};
22+
23+
use electrsd::corepc_node::Client as BitcoindClient;
24+
use electrum_client::Client as ElectrumClient;
25+
use electrum_client::ElectrumApi;
26+
27+
use std::str::FromStr;
28+
use std::sync::OnceLock;
29+
30+
use crate::common::{
31+
distribute_funds_unconfirmed, expect_channel_pending_event, expect_channel_ready_event,
32+
expect_event, generate_blocks_and_wait, premine_blocks, random_config, wait_for_tx,
33+
};
34+
35+
pub trait ExternalLightningNode {
36+
fn get_node_info(&mut self) -> (PublicKey, SocketAddress);
37+
38+
fn create_invoice(&mut self, amount_msat: u64, description: Option<String>) -> String;
39+
40+
fn pay_invoice(&mut self, invoice: &str);
41+
42+
fn check_receive_payment(&mut self, invoice: Bolt11Invoice);
43+
44+
fn close_channel(&mut self, channel_id: OutPoint, node_id: PublicKey, force: bool);
45+
}
46+
47+
static BITCOIND_CLIENT: OnceLock<BitcoindClient> = OnceLock::new();
48+
49+
pub(crate) fn init_bitcoind_client() -> &'static BitcoindClient {
50+
BITCOIND_CLIENT.get_or_init(|| {
51+
let bitcoind_client = BitcoindClient::new_with_auth(
52+
"http://127.0.0.1:18443",
53+
Auth::UserPass("user".to_string(), "pass".to_string()),
54+
)
55+
.unwrap();
56+
57+
let electrs_client = ElectrumClient::new("tcp://127.0.0.1:50001").unwrap();
58+
premine_blocks(&bitcoind_client, &electrs_client);
59+
60+
bitcoind_client
61+
})
62+
}
63+
64+
pub(crate) fn setup_test_node(
65+
anchor_channels: bool,
66+
) -> (Node, &'static BitcoindClient, ElectrumClient) {
67+
let bitcoind_client = init_bitcoind_client();
68+
69+
let electrs_client = ElectrumClient::new("tcp://127.0.0.1:50001").unwrap();
70+
71+
let config = random_config(anchor_channels);
72+
let mut builder = Builder::from_config(config.node_config);
73+
builder.set_chain_source_esplora("http://127.0.0.1:3002".to_string(), None);
74+
75+
let node = builder.build().unwrap();
76+
node.start().unwrap();
77+
78+
(node, bitcoind_client, electrs_client)
79+
}
80+
81+
fn add_onchain_funds(bitcoind: &BitcoindClient, electrs: &ElectrumClient, node: &Node) {
82+
let addr = node.onchain_payment().new_address().unwrap();
83+
let amount = Amount::from_sat(5_000_000);
84+
distribute_funds_unconfirmed(bitcoind, electrs, vec![addr], amount);
85+
generate_blocks_and_wait(bitcoind, electrs, 1);
86+
}
87+
88+
fn open_channel<E: ElectrumApi>(
89+
bitcoind: &BitcoindClient, electrs: &E, node: &Node, external_node_id: PublicKey,
90+
external_node_address: SocketAddress, funding_amount_sat: u64, push_msat: Option<u64>,
91+
) -> (UserChannelId, OutPoint) {
92+
node.sync_wallets().unwrap();
93+
94+
// Open the channel
95+
node.open_announced_channel(
96+
external_node_id,
97+
external_node_address,
98+
funding_amount_sat,
99+
push_msat,
100+
None,
101+
)
102+
.unwrap();
103+
104+
let funding_txo = expect_channel_pending_event!(node, external_node_id);
105+
wait_for_tx(electrs, funding_txo.txid);
106+
generate_blocks_and_wait(bitcoind, electrs, 6);
107+
node.sync_wallets().unwrap();
108+
let user_channel_id = expect_channel_ready_event!(node, external_node_id);
109+
110+
(user_channel_id, funding_txo)
111+
}
112+
113+
fn send_payment_to_external_node<E: ExternalLightningNode>(
114+
node: &Node, external_node: &mut E, amount_msat: u64,
115+
) -> Bolt11Invoice {
116+
let invoice_string = external_node.create_invoice(amount_msat, None);
117+
let invoice = Bolt11Invoice::from_str(&invoice_string).unwrap();
118+
node.bolt11_payment()
119+
.send(&invoice, None)
120+
.unwrap_or_else(|e| panic!("Failed to send payment: {:?}", e));
121+
invoice
122+
}
123+
124+
fn check_send_payment_succeeds<E: ExternalLightningNode>(
125+
node: &Node, external_node: &mut E, invoice: Bolt11Invoice,
126+
) {
127+
expect_event!(node, PaymentSuccessful);
128+
external_node.check_receive_payment(invoice)
129+
}
130+
131+
fn receive_payment_from_external_node<E: ExternalLightningNode>(
132+
node: &Node, external_node: &mut E, amount_msat: u64,
133+
) {
134+
let invoice_description = Bolt11InvoiceDescription::Direct(
135+
Description::new("test external node".to_string()).unwrap(),
136+
);
137+
let ldk_invoice =
138+
node.bolt11_payment().receive(amount_msat, &invoice_description, 3600).unwrap();
139+
external_node.pay_invoice(&ldk_invoice.to_string());
140+
141+
expect_event!(node, PaymentReceived);
142+
}
143+
144+
fn close_channel<E: ExternalLightningNode, F: ElectrumApi>(
145+
bitcoind_client: &BitcoindClient, electrs_client: &F, external_node: &mut E, node: &Node,
146+
channel_id: OutPoint, node_id: PublicKey, user_channel_id: &UserChannelId,
147+
external_force_close: Option<bool>,
148+
) {
149+
match external_force_close {
150+
Some(force) => {
151+
if force {
152+
// Other Lightning implementations only force close if the counterparty is not connected;
153+
// they always attempt a cooperative close first before forcing.
154+
node.stop().unwrap();
155+
external_node.close_channel(channel_id, node.node_id(), force);
156+
node.start().unwrap();
157+
} else {
158+
external_node.close_channel(channel_id, node.node_id(), force);
159+
}
160+
generate_blocks_and_wait(bitcoind_client, electrs_client, 1);
161+
},
162+
None => {
163+
node.close_channel(user_channel_id, node_id).unwrap();
164+
},
165+
}
166+
expect_event!(node, ChannelClosed);
167+
}
168+
169+
pub(crate) fn do_ldk_opens_channel_full_cycle_with_external_node<E: ExternalLightningNode>(
170+
external_node: &mut E, external_force_close: Option<bool>,
171+
) {
172+
// Initialize LDK node and clients
173+
let (node, bitcoind_client, electrs_client) = setup_test_node(true);
174+
175+
// setup external node info
176+
let (external_node_id, external_node_address) = external_node.get_node_info();
177+
let external_node_address = external_node_address.clone();
178+
179+
// Open the channel
180+
add_onchain_funds(&bitcoind_client, &electrs_client, &node);
181+
let funding_amount_sat = 2_000_000;
182+
let push_msat = Some(500_000_000);
183+
let (user_channel_id, funding_txo) = open_channel(
184+
&bitcoind_client,
185+
&electrs_client,
186+
&node,
187+
external_node_id,
188+
external_node_address,
189+
funding_amount_sat,
190+
push_msat,
191+
);
192+
193+
// Send a payment to the external node
194+
let invoice_amount_sat = 100_000_000;
195+
let invoice = send_payment_to_external_node(&node, external_node, invoice_amount_sat);
196+
check_send_payment_succeeds(&node, external_node, invoice);
197+
198+
// Send a payment to LDK
199+
let amount_msat = 9_000_000;
200+
receive_payment_from_external_node(&node, external_node, amount_msat);
201+
202+
// Test overpayment: Send more than the invoice amount to the external node
203+
let overpaid_invoice_amount_msat = 100_000_000;
204+
let overpaid_amount_msat = overpaid_invoice_amount_msat + 10_000; // Excesso de 10k msat
205+
let overpaid_invoice = external_node.create_invoice(overpaid_invoice_amount_msat, None);
206+
let overpaid_bolt11_invoice = Bolt11Invoice::from_str(&overpaid_invoice).unwrap();
207+
let _overpaid_payment_id = node
208+
.bolt11_payment()
209+
.send_using_amount(&overpaid_bolt11_invoice, overpaid_amount_msat, None)
210+
.unwrap();
211+
expect_event!(node, PaymentSuccessful);
212+
external_node.check_receive_payment(overpaid_bolt11_invoice); // Assumindo que o externo aceita overpayment
213+
214+
// Test underpayment: Attempt to send less than invoice amount (should fail)
215+
let underpaid_invoice_amount_msat = 100_000_000;
216+
let underpaid_amount_msat = underpaid_invoice_amount_msat - 1;
217+
let underpaid_invoice = external_node.create_invoice(underpaid_invoice_amount_msat, None);
218+
let underpaid_bolt11_invoice = Bolt11Invoice::from_str(&underpaid_invoice).unwrap();
219+
assert_eq!(
220+
Err(NodeError::InvalidAmount),
221+
node.bolt11_payment().send_using_amount(
222+
&underpaid_bolt11_invoice,
223+
underpaid_amount_msat,
224+
None
225+
)
226+
);
227+
228+
// Test variable amount invoice: External node creates zero-amount invoice, LDK pays a determined amount
229+
let variable_invoice = external_node.create_invoice(0, None);
230+
let variable_bolt11_invoice = Bolt11Invoice::from_str(&variable_invoice).unwrap();
231+
let determined_amount_msat = 50_000_000;
232+
let _variable_payment_id = node
233+
.bolt11_payment()
234+
.send_using_amount(&variable_bolt11_invoice, determined_amount_msat, None)
235+
.unwrap();
236+
expect_event!(node, PaymentSuccessful);
237+
238+
// Close the channel
239+
close_channel(
240+
&bitcoind_client,
241+
&electrs_client,
242+
external_node,
243+
&node,
244+
funding_txo,
245+
external_node_id,
246+
&user_channel_id,
247+
external_force_close,
248+
);
249+
250+
node.stop().unwrap();
251+
}

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)