Skip to content

Commit cdccf68

Browse files
feat: implement LND integration tests with lnd_grpc_rust
Adds tests to: - Open a payment channel with LND. - Request and pay an invoice, verifying receipt. - Generate an invoice for LND to pay, verifying receipt. Uses lnd_grpc_rust for gRPC communication. Closes lightningdevkit#505
1 parent 7cf925f commit cdccf68

File tree

3 files changed

+169
-1
lines changed

3 files changed

+169
-1
lines changed

Cargo.toml

100644100755
+5
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ electrsd = { version = "0.29.0", features = ["legacy"] }
106106
[target.'cfg(cln_test)'.dev-dependencies]
107107
clightningrpc = { version = "0.3.0-beta.8", default-features = false }
108108

109+
[target.'cfg(lnd_test)'.dev-dependencies]
110+
lnd_grpc_rust = { version = "2.10.0", default-features = false }
111+
tokio = { version = "1.37", features = ["fs"] }
112+
109113
[build-dependencies]
110114
uniffi = { version = "0.27.3", features = ["build"], optional = true }
111115

@@ -123,4 +127,5 @@ check-cfg = [
123127
"cfg(ldk_bench)",
124128
"cfg(tokio_unstable)",
125129
"cfg(cln_test)",
130+
"cfg(lnd_test)",
126131
]

tests/common/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// http://opensource.org/licenses/MIT>, at your option. You may not use this file except in
66
// accordance with one or both of these licenses.
77

8-
#![cfg(any(test, cln_test, vss_test))]
8+
#![cfg(any(test, cln_test, lnd_test, vss_test))]
99
#![allow(dead_code)]
1010

1111
pub(crate) mod logging;

tests/integration_tests_lnd.rs

