-
Notifications
You must be signed in to change notification settings - Fork 114
Add LND Integration Tests in CI #509
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
name: CI Checks - LND Integration Tests | ||
|
||
on: [push, pull_request] | ||
|
||
jobs: | ||
check-lnd: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@v4 | ||
|
||
- name: Create temporary directory for LND data | ||
id: create-temp-dir | ||
run: echo "LND_DATA_DIR=$(mktemp -d)" >> $GITHUB_ENV | ||
|
||
- name: Start bitcoind, electrs, and LND | ||
run: docker compose -f docker-compose-lnd.yml up -d | ||
env: | ||
LND_DATA_DIR: ${{ env.LND_DATA_DIR }} | ||
|
||
- name: Set permissions for LND data directory | ||
# In PR 4622 (https://github.com/lightningnetwork/lnd/pull/4622), | ||
# LND sets file permissions to 0700, preventing test code from accessing them. | ||
# This step ensures the test suite has the necessary permissions. | ||
run: sudo chmod -R 755 $LND_DATA_DIR | ||
env: | ||
LND_DATA_DIR: ${{ env.LND_DATA_DIR }} | ||
|
||
- name: Run LND integration tests | ||
run: LND_CERT_PATH=$LND_DATA_DIR/tls.cert LND_MACAROON_PATH=$LND_DATA_DIR/data/chain/bitcoin/regtest/admin.macaroon | ||
RUSTFLAGS="--cfg lnd_test" cargo test --test integration_tests_lnd -- --exact --show-output | ||
env: | ||
LND_DATA_DIR: ${{ env.LND_DATA_DIR }} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
services: | ||
bitcoin: | ||
image: blockstream/bitcoind:24.1 | ||
platform: linux/amd64 | ||
command: | ||
[ | ||
"bitcoind", | ||
"-printtoconsole", | ||
"-regtest=1", | ||
"-rpcallowip=0.0.0.0/0", | ||
"-rpcbind=0.0.0.0", | ||
"-rpcuser=user", | ||
"-rpcpassword=pass", | ||
"-fallbackfee=0.00001", | ||
"-zmqpubrawblock=tcp://0.0.0.0:28332", | ||
"-zmqpubrawtx=tcp://0.0.0.0:28333" | ||
] | ||
ports: | ||
- "18443:18443" # Regtest RPC port | ||
- "18444:18444" # Regtest P2P port | ||
- "28332:28332" # ZMQ block port | ||
- "28333:28333" # ZMQ tx port | ||
networks: | ||
- bitcoin-electrs | ||
healthcheck: | ||
test: ["CMD", "bitcoin-cli", "-regtest", "-rpcuser=user", "-rpcpassword=pass", "getblockchaininfo"] | ||
interval: 5s | ||
timeout: 10s | ||
retries: 5 | ||
|
||
electrs: | ||
image: blockstream/esplora:electrs-cd9f90c115751eb9d2bca9a4da89d10d048ae931 | ||
platform: linux/amd64 | ||
depends_on: | ||
bitcoin: | ||
condition: service_healthy | ||
command: | ||
[ | ||
"/app/electrs_bitcoin/bin/electrs", | ||
"-vvvv", | ||
"--timestamp", | ||
"--jsonrpc-import", | ||
"--cookie=user:pass", | ||
"--network=regtest", | ||
"--daemon-rpc-addr=bitcoin:18443", | ||
"--http-addr=0.0.0.0:3002", | ||
"--electrum-rpc-addr=0.0.0.0:50001" | ||
] | ||
ports: | ||
- "3002:3002" | ||
- "50001:50001" | ||
networks: | ||
- bitcoin-electrs | ||
|
||
lnd: | ||
image: lightninglabs/lnd:v0.18.5-beta | ||
container_name: ldk-node-lnd | ||
depends_on: | ||
- bitcoin | ||
volumes: | ||
- ${LND_DATA_DIR}:/root/.lnd | ||
ports: | ||
- "8081:8081" | ||
- "9735:9735" | ||
command: | ||
- "--noseedbackup" | ||
- "--trickledelay=5000" | ||
- "--alias=ldk-node-lnd-test" | ||
- "--externalip=lnd:9735" | ||
- "--bitcoin.active" | ||
- "--bitcoin.regtest" | ||
- "--bitcoin.node=bitcoind" | ||
- "--bitcoind.rpchost=bitcoin:18443" | ||
- "--bitcoind.rpcuser=user" | ||
- "--bitcoind.rpcpass=pass" | ||
- "--bitcoind.zmqpubrawblock=tcp://bitcoin:28332" | ||
- "--bitcoind.zmqpubrawtx=tcp://bitcoin:28333" | ||
- "--accept-keysend" | ||
- "--rpclisten=0.0.0.0:8081" | ||
- "--tlsextradomain=lnd" | ||
- "--tlsextraip=0.0.0.0" | ||
networks: | ||
- bitcoin-electrs | ||
|
||
networks: | ||
bitcoin-electrs: | ||
driver: bridge |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,227 @@ | ||
#![cfg(lnd_test)] | ||
|
||
mod common; | ||
|
||
use ldk_node::bitcoin::secp256k1::PublicKey; | ||
use ldk_node::bitcoin::Amount; | ||
use ldk_node::lightning::ln::msgs::SocketAddress; | ||
use ldk_node::{Builder, Event}; | ||
|
||
use lnd_grpc_rust::lnrpc::{ | ||
invoice::InvoiceState::Settled as LndInvoiceStateSettled, GetInfoRequest as LndGetInfoRequest, | ||
GetInfoResponse as LndGetInfoResponse, Invoice as LndInvoice, | ||
ListInvoiceRequest as LndListInvoiceRequest, QueryRoutesRequest as LndQueryRoutesRequest, | ||
Route as LndRoute, SendRequest as LndSendRequest, | ||
}; | ||
use lnd_grpc_rust::{connect, LndClient}; | ||
|
||
use bitcoincore_rpc::Auth; | ||
use bitcoincore_rpc::Client as BitcoindClient; | ||
|
||
use electrum_client::Client as ElectrumClient; | ||
use lightning_invoice::{Bolt11InvoiceDescription, Description}; | ||
|
||
use bitcoin::hex::DisplayHex; | ||
|
||
use std::default::Default; | ||
use std::str::FromStr; | ||
use tokio::fs; | ||
|
||
#[tokio::test(flavor = "multi_thread", worker_threads = 1)] | ||
async fn test_lnd() { | ||
// Setup bitcoind / electrs clients | ||
let bitcoind_client = BitcoindClient::new( | ||
"127.0.0.1:18443", | ||
Auth::UserPass("user".to_string(), "pass".to_string()), | ||
) | ||
.unwrap(); | ||
let electrs_client = ElectrumClient::new("tcp://127.0.0.1:50001").unwrap(); | ||
|
||
// Give electrs a kick. | ||
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 1); | ||
|
||
// Setup LDK Node | ||
let config = common::random_config(true); | ||
let mut builder = Builder::from_config(config.node_config); | ||
builder.set_chain_source_esplora("http://127.0.0.1:3002".to_string(), None); | ||
|
||
let node = builder.build().unwrap(); | ||
node.start().unwrap(); | ||
|
||
// Premine some funds and distribute | ||
let address = node.onchain_payment().new_address().unwrap(); | ||
let premine_amount = Amount::from_sat(5_000_000); | ||
common::premine_and_distribute_funds( | ||
&bitcoind_client, | ||
&electrs_client, | ||
vec![address], | ||
premine_amount, | ||
); | ||
|
||
// Setup LND | ||
let endpoint = "127.0.0.1:8081"; | ||
let cert_path = std::env::var("LND_CERT_PATH").expect("LND_CERT_PATH not set"); | ||
let macaroon_path = std::env::var("LND_MACAROON_PATH").expect("LND_MACAROON_PATH not set"); | ||
let mut lnd = TestLndClient::new(cert_path, macaroon_path, endpoint.to_string()).await; | ||
|
||
let lnd_node_info = lnd.get_node_info().await; | ||
let lnd_node_id = PublicKey::from_str(&lnd_node_info.identity_pubkey).unwrap(); | ||
let lnd_address: SocketAddress = "127.0.0.1:9735".parse().unwrap(); | ||
|
||
node.sync_wallets().unwrap(); | ||
|
||
// Open the channel | ||
let funding_amount_sat = 1_000_000; | ||
|
||
node.open_channel(lnd_node_id, lnd_address, funding_amount_sat, Some(500_000_000), None) | ||
.unwrap(); | ||
|
||
let funding_txo = common::expect_channel_pending_event!(node, lnd_node_id); | ||
common::wait_for_tx(&electrs_client, funding_txo.txid); | ||
common::generate_blocks_and_wait(&bitcoind_client, &electrs_client, 6); | ||
node.sync_wallets().unwrap(); | ||
let user_channel_id = common::expect_channel_ready_event!(node, lnd_node_id); | ||
|
||
// Send a payment to LND | ||
let lnd_invoice = lnd.create_invoice(100_000_000).await; | ||
let parsed_invoice = lightning_invoice::Bolt11Invoice::from_str(&lnd_invoice).unwrap(); | ||
|
||
node.bolt11_payment().send(&parsed_invoice, None).unwrap(); | ||
common::expect_event!(node, PaymentSuccessful); | ||
let lnd_listed_invoices = lnd.list_invoices().await; | ||
assert_eq!(lnd_listed_invoices.len(), 1); | ||
assert_eq!(lnd_listed_invoices.first().unwrap().state, LndInvoiceStateSettled as i32); | ||
|
||
// Check route LND -> LDK | ||
let amount_msat = 9_000_000; | ||
let max_retries = 7; | ||
for attempt in 1..=max_retries { | ||
match lnd.query_routes(&node.node_id().to_string(), amount_msat).await { | ||
Ok(routes) => { | ||
if !routes.is_empty() { | ||
break; | ||
} | ||
}, | ||
Err(err) => { | ||
if attempt == max_retries { | ||
panic!("Failed to find route from LND to LDK: {}", err); | ||
} | ||
}, | ||
}; | ||
// wait for the payment process | ||
tokio::time::sleep(std::time::Duration::from_millis(200)).await; | ||
} | ||
|
||
// Send a payment to LDK | ||
let invoice_description = | ||
Bolt11InvoiceDescription::Direct(Description::new("lndTest".to_string()).unwrap()); | ||
let ldk_invoice = | ||
node.bolt11_payment().receive(amount_msat, &invoice_description, 3600).unwrap(); | ||
lnd.pay_invoice(&ldk_invoice.to_string()).await; | ||
common::expect_event!(node, PaymentReceived); | ||
|
||
node.close_channel(&user_channel_id, lnd_node_id).unwrap(); | ||
common::expect_event!(node, ChannelClosed); | ||
node.stop().unwrap(); | ||
} | ||
|
||
struct TestLndClient { | ||
client: LndClient, | ||
} | ||
|
||
impl TestLndClient { | ||
async fn new(cert_path: String, macaroon_path: String, socket: String) -> Self { | ||
// Read the contents of the file into a vector of bytes | ||
let cert_bytes = fs::read(cert_path).await.expect("Failed to read tls cert file"); | ||
let mac_bytes = fs::read(macaroon_path).await.expect("Failed to read macaroon file"); | ||
|
||
// Convert the bytes to a hex string | ||
let cert = cert_bytes.as_hex().to_string(); | ||
let macaroon = mac_bytes.as_hex().to_string(); | ||
|
||
let client = connect(cert, macaroon, socket).await.expect("Failed to connect to Lnd"); | ||
|
||
TestLndClient { client } | ||
} | ||
|
||
async fn get_node_info(&mut self) -> LndGetInfoResponse { | ||
let response = self | ||
.client | ||
.lightning() | ||
.get_info(LndGetInfoRequest {}) | ||
.await | ||
.expect("Failed to fetch node info from LND") | ||
.into_inner(); | ||
|
||
response | ||
} | ||
|
||
async fn create_invoice(&mut self, amount_msat: u64) -> String { | ||
let invoice = LndInvoice { value_msat: amount_msat as i64, ..Default::default() }; | ||
|
||
self.client | ||
.lightning() | ||
.add_invoice(invoice) | ||
.await | ||
.expect("Failed to create invoice on LND") | ||
.into_inner() | ||
.payment_request | ||
} | ||
|
||
async fn list_invoices(&mut self) -> Vec<LndInvoice> { | ||
self.client | ||
.lightning() | ||
.list_invoices(LndListInvoiceRequest { ..Default::default() }) | ||
.await | ||
.expect("Failed to list invoices from LND") | ||
.into_inner() | ||
.invoices | ||
} | ||
|
||
async fn query_routes( | ||
&mut self, pubkey: &str, amount_msat: u64, | ||
) -> Result<Vec<LndRoute>, String> { | ||
let request = LndQueryRoutesRequest { | ||
pub_key: pubkey.to_string(), | ||
amt_msat: amount_msat as i64, | ||
..Default::default() | ||
}; | ||
|
||
let response = self | ||
.client | ||
.lightning() | ||
.query_routes(request) | ||
.await | ||
.map_err(|err| format!("Failed to query routes from LND: {:?}", err))? | ||
.into_inner(); | ||
|
||
if response.routes.is_empty() { | ||
return Err(format!("No routes found for pubkey: {}", pubkey)); | ||
} | ||
|
||
Ok(response.routes) | ||
} | ||
|
||
async fn pay_invoice(&mut self, invoice_str: &str) { | ||
let send_req = | ||
LndSendRequest { payment_request: invoice_str.to_string(), ..Default::default() }; | ||
let response = self | ||
.client | ||
.lightning() | ||
.send_payment_sync(send_req) | ||
.await | ||
.expect("Failed to pay invoice on LND") | ||
.into_inner(); | ||
|
||
if !response.payment_error.is_empty() || response.payment_preimage.is_empty() { | ||
panic!( | ||
"LND payment failed: {}", | ||
if response.payment_error.is_empty() { | ||
"No preimage returned" | ||
} else { | ||
&response.payment_error | ||
} | ||
); | ||
} | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Thanks for adding comment, but let's move it down into the item this is meant for.