Skip to content

Commit a3f4495

Browse files
add optional name field to activities for easier logging and debugging
1 parent dda35c1 commit a3f4495

File tree

6 files changed

+71
-3
lines changed

6 files changed

+71
-3
lines changed

Cargo.lock

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

sim-cli/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ futures = "0.3.30"
2727
console-subscriber = { version = "0.4.0", optional = true}
2828
tokio-util = { version = "0.7.13", features = ["rt"] }
2929
openssl = { version = "0.10", features = ["vendored"] }
30+
regex = "1.11.1"
3031

3132
[features]
3233
dev = ["console-subscriber"]

sim-cli/src/parsing.rs

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@ use simln_lib::{
88
ActivityDefinition, Amount, Interval, LightningError, LightningNode, NodeId, NodeInfo,
99
Simulation, SimulationCfg, WriteResults,
1010
};
11-
use std::collections::HashMap;
11+
use std::collections::{HashMap, HashSet};
1212
use std::fs;
1313
use std::ops::AsyncFn;
1414
use std::path::PathBuf;
1515
use std::sync::Arc;
1616
use tokio::sync::Mutex;
1717
use tokio_util::task::TaskTracker;
18+
use regex::Regex;
1819

1920
/// The default directory where the simulation files are stored and where the results will be written to.
2021
pub const DEFAULT_DATA_DIR: &str = ".";
@@ -100,6 +101,9 @@ enum NodeConnection {
100101
/// [NodeId], which enables the use of public keys and aliases in the simulation description.
101102
#[derive(Debug, Clone, Serialize, Deserialize)]
102103
struct ActivityParser {
104+
/// Optional identifier for this activity.
105+
#[serde(default)]
106+
pub name: Option<String>,
103107
/// The source of the payment.
104108
#[serde(with = "serializers::serde_node_id")]
105109
pub source: NodeId,
@@ -259,9 +263,55 @@ async fn validate_activities(
259263
get_node_info: impl AsyncFn(&PublicKey) -> Result<NodeInfo, LightningError>,
260264
) -> Result<Vec<ActivityDefinition>, LightningError> {
261265
let mut validated_activities = vec![];
266+
let mut activity_names = HashSet::new();
262267

263268
// Make all the activities identifiable by PK internally
264-
for act in activity.into_iter() {
269+
for (index, act) in activity.into_iter().enumerate() {
270+
// Generate a default name if one is not provided
271+
let name = match &act.name {
272+
Some(name) => {
273+
// Disallow empty names
274+
if name.is_empty() {
275+
return Err(LightningError::ValidationError(
276+
"activity name cannot be empty".to_string()
277+
));
278+
}
279+
280+
// Disallow users from using the reserved "Activity-x" format
281+
let reserved_pattern = Regex::new(r"^Activity-\d+$").unwrap();
282+
if reserved_pattern.is_match(name) {
283+
return Err(LightningError::ValidationError(format!(
284+
"'{}' uses a reserved name format. 'Activity-{{number}}' is reserved for system use.",
285+
name
286+
)));
287+
}
288+
289+
// Check for duplicate names
290+
if !activity_names.insert(name.clone()) {
291+
return Err(LightningError::ValidationError(format!(
292+
"duplicate activity name: {}",
293+
name
294+
)));
295+
}
296+
name.clone()
297+
},
298+
None => {
299+
// Generate a unique system name
300+
let mut counter = index;
301+
let mut unique_name;
302+
303+
loop {
304+
unique_name = format!("Activity-{}", counter);
305+
if activity_names.insert(unique_name.clone()) {
306+
break;
307+
}
308+
counter += 1;
309+
}
310+
311+
unique_name
312+
},
313+
};
314+
265315
// We can only map aliases to nodes we control, so if either the source or destination alias
266316
// is not in alias_node_map, we fail
267317
let source = if let Some(source) = match &act.source {
@@ -297,6 +347,7 @@ async fn validate_activities(
297347
};
298348

299349
validated_activities.push(ActivityDefinition {
350+
name: Some(name),
300351
source,
301352
destination,
302353
interval_secs: act.interval_secs,

simln-lib/src/defined_activity.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ use tokio::time::Duration;
77

88
#[derive(Clone)]
99
pub struct DefinedPaymentActivity {
10+
#[allow(dead_code)]
11+
name: Option<String>,
1012
destination: NodeInfo,
1113
start: Option<Duration>,
1214
count: Option<u64>,
@@ -16,13 +18,15 @@ pub struct DefinedPaymentActivity {
1618

1719
impl DefinedPaymentActivity {
1820
pub fn new(
21+
name: Option<String>,
1922
destination: NodeInfo,
2023
start: Option<Duration>,
2124
count: Option<u64>,
2225
wait: ValueOrRange<u16>,
2326
amount: ValueOrRange<u64>,
2427
) -> Self {
2528
DefinedPaymentActivity {
29+
name,
2630
destination,
2731
start,
2832
count,
@@ -86,13 +90,15 @@ mod tests {
8690

8791
#[test]
8892
fn test_defined_activity_generator() {
93+
let name: String = "test_generator".to_string();
8994
let node = create_nodes(1, 100000);
9095
let node = &node.first().unwrap().0;
9196

9297
let source = get_random_keypair();
9398
let payment_amt = 50;
9499

95100
let generator = DefinedPaymentActivity::new(
101+
Option::from(name),
96102
node.clone(),
97103
None,
98104
None,

simln-lib/src/lib.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ pub type Interval = ValueOrRange<u16>;
164164
/// This is constructed during activity validation and passed along to the [Simulation].
165165
#[derive(Debug, Clone)]
166166
pub struct ActivityDefinition {
167+
/// Optional identifier for this activity.
168+
pub name: Option<String>,
167169
/// The source of the payment.
168170
pub source: NodeInfo,
169171
/// The destination of the payment.
@@ -500,6 +502,7 @@ pub struct WriteResults {
500502
/// ExecutorKit contains the components required to spin up an activity configured by the user, to be used to
501503
/// spin up the appropriate producers and consumers for the activity.
502504
struct ExecutorKit {
505+
name: Option<String>,
503506
source_info: NodeInfo,
504507
/// We use an arc mutex here because some implementations of the trait will be very expensive to clone.
505508
/// See [NetworkGraphView] for details.
@@ -806,6 +809,7 @@ impl Simulation {
806809
if !self.activity.is_empty() {
807810
for description in self.activity.iter() {
808811
let activity_generator = DefinedPaymentActivity::new(
812+
description.name.clone(),
809813
description.destination.clone(),
810814
description
811815
.start_secs
@@ -816,6 +820,7 @@ impl Simulation {
816820
);
817821

818822
generators.push(ExecutorKit {
823+
name: description.name.clone(),
819824
source_info: description.source.clone(),
820825
// Defined activities have very simple generators, so the traits required are implemented on
821826
// a single struct which we just cheaply clone.
@@ -874,6 +879,7 @@ impl Simulation {
874879

875880
for (node_info, capacity) in active_nodes.values() {
876881
generators.push(ExecutorKit {
882+
name: None,
877883
source_info: node_info.clone(),
878884
network_generator: network_generator.clone(),
879885
payment_generator: Box::new(
@@ -958,9 +964,11 @@ impl Simulation {
958964
let pe_sender = sender.clone();
959965
tasks.spawn(async move {
960966
let source = executor.source_info.clone();
967+
let name = executor.name.as_deref().unwrap();
961968

962969
log::info!(
963-
"Starting activity producer for {}: {}.",
970+
"[{}] Starting activity producer for {}: {}.",
971+
name,
964972
source,
965973
executor.payment_generator
966974
);

simln-lib/src/test_utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ pub fn create_activity(
212212
amount_msat: u64,
213213
) -> ActivityDefinition {
214214
ActivityDefinition {
215+
name: None,
215216
source,
216217
destination,
217218
start_secs: None,

0 commit comments

Comments
 (0)