diff --git a/sim-lib/src/cln.rs b/sim-lib/src/cln.rs index 04933dc2..f0befbda 100644 --- a/sim-lib/src/cln.rs +++ b/sim-lib/src/cln.rs @@ -21,49 +21,60 @@ pub struct ClnNode { 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 + .identity(Identity::from_pem( + reader(&connection.client_cert).await.map_err(|_| { + LightningError::ConnectionError("Cannot loads client certificate".to_string()) + })?, + reader(&connection.client_key).await.map_err(|_| { + LightningError::ConnectionError("Cannot loads client key".to_string()) + })?, + )) + .ca_certificate(Certificate::from_pem( + reader(&connection.ca_cert).await.map_err(|_| { + LightningError::ConnectionError("Cannot loads CA certificate".to_string()) + })?, + )); + + let mut client = NodeClient::new( + 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 GetinfoResponse { + id, + alias, + our_features, + .. + } = client .getinfo(GetinfoRequest {}) .await .map_err(|err| LightningError::GetInfoError(err.to_string()))? .into_inner(); + //FIXME: our_features is returning None, but it should not :S + let features = if let Some(features) = our_features { + NodeFeatures::from_le_bytes(features.node) + } else { + NodeFeatures::empty() + }; + Ok(Self { client, info: NodeInfo { pubkey: PublicKey::from_slice(&id) .map_err(|err| LightningError::GetInfoError(err.to_string()))?, - features: vec![], + features, alias, }, }) diff --git a/sim-lib/src/lib.rs b/sim-lib/src/lib.rs index 805221c9..95a2cf64 100644 --- a/sim-lib/src/lib.rs +++ b/sim-lib/src/lib.rs @@ -101,7 +101,7 @@ pub enum LightningError { pub struct NodeInfo { pub pubkey: PublicKey, pub alias: String, - pub features: Vec, + pub features: NodeFeatures, } /// LightningNode represents the functionality that is required to execute events on a lightning node. diff --git a/sim-lib/src/lnd.rs b/sim-lib/src/lnd.rs index 73b2c66b..230c2ac0 100644 --- a/sim-lib/src/lnd.rs +++ b/sim-lib/src/lnd.rs @@ -1,3 +1,4 @@ +use std::collections::HashSet; use std::{collections::HashMap, str::FromStr}; use crate::{ @@ -14,7 +15,6 @@ 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; @@ -24,6 +24,24 @@ pub struct LndNode { info: NodeInfo, } +// TODO: We could even generalize this to parse any type of Features +/// Parses the node features from the format returned by LND gRPC to LDK NodeFeatures +fn parse_node_features(features: HashSet) -> NodeFeatures { + let mut flags = vec![0; 256]; + + for f in features.into_iter() { + let byte_offset = (f / 8) as usize; + let mask = 1 << (f - 8 * byte_offset as u32); + if flags.len() <= byte_offset { + flags.resize(byte_offset + 1, 0u8); + } + + flags[byte_offset] |= mask + } + + NodeFeatures::from_le_bytes(flags) +} + impl LndNode { pub async fn new(conn_data: LndConnection) -> Result { let mut client = tonic_lnd::connect(conn_data.address, conn_data.cert, conn_data.macaroon) @@ -47,7 +65,7 @@ impl LndNode { info: NodeInfo { pubkey: PublicKey::from_str(&identity_pubkey) .map_err(|err| LightningError::GetInfoError(err.to_string()))?, - features: features.keys().copied().collect(), + features: parse_node_features(features.keys().cloned().collect()), alias, }, }) @@ -171,15 +189,10 @@ 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 { - // 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) + Ok(parse_node_features( + node_info.features.keys().cloned().collect(), + )) } else { Err(LightningError::GetNodeInfoError( "Node not found".to_string(),