diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9e414c44..50f26500 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -11,6 +11,8 @@ jobs: name: Check code formatting runs-on: ubuntu-latest steps: + - name: Install protoc + run: sudo apt install -y protobuf-compiler - uses: actions/checkout@v3 - uses: actions-rs/cargo@v1 name: cargo fmt @@ -27,6 +29,8 @@ jobs: name: Build and test runs-on: ubuntu-latest steps: + - name: Install protoc + run: sudo apt install -y protobuf-compiler - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: diff --git a/Cargo.lock b/Cargo.lock index 557b1884..1706bfee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,15 @@ dependencies = [ "version_check", ] +[[package]] +name = "aho-corasick" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6748e8def348ed4d14996fa801f4122cd763fff530258cdc03f64b25f89d3a5a" +dependencies = [ + "memchr", +] + [[package]] name = "anstyle" version = "1.0.1" @@ -103,6 +112,51 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "axum" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +dependencies = [ + "async-trait", + "axum-core", + "bitflags 1.3.2", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "sync_wrapper", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + [[package]] name = "backtrace" version = "0.3.68" @@ -145,6 +199,7 @@ dependencies = [ "bech32", "bitcoin_hashes 0.11.0", "secp256k1 0.24.3", + "serde", ] [[package]] @@ -172,6 +227,9 @@ name = "bitcoin_hashes" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90064b8dee6815a6470d60bad07bbbaee885c0e12d04177138fa3291a01b7bc4" +dependencies = [ + "serde", +] [[package]] name = "bitcoin_hashes" @@ -262,6 +320,40 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +[[package]] +name = "cln-grpc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6dfb1141b61c16e04c39a05de429f7cc825fd5d2c9e374b59a8db0703a69cd1" +dependencies = [ + "anyhow", + "bitcoin 0.29.2", + "cln-rpc", + "hex", + "log", + "prost 0.11.9", + "tonic 0.8.3", + "tonic-build 0.8.4", +] + +[[package]] +name = "cln-rpc" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3b630e345cdfc6f64315414b50815a9eeabbf12438413798bf09e9e79be8b8" +dependencies = [ + "anyhow", + "bitcoin 0.29.2", + "bytes", + "futures-util", + "hex", + "log", + "serde", + "serde_json", + "tokio", + "tokio-util 0.7.8", +] + [[package]] name = "colored" version = "2.0.4" @@ -348,6 +440,12 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "errno" version = "0.3.2" @@ -387,6 +485,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + [[package]] name = "fnv" version = "1.0.7" @@ -511,7 +615,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 1.9.3", "slab", "tokio", "tokio-util 0.7.8", @@ -652,6 +756,16 @@ dependencies = [ "hashbrown 0.12.3", ] +[[package]] +name = "indexmap" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +dependencies = [ + "equivalent", + "hashbrown 0.14.0", +] + [[package]] name = "is-terminal" version = "0.4.9" @@ -730,12 +844,24 @@ version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +[[package]] +name = "matchit" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed1202b2a6f884ae56f04cff409ab315c5ce26b5e58d7412e484f01fd52f52ef" + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "miniz_oxide" version = "0.7.1" @@ -859,8 +985,18 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" dependencies = [ - "fixedbitset", - "indexmap", + "fixedbitset 0.2.0", + "indexmap 1.9.3", +] + +[[package]] +name = "petgraph" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +dependencies = [ + "fixedbitset 0.4.2", + "indexmap 2.0.0", ] [[package]] @@ -901,6 +1037,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8646e95016a7a6c4adea95bafa8a16baab64b583356217f2c85db4a39d9a86" +dependencies = [ + "proc-macro2", + "syn 1.0.109", +] + [[package]] name = "proc-macro2" version = "1.0.66" @@ -930,6 +1076,16 @@ dependencies = [ "prost-derive 0.9.0", ] +[[package]] +name = "prost" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" +dependencies = [ + "bytes", + "prost-derive 0.11.9", +] + [[package]] name = "prost-build" version = "0.8.0" @@ -941,9 +1097,31 @@ dependencies = [ "itertools", "log", "multimap", - "petgraph", + "petgraph 0.5.1", "prost 0.8.0", - "prost-types", + "prost-types 0.8.0", + "tempfile", + "which", +] + +[[package]] +name = "prost-build" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" +dependencies = [ + "bytes", + "heck 0.4.1", + "itertools", + "lazy_static", + "log", + "multimap", + "petgraph 0.6.4", + "prettyplease", + "prost 0.11.9", + "prost-types 0.11.9", + "regex", + "syn 1.0.109", "tempfile", "which", ] @@ -974,6 +1152,19 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "prost-derive" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" +dependencies = [ + "anyhow", + "itertools", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "prost-types" version = "0.8.0" @@ -984,6 +1175,15 @@ dependencies = [ "prost 0.8.0", ] +[[package]] +name = "prost-types" +version = "0.11.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" +dependencies = [ + "prost 0.11.9", +] + [[package]] name = "queue-ext" version = "0.4.0" @@ -1044,6 +1244,35 @@ dependencies = [ "bitflags 1.3.2", ] +[[package]] +name = "regex" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81bc1d4caf89fac26a70747fe603c130093b53c773888797a6329091246d651a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed1ceff11a1dddaee50c9dc8e4938bd106e9d89ae372f192311e7da498e3b69" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" + [[package]] name = "ring" version = "0.16.20" @@ -1087,8 +1316,20 @@ dependencies = [ "base64 0.13.1", "log", "ring", - "sct", - "webpki", + "sct 0.6.1", + "webpki 0.21.4", +] + +[[package]] +name = "rustls" +version = "0.20.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" +dependencies = [ + "log", + "ring", + "sct 0.7.0", + "webpki 0.22.0", ] [[package]] @@ -1100,6 +1341,12 @@ dependencies = [ "base64 0.21.2", ] +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + [[package]] name = "ryu" version = "1.0.15" @@ -1122,6 +1369,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "secp256k1" version = "0.24.3" @@ -1130,6 +1387,7 @@ checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ "bitcoin_hashes 0.11.0", "secp256k1-sys 0.6.1", + "serde", ] [[package]] @@ -1233,6 +1491,7 @@ dependencies = [ "anyhow", "async-trait", "bitcoin 0.30.1", + "cln-grpc", "csv", "hex", "lightning", @@ -1244,6 +1503,7 @@ dependencies = [ "serde_millis", "thiserror", "tokio", + "tonic 0.8.3", "tonic_lnd", "triggered", ] @@ -1346,6 +1606,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "tempfile" version = "3.7.1" @@ -1455,9 +1721,20 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6" dependencies = [ - "rustls", + "rustls 0.19.1", "tokio", - "webpki", + "webpki 0.21.4", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" +dependencies = [ + "rustls 0.20.8", + "tokio", + "webpki 0.22.0", ] [[package]] @@ -1521,7 +1798,7 @@ dependencies = [ "prost 0.9.0", "prost-derive 0.9.0", "tokio", - "tokio-rustls", + "tokio-rustls 0.22.0", "tokio-stream", "tokio-util 0.6.10", "tower", @@ -1531,6 +1808,40 @@ dependencies = [ "tracing-futures", ] +[[package]] +name = "tonic" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f219fad3b929bef19b1f86fbc0358d35daed8f2cac972037ac0dc10bbb8d5fb" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64 0.13.1", + "bytes", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "hyper", + "hyper-timeout", + "percent-encoding", + "pin-project", + "prost 0.11.9", + "prost-derive 0.11.9", + "rustls-pemfile", + "tokio", + "tokio-rustls 0.23.4", + "tokio-stream", + "tokio-util 0.7.8", + "tower", + "tower-layer", + "tower-service", + "tracing", + "tracing-futures", +] + [[package]] name = "tonic-build" version = "0.5.2" @@ -1538,7 +1849,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12b52d07035516c2b74337d2ac7746075e7dcae7643816c1b12c5ff8a7484c08" dependencies = [ "proc-macro2", - "prost-build", + "prost-build 0.8.0", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "tonic-build" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bf5e9b9c0f7e0a7c027dcfaba7b2c60816c7049171f679d99ee2ff65d0de8c4" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build 0.11.9", "quote", "syn 1.0.109", ] @@ -1550,13 +1874,13 @@ source = "git+https://github.com/fedimint/tonic_lnd?branch=master#ba44af4a4293f4 dependencies = [ "hex", "prost 0.9.0", - "rustls", + "rustls 0.19.1", "rustls-pemfile", "tokio", "tokio-stream", - "tonic", - "tonic-build", - "webpki", + "tonic 0.6.2", + "tonic-build 0.5.2", + "webpki 0.21.4", ] [[package]] @@ -1567,7 +1891,7 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", - "indexmap", + "indexmap 1.9.3", "pin-project", "pin-project-lite", "rand", @@ -1759,6 +2083,16 @@ dependencies = [ "untrusted", ] +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "which" version = "4.4.0" diff --git a/sim-cli/src/main.rs b/sim-cli/src/main.rs index 52cb39b3..c706710e 100644 --- a/sim-cli/src/main.rs +++ b/sim-cli/src/main.rs @@ -6,7 +6,7 @@ use tokio::sync::Mutex; use clap::Parser; use log::LevelFilter; -use sim_lib::{lnd::LndNode, Config, LightningNode, Simulation}; +use sim_lib::{cln::ClnNode, lnd::LndNode, Config, LightningNode, NodeConnection, Simulation}; use simple_logger::SimpleLogger; #[derive(Parser)] @@ -34,16 +34,34 @@ async fn main() -> anyhow::Result<()> { let mut clients: HashMap>> = HashMap::new(); - for node in nodes { - let lnd = LndNode::new(node.address, node.macaroon, node.cert).await?; + for connection in nodes { + // TODO: We should simplify this into two minimal branches plus shared logging and inserting into the list + match connection { + NodeConnection::LND(c) => { + let node_id = c.id; + let lnd = LndNode::new(c).await?; - log::info!( - "Connected to {} - Node ID: {}", - lnd.get_info().alias, - lnd.get_info().pubkey - ); + log::info!( + "Connected to {} - Node ID: {}", + lnd.get_info().alias, + lnd.get_info().pubkey + ); - clients.insert(node.id, Arc::new(Mutex::new(lnd))); + clients.insert(node_id, Arc::new(Mutex::new(lnd))); + } + NodeConnection::CLN(c) => { + let node_id = c.id; + let cln = ClnNode::new(c).await?; + + log::info!( + "Connected to {} - Node ID: {}", + cln.get_info().alias, + cln.get_info().pubkey + ); + + clients.insert(node_id, Arc::new(Mutex::new(cln))); + } + } } let sim = Simulation::new(clients, activity, cli.total_time); diff --git a/sim-lib/Cargo.toml b/sim-lib/Cargo.toml index 7429117d..7568b5c6 100644 --- a/sim-lib/Cargo.toml +++ b/sim-lib/Cargo.toml @@ -7,11 +7,13 @@ edition = "2021" [dependencies] anyhow = { version = "1.0.69", features = ["backtrace"] } +cln-grpc = "0.1.3" serde = { version="1.0.183", features=["derive"] } serde_json = "1.0.104" bitcoin = { version = "0.30.1", features=["serde"] } lightning = { version = "0.0.116" } tonic_lnd = { git = "https://github.com/fedimint/tonic_lnd", branch="master", features=["lightningrpc", "routerrpc"]} +tonic = { version = "0.8", features = ["tls", "transport"] } async-trait = "0.1.73" thiserror = "1.0.45" log = "0.4.20" diff --git a/sim-lib/src/cln.rs b/sim-lib/src/cln.rs new file mode 100644 index 00000000..04933dc2 --- /dev/null +++ b/sim-lib/src/cln.rs @@ -0,0 +1,145 @@ +use async_trait::async_trait; +use bitcoin::secp256k1::PublicKey; +use cln_grpc::pb::{ + node_client::NodeClient, Amount, GetinfoRequest, GetinfoResponse, KeysendRequest, + KeysendResponse, ListnodesRequest, +}; +use lightning::ln::features::NodeFeatures; +use lightning::ln::PaymentHash; + +use tokio::fs::File; +use tokio::io::{AsyncReadExt, Error}; +use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity}; +use triggered::Listener; + +use crate::{ClnConnection, LightningError, LightningNode, NodeInfo, PaymentResult}; + +pub struct ClnNode { + pub client: NodeClient, + info: NodeInfo, +} + +impl ClnNode { + pub async fn new(connection: ClnConnection) -> Result { + let ca_pem = reader(&connection.ca_cert).await.map_err(|_| { + LightningError::ConnectionError("Cannot loads CA certificate".to_string()) + })?; + let client_pem = reader(&connection.client_cert).await.map_err(|_| { + LightningError::ConnectionError("Cannot loads client certificate".to_string()) + })?; + let client_key = reader(&connection.client_key) + .await + .map_err(|_| LightningError::ConnectionError("Cannot loads client key".to_string()))?; + + let ca = Certificate::from_pem(ca_pem); + let ident = Identity::from_pem(client_pem, client_key); + + let tls = ClientTlsConfig::new() + .domain_name("cln") + .identity(ident) + .ca_certificate(ca); + + let channel = Channel::from_shared(connection.address.to_string()) + .map_err(|err| LightningError::ConnectionError(err.to_string()))? + .tls_config(tls) + .map_err(|_| { + LightningError::ConnectionError("Cannot establish tls connection".to_string()) + })? + .connect() + .await + .map_err(|_| { + LightningError::ConnectionError("Cannot connect to gRPC server".to_string()) + })?; + let mut client = NodeClient::new(channel); + + let GetinfoResponse { id, alias, .. } = client + .getinfo(GetinfoRequest {}) + .await + .map_err(|err| LightningError::GetInfoError(err.to_string()))? + .into_inner(); + + Ok(Self { + client, + info: NodeInfo { + pubkey: PublicKey::from_slice(&id) + .map_err(|err| LightningError::GetInfoError(err.to_string()))?, + features: vec![], + alias, + }, + }) + } +} + +#[async_trait] +impl LightningNode for ClnNode { + fn get_info(&self) -> &NodeInfo { + &self.info + } + + async fn send_payment( + &mut self, + dest: PublicKey, + amount_msat: u64, + ) -> Result { + let KeysendResponse { payment_hash, .. } = self + .client + .key_send(KeysendRequest { + destination: dest.serialize().to_vec(), + amount_msat: Some(Amount { msat: amount_msat }), + ..Default::default() + }) + .await + .map_err(|err| LightningError::SendPaymentError(err.to_string()))? + .into_inner(); + let slice: [u8; 32] = payment_hash + .as_slice() + .try_into() + .map_err(|_| LightningError::InvalidPaymentHash)?; + + Ok(PaymentHash(slice)) + } + + async fn track_payment( + &mut self, + _hash: PaymentHash, + _shutdown: Listener, + ) -> Result { + unimplemented!() + } + + async fn get_node_features(&mut self, node: PublicKey) -> Result { + let node_id = node.serialize().to_vec(); + let nodes: Vec = self + .client + .list_nodes(ListnodesRequest { + id: Some(node_id.clone()), + }) + .await + .map_err(|err| LightningError::GetNodeInfoError(err.to_string()))? + .into_inner() + .nodes; + + // We are filtering `list_nodes` to a single node, so we should get either an empty vector or one with a single element + if let Some(node) = nodes.first() { + Ok(node + .features + .clone() + .map_or(NodeFeatures::empty(), |mut f| { + // We need to reverse this given it has the CLN wire encoding which is BE + f.reverse(); + NodeFeatures::from_le_bytes(f) + })) + } else { + Err(LightningError::GetNodeInfoError( + "Node not found".to_string(), + )) + } + } +} + +async fn reader(filename: &str) -> Result, Error> { + let mut file = File::open(filename).await?; + let mut contents = vec![]; + file.read_to_end(&mut contents).await?; + Ok(contents) +} diff --git a/sim-lib/src/lib.rs b/sim-lib/src/lib.rs index f8120554..9b761072 100644 --- a/sim-lib/src/lib.rs +++ b/sim-lib/src/lib.rs @@ -1,6 +1,7 @@ use async_trait::async_trait; use bitcoin::secp256k1::PublicKey; use csv::WriterBuilder; +use lightning::ln::features::NodeFeatures; use lightning::ln::PaymentHash; use serde::{Deserialize, Serialize}; use std::collections::HashSet; @@ -15,19 +16,35 @@ use tokio::time; use tokio::time::Duration; use triggered::{Listener, Trigger}; +pub mod cln; pub mod lnd; mod serializers; -const KEYSEND_OPTIONAL: u32 = 55; +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum NodeConnection { + #[serde(alias = "lnd", alias = "Lnd")] + LND(LndConnection), + #[serde(alias = "cln", alias = "Cln")] + CLN(ClnConnection), +} #[derive(Serialize, Deserialize, Debug, Clone)] -pub struct NodeConnection { +pub struct LndConnection { pub id: PublicKey, pub address: String, pub macaroon: String, pub cert: String, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ClnConnection { + pub id: PublicKey, + pub address: String, + pub ca_cert: String, + pub client_cert: String, + pub client_key: String, +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Config { pub nodes: Vec, @@ -104,9 +121,8 @@ pub trait LightningNode { hash: PaymentHash, shutdown: Listener, ) -> Result; - /// Looks up a node's announcement in the graph. This function currently only returns features, as they're all we - /// need, but may be updated to include any other node announcement fields if required. - async fn get_node_announcement(&self, node: PublicKey) -> Result, LightningError>; + /// Gets the list of features of a given node + async fn get_node_features(&mut self, node: PublicKey) -> Result; } #[derive(Clone, Copy)] @@ -204,11 +220,11 @@ impl Simulation { let features = source_node .lock() .await - .get_node_announcement(payment_flow.destination) + .get_node_features(payment_flow.destination) .await .map_err(|err| LightningError::GetNodeInfoError(err.to_string()))?; - if !features.contains(&KEYSEND_OPTIONAL) { + if !features.supports_keysend() { return Err(LightningError::ValidationError(format!( "destination node does not support keysend {}", payment_flow.destination, diff --git a/sim-lib/src/lnd.rs b/sim-lib/src/lnd.rs index 5817e20c..73b2c66b 100644 --- a/sim-lib/src/lnd.rs +++ b/sim-lib/src/lnd.rs @@ -1,17 +1,20 @@ use std::{collections::HashMap, str::FromStr}; -use crate::{LightningError, LightningNode, NodeInfo, PaymentOutcome, PaymentResult}; +use crate::{ + LightningError, LightningNode, LndConnection, NodeInfo, PaymentOutcome, PaymentResult, +}; use async_trait::async_trait; use bitcoin::hashes::{sha256, Hash}; use bitcoin::secp256k1::PublicKey; +use lightning::ln::features::NodeFeatures; use lightning::ln::{PaymentHash, PaymentPreimage}; -use std::collections::HashSet; use tonic_lnd::lnrpc::{payment::PaymentStatus, GetInfoRequest, GetInfoResponse}; use tonic_lnd::lnrpc::{NodeInfoRequest, PaymentFailureReason}; use tonic_lnd::routerrpc::TrackPaymentRequest; use tonic_lnd::{routerrpc::SendPaymentRequest, Client}; use triggered::Listener; +const KEYSEND_OPTIONAL: u32 = 55; const KEYSEND_KEY: u64 = 5482373484; const SEND_PAYMENT_TIMEOUT_SECS: i32 = 300; @@ -22,12 +25,8 @@ pub struct LndNode { } impl LndNode { - pub async fn new( - address: String, - macaroon: String, - cert: String, - ) -> Result { - let mut client = tonic_lnd::connect(address, cert, macaroon) + pub async fn new(conn_data: LndConnection) -> Result { + let mut client = tonic_lnd::connect(conn_data.address, conn_data.cert, conn_data.macaroon) .await .map_err(|err| LightningError::ConnectionError(err.to_string()))?; @@ -160,11 +159,10 @@ impl LightningNode for LndNode { } } - async fn get_node_announcement(&self, node: PublicKey) -> Result, LightningError> { - let mut client = self.client.clone(); - let lightning_client = client.lightning(); - - let node_info = lightning_client + async fn get_node_features(&mut self, node: PublicKey) -> Result { + let node_info = self + .client + .lightning() .get_node_info(NodeInfoRequest { pub_key: node.to_string(), include_channels: false, @@ -173,11 +171,18 @@ impl LightningNode for LndNode { .map_err(|err| LightningError::GetNodeInfoError(err.to_string()))? .into_inner(); + let mut nf = NodeFeatures::empty(); + if let Some(node_info) = node_info.node { - Ok(node_info.features.into_keys().collect()) + // FIXME: We only care about the keysend feature now, but we should parse the whole feature vector + // into LDK's feature bitvector and properly construct NodeFeatures. + if node_info.features.contains_key(&KEYSEND_OPTIONAL) { + nf.set_keysend_optional() + } + Ok(nf) } else { Err(LightningError::GetNodeInfoError( - "node not found".to_string(), + "Node not found".to_string(), )) } }