Skip to content
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

Allows using aliases within the config file #103

Merged
merged 6 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 100 additions & 26 deletions sim-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ use tokio::sync::Mutex;

use clap::Parser;
use log::LevelFilter;
use sim_lib::{cln::ClnNode, lnd::LndNode, Config, LightningNode, NodeConnection, Simulation};
carlaKC marked this conversation as resolved.
Show resolved Hide resolved
use sim_lib::{
cln::ClnNode, lnd::LndNode, ActivityDefinition, Config, LightningError, LightningNode,
NodeConnection, NodeId, Simulation,
};
use simple_logger::SimpleLogger;

#[derive(Parser)]
Expand Down Expand Up @@ -38,38 +41,109 @@ async fn main() -> anyhow::Result<()> {
let Config { nodes, activity } = serde_json::from_str(&config_str)?;

let mut clients: HashMap<PublicKey, Arc<Mutex<dyn LightningNode + Send>>> = HashMap::new();
let mut pk_node_map = HashMap::new();
let mut alias_node_map = HashMap::new();

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
);

clients.insert(node_id, Arc::new(Mutex::new(lnd)));
}
NodeConnection::CLN(c) => {
let node_id = c.id;
let cln = ClnNode::new(c).await?;
// TODO: Feels like there should be a better way of doing this without having to Arc<Mutex<T>>> it at this time.
// Box sort of works, but we won't know the size of the dyn LightningNode at compile time so the compiler will
// scream at us when trying to create the Arc<Mutex>> later on while adding the node to the clients map
let node: Arc<Mutex<dyn LightningNode + Send>> = match connection {
NodeConnection::LND(c) => Arc::new(Mutex::new(LndNode::new(c).await?)),
NodeConnection::CLN(c) => Arc::new(Mutex::new(ClnNode::new(c).await?)),
};

log::info!(
"Connected to {} - Node ID: {}.",
cln.get_info().alias,
cln.get_info().pubkey
);
let node_info = node.lock().await.get_info().clone();
okjodom marked this conversation as resolved.
Show resolved Hide resolved

clients.insert(node_id, Arc::new(Mutex::new(cln)));
}
log::info!(
"Connected to {} - Node ID: {}.",
node_info.alias,
node_info.pubkey
);

if clients.contains_key(&node_info.pubkey) {
anyhow::bail!(LightningError::ValidationError(format!(
"duplicated node: {}.",
node_info.pubkey
)));
}

if alias_node_map.contains_key(&node_info.alias) {
anyhow::bail!(LightningError::ValidationError(format!(
"duplicated node: {}.",
node_info.alias
)));
}

clients.insert(node_info.pubkey, node);
pk_node_map.insert(node_info.pubkey, node_info.clone());
alias_node_map.insert(node_info.alias.clone(), node_info);
}

let mut validated_activities = vec![];
// Make all the activities identifiable by PK internally
for act in activity.into_iter() {
// We can only map aliases to nodes we control, so if either the source or destination alias
// is not in alias_node_map, we fail
let source = if let Some(source) = match &act.source {
NodeId::PublicKey(pk) => pk_node_map.get(pk),
NodeId::Alias(a) => alias_node_map.get(a),
} {
source.clone()
} else {
anyhow::bail!(LightningError::ValidationError(format!(
"activity source {} not found in nodes.",
act.source
)));
};

let destination = match &act.destination {
NodeId::Alias(a) => {
if let Some(info) = alias_node_map.get(a) {
info.clone()
} else {
anyhow::bail!(LightningError::ValidationError(format!(
"unknown activity destination: {}.",
act.destination
)));
}
}
NodeId::PublicKey(pk) => {
if let Some(info) = pk_node_map.get(pk) {
info.clone()
} else {
clients
.get(&source.pubkey)
.unwrap()
.lock()
.await
.get_node_info(pk)
.await
.map_err(|e| {
log::debug!("{}", e);
LightningError::ValidationError(format!(
"Destination node unknown or invalid: {}.",
pk,
))
})?
}
}
};

validated_activities.push(ActivityDefinition {
source,
destination,
interval_secs: act.interval_secs,
amount_msat: act.amount_msat,
});
}

let sim = Simulation::new(clients, activity, cli.total_time, cli.print_batch_size);
let sim = Simulation::new(
clients,
validated_activities,
cli.total_time,
cli.print_batch_size,
);
let sim2 = sim.clone();

