Skip to content

Commit c017d1a

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 67bb7f7 commit c017d1a

File tree

2 files changed

+182
-0
lines changed

2 files changed

+182
-0
lines changed

Cargo.toml

100644100755
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ electrum-client = { version = "0.21.0", default-features = true }
9696
bitcoincore-rpc = { version = "0.19.0", default-features = false }
9797
proptest = "1.0.0"
9898
regex = "1.5.6"
99+
lnd_grpc_rust = "2.10.0"
99100

100101
[target.'cfg(not(no_download))'.dev-dependencies]
101102
electrsd = { version = "0.29.0", features = ["legacy", "esplora_a33e97e1", "bitcoind_25_0"] }
@@ -123,4 +124,5 @@ check-cfg = [
123124
"cfg(ldk_bench)",
124125
"cfg(tokio_unstable)",
125126
"cfg(cln_test)",
127+
"cfg(lnd_test)",
126128
]

tests/integration_test_lnd.rs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
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::{GetInfoResponse, GetInfoRequest, ListInvoiceRequest, Invoice, SendRequest, SendResponse, invoice::InvoiceState::Settled};
11+
use lnd_grpc_rust::{connect, LndClient};
12+
13+
use bitcoincore_rpc::Auth;
14+
use bitcoincore_rpc::Client as BitcoindClient;
15+
16+
use electrum_client::Client as ElectrumClient;
17+
use lightning_invoice::{Bolt11InvoiceDescription, Description};
18+
19+
use rand::distributions::Alphanumeric;
20+
use rand::{thread_rng, Rng};
21+
22+
use bitcoin::hex::DisplayHex;
23+
24+
use std::default::Default;
25+
use std::error::Error;
26+
use std::str::FromStr;
27+
use std::time::Duration;
28+
use std::{fs, thread};
29+
30+
#[test]
31+
fn test_lnd() {
32+
// Setup bitcoind / electrs clients
33+
let bitcoind_client = BitcoindClient::new(
34+
"127.0.0.1:18443",
35+
Auth::UserPass("user".to_string(), "pass".to_string()),
36+
)
37+
.unwrap();
38+
let electrs_client = ElectrumClient::new("tcp://127.0.0.1:50001").unwrap();
39+
40+
// Give electrs a kick.
41+
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 1);
42+
43+
// Setup LDK Node
44+
let config = common::random_config(true);
45+
let mut builder = Builder::from_config(config.node_config);
46+
builder.set_chain_source_esplora("http://127.0.0.1:3002".to_string(), None);
47+
48+
let node = builder.build().unwrap();
49+
node.start().unwrap();
50+
51+
// Premine some funds and distribute
52+
let address = node.onchain_payment().new_address().unwrap();
53+
let premine_amount = Amount::from_sat(5_000_000);
54+
common::premine_and_distribute_funds(
55+
&bitcoind_client,
56+
&electrs_client,
57+
vec![address],
58+
premine_amount,
59+
);
60+
61+
// Setup LND
62+
let endpoint = "127.0.0.1:8081";
63+
let cert_path = "./data_lnd/tls.cert".to_string();
64+
let macaroon_path = "./data_lnd/data/chain/bitcoin/regtest/admin.macaroon".to_string();
65+
let mut lnd = ClientLnd::new(cert_path, macaroon_path, endpoint.to_string()).unwrap();
66+
67+
let info = lnd.get_node_info().unwrap();
68+
let lnd_node_pubkey = info.identity_pubkey;
69+
let lnd_node_id = PublicKey::from_str(&lnd_node_pubkey).unwrap();
70+
let lnd_address: SocketAddress = "127.0.0.1:9735".parse().unwrap();
71+
72+
node.sync_wallets().unwrap();
73+
74+
// Open the channel
75+
let funding_amount_sat = 1_000_000;
76+
77+
node.open_channel(lnd_node_id, lnd_address, funding_amount_sat, Some(500_000_000), None)
78+
.unwrap();
79+
80+
let funding_txo = common::expect_channel_pending_event!(node, lnd_node_id);
81+
common::wait_for_tx(&electrs_client, funding_txo.txid);
82+
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 6);
83+
node.sync_wallets().unwrap();
84+
let user_channel_id = common::expect_channel_ready_event!(node, lnd_node_id);
85+
86+
// Send a payment to LND
87+
let mut rng = thread_rng();
88+
let rand_label: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
89+
let lnd_invoice = lnd.create_invoice(100_000_000, rand_label).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+
lnd.check_invoice_paid(&lnd_invoice).unwrap();
95+
96+
// Sleep to process payment in LND
97+
thread::sleep(Duration::from_secs(1));
98+
99+
// Send a payment to LDK
100+
let rand_label: String = (0..7).map(|_| rng.sample(Alphanumeric) as char).collect();
101+
let invoice_description =
102+
Bolt11InvoiceDescription::Direct(Description::new(rand_label).unwrap());
103+
let ldk_invoice =
104+
node.bolt11_payment().receive(9_000_000, &invoice_description, 3600).unwrap();
105+
lnd.pay_invoice(&ldk_invoice.to_string()).unwrap();
106+
common::expect_event!(node, PaymentReceived);
107+
108+
node.close_channel(&user_channel_id, lnd_node_id).unwrap();
109+
common::expect_event!(node, ChannelClosed);
110+
node.stop().unwrap();
111+
}
112+
113+
macro_rules! async_block {
114+
($expr:expr) => {{
115+
let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
116+
rt.block_on($expr)
117+
}};
118+
}
119+
120+
pub struct ClientLnd {
121+
client: LndClient,
122+
}
123+
124+
impl ClientLnd {
125+
pub fn new(cert: String, macaroon: String, socket: String) -> Result<Self, Box<dyn Error>> {
126+
// Read the contents of the file into a vector of bytes
127+
let cert_bytes = fs::read(cert).expect("FailedToReadTlsCertFile");
128+
let mac_bytes = fs::read(macaroon).expect("FailedToReadMacaroonFile");
129+
130+
// Convert the bytes to a hex string
131+
let cert = cert_bytes.as_hex().to_string();
132+
let macaroon = mac_bytes.as_hex().to_string();
133+
134+
let client = async_block!(connect(cert, macaroon, socket))?;
135+
Ok(ClientLnd { client })
136+
}
137+
138+
pub fn get_node_info(&mut self) -> Result<GetInfoResponse, Box<dyn Error>> {
139+
let response = async_block!(
140+
self.client.lightning().get_info(GetInfoRequest {})
141+
)?.into_inner();
142+
Ok(response)
143+
}
144+
145+
pub fn create_invoice(&mut self, amount_msat: u64, memo: String) -> Result<String, Box<dyn Error>> {
146+
let invoice = Invoice {
147+
value_msat: amount_msat as i64,
148+
memo,
149+
..Default::default()
150+
};
151+
let response = async_block!(
152+
self.client.lightning().add_invoice(invoice)
153+
)?.into_inner();
154+
Ok(response.payment_request)
155+
}
156+
157+
pub fn check_invoice_paid(&mut self, payment_request: &str) -> Result<(), Box<dyn Error>> {
158+
let response = async_block!(
159+
self.client.lightning().list_invoices(ListInvoiceRequest {pending_only:false, ..Default::default()})
160+
)?.into_inner();
161+
162+
if let Some(invoice) = response.invoices.iter().find(|inv| inv.payment_request == payment_request) {
163+
if invoice.state == Settled as i32 {
164+
Ok(())
165+
} else {
166+
panic!("InvoiceNotPaid");
167+
}
168+
} else {
169+
panic!("InvoiceNotFound");
170+
}
171+
}
172+
173+
pub fn pay_invoice(&mut self, invoice_str: &str) -> Result<SendResponse, Box<dyn Error>> {
174+
let send_req = SendRequest {payment_request: invoice_str.to_string(), ..Default::default()};
175+
let response = async_block!(
176+
self.client.lightning().send_payment_sync(send_req)
177+
)?.into_inner();
178+
Ok(response)
179+
}
180+
}

0 commit comments

Comments
 (0)