Skip to content

Commit c0e4b0b

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 #505
1 parent 67bb7f7 commit c0e4b0b

File tree

3 files changed

+186
-1
lines changed

3 files changed

+186
-1
lines changed

Cargo.toml

100644100755
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ 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 = "2.10.0"
111+
109112
[build-dependencies]
110113
uniffi = { version = "0.27.3", features = ["build"], optional = true }
111114

@@ -123,4 +126,5 @@ check-cfg = [
123126
"cfg(ldk_bench)",
124127
"cfg(tokio_unstable)",
125128
"cfg(cln_test)",
129+
"cfg(lnd_test)",
126130
]

tests/common/mod.rs

Lines changed: 1 addition & 1 deletion
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

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
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 std::{fs, thread};
32+
33+
#[test]
34+
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 = "./data_lnd/tls.cert".to_string();
67+
let macaroon_path = "./data_lnd/data/chain/bitcoin/regtest/admin.macaroon".to_string();
68+
let mut lnd = ClientLnd::new(cert_path, macaroon_path, endpoint.to_string()).unwrap();
69+
70+
let info = lnd.get_node_info().unwrap();
71+
let lnd_node_pubkey = info.identity_pubkey;
72+
let lnd_node_id = PublicKey::from_str(&lnd_node_pubkey).unwrap();
73+
let lnd_address: SocketAddress = "127.0.0.1:9735".parse().unwrap();
74+
75+
node.sync_wallets().unwrap();
76+
77+
// Open the channel
78+
let funding_amount_sat = 1_000_000;
79+
80+
node.open_channel(lnd_node_id, lnd_address, funding_amount_sat, Some(500_000_000), None)
81+
.unwrap();
82+
83+
let funding_txo = common::expect_channel_pending_event!(node, lnd_node_id);
84+
common::wait_for_tx(&electrs_client, funding_txo.txid);
85+
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 6);
86+
node.sync_wallets().unwrap();
87+
let user_channel_id = common::expect_channel_ready_event!(node, lnd_node_id);
88+
89+
// Send a payment to LND
90+
let mut rng = thread_rng();
91+
let rand_label: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
92+
let lnd_invoice = lnd.create_invoice(100_000_000, rand_label).unwrap();
93+
let parsed_invoice = lightning_invoice::Bolt11Invoice::from_str(&lnd_invoice).unwrap();
94+
95+
node.bolt11_payment().send(&parsed_invoice, None).unwrap();
96+
common::expect_event!(node, PaymentSuccessful);
97+
lnd.check_invoice_paid(&lnd_invoice).unwrap();
98+
99+
// Sleep to process payment in LND
100+
thread::sleep(Duration::from_secs(1));
101+
102+
// Send a payment to LDK
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()).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+
macro_rules! async_block {
116+
($expr:expr) => {{
117+
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
118+
rt.block_on($expr)
119+
}};
120+
}
121+
122+
pub struct ClientLnd {
123+
client: LndClient,
124+
}
125+
126+
impl ClientLnd {
127+
pub fn new(cert: String, macaroon: String, socket: String) -> Result<Self, Box<dyn Error>> {
128+
// Read the contents of the file into a vector of bytes
129+
let cert_bytes = fs::read(cert).expect("FailedToReadTlsCertFile");
130+
let mac_bytes = fs::read(macaroon).expect("FailedToReadMacaroonFile");
131+
132+
// Convert the bytes to a hex string
133+
let cert = cert_bytes.as_hex().to_string();
134+
let macaroon = mac_bytes.as_hex().to_string();
135+
136+
let client = async_block!(connect(cert, macaroon, socket))?;
137+
Ok(ClientLnd { client })
138+
}
139+
140+
pub fn get_node_info(&mut self) -> Result<GetInfoResponse, Box<dyn Error>> {
141+
let response =
142+
async_block!(self.client.lightning().get_info(GetInfoRequest {}))?.into_inner();
143+
Ok(response)
144+
}
145+
146+
pub fn create_invoice(
147+
&mut self, amount_msat: u64, memo: String,
148+
) -> Result<String, Box<dyn Error>> {
149+
let invoice = Invoice { value_msat: amount_msat as i64, memo, ..Default::default() };
150+
let response = async_block!(self.client.lightning().add_invoice(invoice))?.into_inner();
151+
Ok(response.payment_request)
152+
}
153+
154+
pub fn check_invoice_paid(&mut self, payment_request: &str) -> Result<(), Box<dyn Error>> {
155+
let response = async_block!(self
156+
.client
157+
.lightning()
158+
.list_invoices(ListInvoiceRequest { pending_only: false, ..Default::default() }))?
159+
.into_inner();
160+
161+
if let Some(invoice) =
162+
response.invoices.iter().find(|inv| inv.payment_request == payment_request)
163+
{
164+
if invoice.state == Settled as i32 {
165+
Ok(())
166+
} else {
167+
panic!("InvoiceNotPaid");
168+
}
169+
} else {
170+
panic!("InvoiceNotFound");
171+
}
172+
}
173+
174+
pub fn pay_invoice(&mut self, invoice_str: &str) -> Result<SendResponse, Box<dyn Error>> {
175+
let send_req =
176+
SendRequest { payment_request: invoice_str.to_string(), ..Default::default() };
177+
let response =
178+
async_block!(self.client.lightning().send_payment_sync(send_req))?.into_inner();
179+
Ok(response)
180+
}
181+
}

0 commit comments

Comments
 (0)