Skip to content

Commit

Permalink
sim-all: allows aliases to be used as source/destination of activities
Browse files Browse the repository at this point in the history
The approach followed for this is pretty straight-forward: instead of loading
`PublicKey`s directly to our `ActivityDefinition` data structure, we create an
intermediary class `ActivityParser` that accepts `NodeId`.

However, we internally identify activities using `PublicKey`s, so we do an additional mapping
step once the data is loaded to go from `ActivityParser` to `ActivityDefinition`, hence,
once data is passed to the simulator it is always identified by `PublicKey`.

For activity destinations, we also need to make sure that if an alias has been provided we
do control that node, otherwise the Alias->PublicKey mapping cannot be done, in which case we fail
with a `ValidationError`.
  • Loading branch information
sr-gi committed Oct 5, 2023
1 parent ff1ddd5 commit b502082
Show file tree
Hide file tree
Showing 2 changed files with 107 additions and 12 deletions.
43 changes: 40 additions & 3 deletions sim-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ use tokio::sync::Mutex;
use clap::Parser;
use log::LevelFilter;
use sim_lib::{
cln::ClnNode, lnd::LndNode, Config, LightningError, LightningNode, NodeConnection, Simulation,
cln::ClnNode, lnd::LndNode, ActivityDefinition, Config, LightningError, LightningNode,
NodeConnection, NodeId, Simulation,
};
use simple_logger::SimpleLogger;

Expand Down Expand Up @@ -37,7 +38,10 @@ async fn main() -> anyhow::Result<()> {
.unwrap();

let config_str = std::fs::read_to_string(cli.config)?;
let Config { nodes, activity } = serde_json::from_str(&config_str)?;
let Config {
nodes,
mut activity,
} = serde_json::from_str(&config_str)?;

let mut clients: HashMap<PublicKey, Arc<Mutex<dyn LightningNode + Send>>> = HashMap::new();
let mut alias_node_map = HashMap::new();
Expand Down Expand Up @@ -77,7 +81,40 @@ async fn main() -> anyhow::Result<()> {
clients.insert(node_info.pubkey, node);
}

let sim = Simulation::new(clients, activity, cli.total_time, cli.print_batch_size);
let mut validated_activities = vec![];
// Make all the activities identifiable by PK internally
for act in activity.iter_mut() {
// 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
if let NodeId::Alias(a) = &act.source {
if let Some(pk) = alias_node_map.get(a) {
act.source = NodeId::PublicKey(*pk);
} else {
anyhow::bail!(LightningError::ValidationError(format!(
"activity source alias {} not found in nodes.",
act.source
)));
}
}
if let NodeId::Alias(a) = &act.destination {
if let Some(pk) = alias_node_map.get(a) {
act.destination = NodeId::PublicKey(*pk);
} else {
anyhow::bail!(LightningError::ValidationError(format!(
"unknown activity destination: {}.",
act.destination
)));
}
}
validated_activities.push(ActivityDefinition::try_from(act)?);
}

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: 67 additions & 9 deletions sim-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,27 +62,85 @@ impl NodeId {
}
Ok(())
}

pub fn get_pk(&self) -> Result<&PublicKey, String> {
if let NodeId::PublicKey(pk) = self {
Ok(pk)
} else {
Err("NodeId is not a PublicKey".to_string())
}
}
}

impl std::fmt::Display for NodeId {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}",
match self {
NodeId::PublicKey(pk) => pk.to_string(),
NodeId::Alias(a) => a.to_owned(),
}
)
}
}

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Config {
pub nodes: Vec<NodeConnection>,
pub activity: Option<Vec<ActivityDefinition>>,
#[serde(default)]
pub activity: Vec<ActivityParser>,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
/// Data structure used to parse information from the simulation file. It allows source and destination to be
/// [NodeId], which enables the use of public keys and aliases in the simulation description.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActivityParser {
// The source of the payment.
#[serde(with = "serializers::serde_node_id")]
pub source: NodeId,
// The destination of the payment.
#[serde(with = "serializers::serde_node_id")]
pub destination: NodeId,
// The interval of the event, as in every how many seconds the payment is performed.
pub interval_secs: u16,
// The amount of m_sat to used in this payment.
pub amount_msat: u64,
}

/// Data structure used internally by the simulator. Both source and destination are represented as [PublicKey] here.
/// This is constructed during activity validation and passed along to the [Simulation].
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ActivityDefinition {
// The source of the payment.
pub source: PublicKey,
// The destination of the payment.
pub destination: PublicKey,
// The interval of the event, as in every how many seconds the payment is performed.
#[serde(alias = "interval_secs")]
pub interval: u16,
pub interval_secs: u16,
// The amount of m_sat to used in this payment.
pub amount_msat: u64,
}

impl TryFrom<&mut ActivityParser> for ActivityDefinition {
type Error = LightningError;

fn try_from(a: &mut ActivityParser) -> Result<Self, Self::Error> {
let source = *a.source.get_pk().map_err(Self::Error::ValidationError)?;
let destination = *a
.destination
.get_pk()
.map_err(Self::Error::ValidationError)?;

Ok(Self {
source,
destination,
interval_secs: a.interval_secs,
amount_msat: a.amount_msat,
})
}
}

#[derive(Debug, Error)]
pub enum SimulationError {
#[error("Lightning Error: {0:?}")]
Expand Down Expand Up @@ -290,14 +348,14 @@ const DEFAULT_PRINT_BATCH_SIZE: u32 = 500;
impl Simulation {
pub fn new(
nodes: HashMap<PublicKey, Arc<Mutex<dyn LightningNode + Send>>>,
activity: Option<Vec<ActivityDefinition>>,
activity: Vec<ActivityDefinition>,
total_time: Option<u32>,
print_batch_size: Option<u32>,
) -> Self {
let (shutdown_trigger, shutdown_listener) = triggered::trigger();
Self {
nodes,
activity: activity.unwrap_or_default(),
activity,
shutdown_trigger,
shutdown_listener,
total_time: total_time.map(|x| Duration::from_secs(x as u64)),
Expand Down Expand Up @@ -574,7 +632,7 @@ impl Simulation {
for description in self.activity.iter() {
let sender_chan = producer_channels.get(&description.source).unwrap();
tasks.spawn(produce_events(
*description,
description.clone(),
sender_chan.clone(),
self.shutdown_trigger.clone(),
self.shutdown_listener.clone(),
Expand Down Expand Up @@ -706,12 +764,12 @@ async fn produce_events(
listener: Listener,
) {
let e: SimulationEvent = SimulationEvent::SendPayment(act.destination, act.amount_msat);
let interval = time::Duration::from_secs(act.interval as u64);
let interval = time::Duration::from_secs(act.interval_secs as u64);

log::debug!(
"Started producer for {} every {}s: {} -> {}.",
act.amount_msat,
act.interval,
act.interval_secs,
act.source,
act.destination
);
Expand Down

0 comments on commit b502082

Please sign in to comment.