Skip to content

Commit ef97b98

Browse files
committed
Add property and protocol coverage tests
1 parent d842fc9 commit ef97b98

8 files changed

Lines changed: 336 additions & 59 deletions

File tree

Cargo.lock

Lines changed: 78 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e-tests/Cargo.lock

Lines changed: 24 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

e2e-tests/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ ldk-server-protos = { path = "../ldk-server-protos", features = ["serde"] }
1212
serde_json = "1.0"
1313
hex-conservative = { version = "0.2", features = ["std"] }
1414
ldk-node = { git = "https://github.com/lightningdevkit/ldk-node", rev = "d1bbf978c8b7abe87ae2e40793556c1fe4e7ea49" }
15+
reqwest = { version = "0.12", default-features = false, features = ["http2", "rustls-tls"] }

e2e-tests/tests/e2e.rs

Lines changed: 85 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ use e2e_tests::{
1414
find_available_port, mine_and_sync, run_cli, run_cli_raw, setup_funded_channel,
1515
wait_for_onchain_balance, LdkServerConfig, LdkServerHandle, TestBitcoind,
1616
};
17-
use ldk_node::lightning::ln::msgs::SocketAddress;
1817
use hex_conservative::{DisplayHex, FromHex};
18+
use ldk_node::bitcoin::hashes::hmac::{Hmac, HmacEngine};
19+
use ldk_node::bitcoin::hashes::HashEngine;
1920
use ldk_node::bitcoin::hashes::{sha256, Hash};
21+
use ldk_node::lightning::ln::msgs::SocketAddress;
2022
use ldk_node::lightning::offers::offer::Offer;
2123
use ldk_node::lightning_invoice::Bolt11Invoice;
2224
use ldk_server_client::ldk_server_protos::api::{
@@ -26,6 +28,35 @@ use ldk_server_client::ldk_server_protos::events::event_envelope::Event;
2628
use ldk_server_client::ldk_server_protos::types::{
2729
bolt11_invoice_description, Bolt11InvoiceDescription,
2830
};
31+
use ldk_server_protos::endpoints::GET_NODE_INFO_PATH;
32+
use reqwest::{header::HeaderMap, Certificate, Client};
33+
34+
fn compute_auth_header(api_key: &str) -> String {
35+
let timestamp =
36+
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_secs();
37+
let mut hmac_engine: HmacEngine<sha256::Hash> = HmacEngine::new(api_key.as_bytes());
38+
hmac_engine.input(&timestamp.to_be_bytes());
39+
let hmac = Hmac::<sha256::Hash>::from_engine(hmac_engine);
40+
format!("HMAC {timestamp}:{hmac}")
41+
}
42+
43+
async fn raw_grpc_request(
44+
server: &LdkServerHandle, path: &str, extra_headers: HeaderMap,
45+
) -> reqwest::Response {
46+
let cert = Certificate::from_pem(&std::fs::read(&server.tls_cert_path).unwrap()).unwrap();
47+
let client = Client::builder().use_rustls_tls().add_root_certificate(cert).build().unwrap();
48+
49+
let mut request = client
50+
.post(format!("https://{}{}", server.base_url(), path))
51+
.header("content-type", "application/grpc+proto")
52+
.header("te", "trailers")
53+
.header("x-auth", compute_auth_header(&server.api_key));
54+
for (name, value) in &extra_headers {
55+
request = request.header(name, value);
56+
}
57+
58+
request.body(vec![0, 0, 0, 0, 0]).send().await.unwrap()
59+
}
2960

3061
#[tokio::test]
3162
async fn test_cli_get_node_info() {
@@ -215,31 +246,21 @@ async fn test_cli_decode_invoice() {
215246
// serde_json escapes control chars (U+0000–U+001F) as \uXXXX in JSON.
216247
let desc_with_ansi = "pay me\x1b[31m RED \x1b[0m";
217248
let output_ansi = run_cli(&server, &["bolt11-receive", "-d", desc_with_ansi]);
218-
let raw_decoded = run_cli_raw(
219-
&server,
220-
&["decode-invoice", output_ansi["invoice"].as_str().unwrap()],
221-
);
222-
assert!(
223-
!raw_decoded.contains('\x1b'),
224-
"Raw CLI output must not contain ANSI escape bytes"
225-
);
249+
let raw_decoded =
250+
run_cli_raw(&server, &["decode-invoice", output_ansi["invoice"].as_str().unwrap()]);
251+
assert!(!raw_decoded.contains('\x1b'), "Raw CLI output must not contain ANSI escape bytes");
226252

227253
// Test that Unicode bidi override characters in the description are escaped
228254
// (sanitize_for_terminal replaces them with \uXXXX in CLI output)
229255
let desc_with_bidi = "pay me\u{202E}evil";
230256
let output_bidi = run_cli(&server, &["bolt11-receive", "-d", desc_with_bidi]);
231-
let raw_bidi = run_cli_raw(
232-
&server,
233-
&["decode-invoice", output_bidi["invoice"].as_str().unwrap()],
234-
);
257+
let raw_bidi =
258+
run_cli_raw(&server, &["decode-invoice", output_bidi["invoice"].as_str().unwrap()]);
235259
assert!(
236260
!raw_bidi.contains('\u{202E}'),
237261
"Raw CLI output must not contain bidi override characters"
238262
);
239-
assert!(
240-
raw_bidi.contains("\\u202E"),
241-
"Bidi characters should be escaped as \\uXXXX in output"
242-
);
263+
assert!(raw_bidi.contains("\\u202E"), "Bidi characters should be escaped as \\uXXXX in output");
243264
}
244265

245266
#[tokio::test]
@@ -307,30 +328,20 @@ async fn test_cli_decode_offer() {
307328
// Test that ANSI escape sequences cannot reach the terminal via CLI output.
308329
let desc_with_ansi = "offer\x1b[31m RED \x1b[0m";
309330
let output_ansi = run_cli(&server_a, &["bolt12-receive", desc_with_ansi]);
310-
let raw_decoded = run_cli_raw(
311-
&server_a,
312-
&["decode-offer", output_ansi["offer"].as_str().unwrap()],
313-
);
314-
assert!(
315-
!raw_decoded.contains('\x1b'),
316-
"Raw CLI output must not contain ANSI escape bytes"
317-
);
331+
let raw_decoded =
332+
run_cli_raw(&server_a, &["decode-offer", output_ansi["offer"].as_str().unwrap()]);
333+
assert!(!raw_decoded.contains('\x1b'), "Raw CLI output must not contain ANSI escape bytes");
318334

319335
// Test that Unicode bidi override characters in the description are escaped
320336
let desc_with_bidi = "offer\u{202E}evil";
321337
let output_bidi = run_cli(&server_a, &["bolt12-receive", desc_with_bidi]);
322-
let raw_bidi = run_cli_raw(
323-
&server_a,
324-
&["decode-offer", output_bidi["offer"].as_str().unwrap()],
325-
);
338+
let raw_bidi =
339+
run_cli_raw(&server_a, &["decode-offer", output_bidi["offer"].as_str().unwrap()]);
326340
assert!(
327341
!raw_bidi.contains('\u{202E}'),
328342
"Raw CLI output must not contain bidi override characters"
329343
);
330-
assert!(
331-
raw_bidi.contains("\\u202E"),
332-
"Bidi characters should be escaped as \\uXXXX in output"
333-
);
344+
assert!(raw_bidi.contains("\\u202E"), "Bidi characters should be escaped as \\uXXXX in output");
334345
}
335346

336347
#[tokio::test]
@@ -1148,3 +1159,44 @@ async fn test_metrics_endpoint_with_auth() {
11481159
assert!(metrics.contains("ldk_server_total_anchor_channels_reserve_sats 0"));
11491160
assert!(metrics.contains("ldk_server_total_lightning_balance_sats 0"));
11501161
}
1162+
1163+
#[tokio::test]
1164+
async fn test_grpc_rejects_invalid_timeout_header() {
1165+
let bitcoind = TestBitcoind::new();
1166+
let server = LdkServerHandle::start(&bitcoind).await;
1167+
1168+
let mut headers = HeaderMap::new();
1169+
headers.insert("grpc-timeout", "abc".parse().unwrap());
1170+
1171+
let response =
1172+
raw_grpc_request(&server, &format!("/api.LightningNode/{GET_NODE_INFO_PATH}"), headers)
1173+
.await;
1174+
assert_eq!(response.status(), 200);
1175+
assert_eq!(response.headers().get("grpc-status").unwrap(), "3");
1176+
assert_eq!(response.headers().get("grpc-message").unwrap(), "Invalid grpc-timeout header");
1177+
}
1178+
1179+
#[tokio::test]
1180+
async fn test_grpc_rejects_unknown_paths_and_methods() {
1181+
let bitcoind = TestBitcoind::new();
1182+
let server = LdkServerHandle::start(&bitcoind).await;
1183+
1184+
let response = raw_grpc_request(
1185+
&server,
1186+
&format!("/not-the-service/{GET_NODE_INFO_PATH}"),
1187+
HeaderMap::new(),
1188+
)
1189+
.await;
1190+
assert_eq!(response.status(), 200);
1191+
assert_eq!(response.headers().get("grpc-status").unwrap(), "12");
1192+
assert_eq!(
1193+
response.headers().get("grpc-message").unwrap(),
1194+
"Unknown path%3A %2Fnot-the-service%2FGetNodeInfo"
1195+
);
1196+
1197+
let response =
1198+
raw_grpc_request(&server, "/api.LightningNode/DoesNotExist", HeaderMap::new()).await;
1199+
assert_eq!(response.status(), 200);
1200+
assert_eq!(response.headers().get("grpc-status").unwrap(), "12");
1201+
assert_eq!(response.headers().get("grpc-message").unwrap(), "Unknown method%3A DoesNotExist");
1202+
}

ldk-server-client/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,5 @@ rustls = "0.21"
1919
rustls-pemfile = "1"
2020

2121
[dev-dependencies]
22+
proptest = "1.6.0"
2223
tokio = { version = "1", default-features = false, features = ["macros", "rt"] }

0 commit comments

Comments
 (0)