ctrlc::set_handler(move || {
Expand Down
76 changes: 49 additions & 27 deletions sim-lib/src/cln.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,36 @@ use bitcoin::secp256k1::PublicKey;
use bitcoin::Network;
use cln_grpc::pb::{
listpays_pays::ListpaysPaysStatus, node_client::NodeClient, Amount, GetinfoRequest,
GetinfoResponse, KeysendRequest, KeysendResponse, ListchannelsRequest, ListnodesRequest,
ListpaysRequest, ListpaysResponse,
KeysendRequest, KeysendResponse, ListchannelsRequest, ListnodesRequest, ListpaysRequest,
ListpaysResponse,
};
use lightning::ln::features::NodeFeatures;
use lightning::ln::PaymentHash;

use serde::{Deserialize, Serialize};
use tokio::fs::File;
use tokio::io::{AsyncReadExt, Error};
use tokio::time::{self, Duration};
use tonic::transport::{Certificate, Channel, ClientTlsConfig, Identity};
use triggered::Listener;

use crate::{
ClnConnection, LightningError, LightningNode, NodeInfo, PaymentOutcome, PaymentResult,
serializers, LightningError, LightningNode, NodeId, NodeInfo, PaymentOutcome, PaymentResult,
};

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ClnConnection {
#[serde(with = "serializers::serde_node_id")]
pub id: NodeId,
pub address: String,
#[serde(deserialize_with = "serializers::deserialize_path")]
pub ca_cert: String,
#[serde(deserialize_with = "serializers::deserialize_path")]
pub client_cert: String,
#[serde(deserialize_with = "serializers::deserialize_path")]
pub client_key: String,
}

pub struct ClnNode {
pub client: NodeClient<Channel>,
info: NodeInfo,
Expand Down Expand Up @@ -56,16 +70,22 @@ impl ClnNode {
})?,
);

let GetinfoResponse {
id,
alias,
our_features,
..
} = client
let (id, mut alias, our_features) = client
.getinfo(GetinfoRequest {})
.await
.map_err(|err| LightningError::GetInfoError(err.to_string()))?
.into_inner();
.map(|r| {
let inner = r.into_inner();
(
inner.id,
inner.alias.unwrap_or_default(),
inner.our_features,
)
})
.map_err(|err| LightningError::GetInfoError(err.to_string()))?;

let pubkey = PublicKey::from_slice(&id)
.map_err(|err| LightningError::GetInfoError(err.to_string()))?;
connection.id.validate(&pubkey, &mut alias)?;

//FIXME: our_features is returning None, but it should not :S
let features = if let Some(features) = our_features {
Expand All @@ -77,10 +97,9 @@ impl ClnNode {
Ok(Self {
client,
info: NodeInfo {
pubkey: PublicKey::from_slice(&id)
.map_err(|err| LightningError::GetInfoError(err.to_string()))?,
pubkey,
features,
alias: alias.unwrap_or("".to_string()),
alias,
},
})
}
Expand Down Expand Up @@ -211,28 +230,31 @@ impl LightningNode for ClnNode {
}
}

async fn get_node_features(&mut self, node: PublicKey) -> Result<NodeFeatures, LightningError> {
let node_id = node.serialize().to_vec();
let nodes: Vec<cln_grpc::pb::ListnodesNodes> = self
async fn get_node_info(&mut self, node_id: &PublicKey) -> Result<NodeInfo, LightningError> {
let mut nodes: Vec<cln_grpc::pb::ListnodesNodes> = self
.client
.list_nodes(ListnodesRequest {
id: Some(node_id.clone()),
id: Some(node_id.serialize().to_vec()),
})
.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)
}))
if let Some(node) = nodes.pop() {
Ok(NodeInfo {
pubkey: *node_id,
alias: node.alias.unwrap_or(String::new()),
features: node
.features
.clone()
.map_or(NodeFeatures::empty(), |mut f| {
// We need to reverse this given it has the CLN wire encoding which is BE
sr-gi marked this conversation as resolved.
Show resolved Hide resolved
f.reverse();
NodeFeatures::from_le_bytes(f)
}),
})
} else {
Err(LightningError::GetNodeInfoError(
"Node not found".to_string(),
Expand Down
Loading
Loading