+163
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
#![cfg(lnd_test)]
2+
3+
mod common;
4+
5+
use ldk_node::bitcoin::secp256k1::PublicKey;
6+
use ldk_node::bitcoin::Amount;
7+
use ldk_node::lightning::ln::msgs::SocketAddress;
8+
use ldk_node::{Builder, Event};
9+
10+
use lnd_grpc_rust::lnrpc::{
11+
invoice::InvoiceState::Settled, GetInfoRequest, GetInfoResponse, Invoice, ListInvoiceRequest,
12+
SendRequest, SendResponse,
13+
};
14+
use lnd_grpc_rust::{connect, LndClient};
15+
16+
use bitcoincore_rpc::Auth;
17+
use bitcoincore_rpc::Client as BitcoindClient;
18+
19+
use electrum_client::Client as ElectrumClient;
20+
use lightning_invoice::{Bolt11InvoiceDescription, Description};
21+
22+
use rand::distributions::Alphanumeric;
23+
use rand::{thread_rng, Rng};
24+
25+
use bitcoin::hex::DisplayHex;
26+
27+
use std::default::Default;
28+
use std::error::Error;
29+
use std::str::FromStr;
30+
use std::time::Duration;
31+
use tokio::fs;
32+
33+
#[tokio::test(flavor = "multi_thread", worker_threads = 1)]
34+
async fn test_lnd() {
35+
// Setup bitcoind / electrs clients
36+
let bitcoind_client = BitcoindClient::new(
37+
"127.0.0.1:18443",
38+
Auth::UserPass("user".to_string(), "pass".to_string()),
39+
)
40+
.unwrap();
41+
let electrs_client = ElectrumClient::new("tcp://127.0.0.1:50001").unwrap();
42+
43+
// Give electrs a kick.
44+
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 1);
45+
46+
// Setup LDK Node
47+
let config = common::random_config(true);
48+
let mut builder = Builder::from_config(config.node_config);
49+
builder.set_chain_source_esplora("http://127.0.0.1:3002".to_string(), None);
50+
51+
let node = builder.build().unwrap();
52+
node.start().unwrap();
53+
54+
// Premine some funds and distribute
55+
let address = node.onchain_payment().new_address().unwrap();
56+
let premine_amount = Amount::from_sat(5_000_000);
57+
common::premine_and_distribute_funds(
58+
&bitcoind_client,
59+
&electrs_client,
60+
vec![address],
61+
premine_amount,
62+
);
63+
64+
// Setup LND
65+
let endpoint = "127.0.0.1:8081";
66+
let cert_path = std::env::var("LND_CERT_PATH").expect("LND_CERT_PATH not set");
67+
let macaroon_path = std::env::var("LND_MACAROON_PATH").expect("LND_MACAROON_PATH not set");
68+
let mut lnd = TestLndClient::new(cert_path, macaroon_path, endpoint.to_string()).await.unwrap();
69+
70+
let lnd_node_info = lnd.get_node_info().await.unwrap();
71+
let lnd_node_id = PublicKey::from_str(&lnd_node_info.identity_pubkey).unwrap();
72+
let lnd_address: SocketAddress = "127.0.0.1:9735".parse().unwrap();
73+
74+
node.sync_wallets().unwrap();
75+
76+
// Open the channel
77+
let funding_amount_sat = 1_000_000;
78+
79+
node.open_channel(lnd_node_id, lnd_address, funding_amount_sat, Some(500_000_000), None)
80+
.unwrap();
81+
82+
let funding_txo = common::expect_channel_pending_event!(node, lnd_node_id);
83+
common::wait_for_tx(&electrs_client, funding_txo.txid);
84+
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 6);
85+
node.sync_wallets().unwrap();
86+
let user_channel_id = common::expect_channel_ready_event!(node, lnd_node_id);
87+
88+
// Send a payment to LND
89+
let lnd_invoice = lnd.create_invoice(100_000_000).await.unwrap();
90+
let parsed_invoice = lightning_invoice::Bolt11Invoice::from_str(&lnd_invoice).unwrap();
91+
92+
node.bolt11_payment().send(&parsed_invoice, None).unwrap();
93+
common::expect_event!(node, PaymentSuccessful);
94+
let lnd_listed_invoices = lnd.list_invoices().await.unwrap();
95+
assert_eq!(lnd_listed_invoices.len(), 1);
96+
assert_eq!(lnd_listed_invoices.first().unwrap().state, Settled as i32);
97+
98+
// Sleep to process payment in LND
99+
tokio::time::sleep(Duration::from_secs(1)).await;
100+
101+
// Send a payment to LDK
102+
let mut rng = thread_rng();
103+
let rand_label: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
104+
let invoice_description =
105+
Bolt11InvoiceDescription::Direct(Description::new(rand_label).unwrap());
106+
let ldk_invoice = node.bolt11_payment().receive(9_000_000, &invoice_description, 3600).unwrap();
107+
lnd.pay_invoice(&ldk_invoice.to_string()).await.unwrap();
108+
common::expect_event!(node, PaymentReceived);
109+
110+
node.close_channel(&user_channel_id, lnd_node_id).unwrap();
111+
common::expect_event!(node, ChannelClosed);
112+
node.stop().unwrap();
113+
}
114+
115+
struct TestLndClient {
116+
client: LndClient,
117+
}
118+
119+
impl TestLndClient {
120+
async fn new(
121+
cert_path: String, macaroon_path: String, socket: String,
122+
) -> Result<Self, Box<dyn Error>> {
123+
// Read the contents of the file into a vector of bytes
124+
let cert_bytes = fs::read(cert_path).await.expect("FailedToReadTlsCertFile");
125+
let mac_bytes = fs::read(macaroon_path).await.expect("FailedToReadMacaroonFile");
126+
127+
// Convert the bytes to a hex string
128+
let cert = cert_bytes.as_hex().to_string();
129+
let macaroon = mac_bytes.as_hex().to_string();
130+
131+
let client = connect(cert, macaroon, socket).await?;
132+
Ok(TestLndClient { client })
133+
}
134+
135+
async fn get_node_info(&mut self) -> Result<GetInfoResponse, Box<dyn Error>> {
136+
let response = self.client.lightning().get_info(GetInfoRequest {}).await?.into_inner();
137+
Ok(response)
138+
}
139+
140+
async fn create_invoice(&mut self, amount_msat: u64) -> Result<String, Box<dyn Error>> {
141+
let invoice = Invoice { value_msat: amount_msat as i64, ..Default::default() };
142+
let response = self.client.lightning().add_invoice(invoice).await?.into_inner();
143+
Ok(response.payment_request)
144+
}
145+
146+
async fn list_invoices(&mut self) -> Result<Vec<Invoice>, Box<dyn Error>> {
147+
let response = self
148+
.client
149+
.lightning()
150+
.list_invoices(ListInvoiceRequest { ..Default::default() })
151+
.await?
152+
.into_inner();
153+
154+
Ok(response.invoices)
155+
}
156+
157+
async fn pay_invoice(&mut self, invoice_str: &str) -> Result<SendResponse, Box<dyn Error>> {
158+
let send_req =
159+
SendRequest { payment_request: invoice_str.to_string(), ..Default::default() };
160+
let response = self.client.lightning().send_payment_sync(send_req).await?.into_inner();
161+
Ok(response)
162+
}
163+
}

0 commit comments

Comments
 (0)