Skip to content

Commit f1a3275

Browse files
committed
sim-lib: Allows amount and interval ranges in manual activity definition
Amounts and intervals in activities can now be fixed values (e.g. 1000) or ranges (e.g. [500, 1000]). If a range is provided, the value will be sampled uniformly at random from it.
1 parent c6ee6ad commit f1a3275

File tree

3 files changed

+98
-16
lines changed

3 files changed

+98
-16
lines changed

sim-lib/src/defined_activity.rs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use crate::{DestinationGenerator, NodeInfo, PaymentGenerationError, PaymentGenerator};
1+
use crate::{
2+
DestinationGenerator, NodeInfo, PaymentGenerationError, PaymentGenerator, ValueOrRange,
3+
};
24
use std::fmt;
35
use tokio::time::Duration;
46

@@ -7,17 +9,17 @@ pub struct DefinedPaymentActivity {
79
destination: NodeInfo,
810
start: Duration,
911
count: Option<u64>,
10-
wait: Duration,
11-
amount: u64,
12+
wait: ValueOrRange<u16>,
13+
amount: ValueOrRange<u64>,
1214
}
1315

1416
impl DefinedPaymentActivity {
1517
pub fn new(
1618
destination: NodeInfo,
1719
start: Duration,
1820
count: Option<u64>,
19-
wait: Duration,
20-
amount: u64,
21+
wait: ValueOrRange<u16>,
22+
amount: ValueOrRange<u64>,
2123
) -> Self {
2224
DefinedPaymentActivity {
2325
destination,
@@ -33,7 +35,7 @@ impl fmt::Display for DefinedPaymentActivity {
3335
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3436
write!(
3537
f,
36-
"static payment of {} to {} every {:?}",
38+
"static payment of {} to {} every {}s",
3739
self.amount, self.destination, self.wait
3840
)
3941
}
@@ -55,7 +57,7 @@ impl PaymentGenerator for DefinedPaymentActivity {
5557
}
5658

5759
fn next_payment_wait(&self) -> Duration {
58-
self.wait
60+
Duration::from_secs(self.wait.value() as u64)
5961
}
6062

6163
fn payment_amount(
@@ -67,17 +69,17 @@ impl PaymentGenerator for DefinedPaymentActivity {
6769
"destination amount must not be set for defined activity generator".to_string(),
6870
))
6971
} else {
70-
Ok(self.amount)
72+
Ok(self.amount.value())
7173
}
7274
}
7375
}
7476

7577
#[cfg(test)]
7678
mod tests {
7779
use super::DefinedPaymentActivity;
80+
use super::*;
7881
use crate::test_utils::{create_nodes, get_random_keypair};
7982
use crate::{DestinationGenerator, PaymentGenerationError, PaymentGenerator};
80-
use std::time::Duration;
8183

8284
#[test]
8385
fn test_defined_activity_generator() {
@@ -91,8 +93,8 @@ mod tests {
9193
node.clone(),
9294
Duration::from_secs(0),
9395
None,
94-
Duration::from_secs(60),
95-
payment_amt,
96+
crate::ValueOrRange::Value(60),
97+
crate::ValueOrRange::Value(payment_amt),
9698
);
9799

98100
let (dest, dest_capacity) = generator.choose_destination(source.1);

sim-lib/src/lib.rs

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use bitcoin::Network;
44
use csv::WriterBuilder;
55
use lightning::ln::features::NodeFeatures;
66
use lightning::ln::PaymentHash;
7+
use rand::Rng;
78
use random_activity::RandomActivityError;
89
use serde::{Deserialize, Serialize};
910
use std::collections::HashSet;
@@ -129,6 +130,47 @@ pub struct SimParams {
129130
pub activity: Vec<ActivityParser>,
130131
}
131132

133+
/// Either a value or a range parsed from the simulation file.
134+
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
135+
#[serde(untagged)]
136+
pub enum ValueOrRange<T> {
137+
Value(T),
138+
Range(T, T),
139+
}
140+
141+
impl<T> ValueOrRange<T>
142+
where
143+
T: std::cmp::PartialOrd + rand_distr::uniform::SampleUniform + Copy,
144+
{
145+
/// Get the enclosed value. If value is defined as a range, sample from it uniformly at random.
146+
pub fn value(&self) -> T {
147+
match self {
148+
ValueOrRange::Value(x) => *x,
149+
ValueOrRange::Range(x, y) => {
150+
let mut rng = rand::thread_rng();
151+
rng.gen_range(*x..*y)
152+
},
153+
}
154+
}
155+
}
156+
157+
impl<T> Display for ValueOrRange<T>
158+
where
159+
T: Display,
160+
{
161+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
162+
match self {
163+
ValueOrRange::Value(x) => write!(f, "{x}"),
164+
ValueOrRange::Range(x, y) => write!(f, "({x}-{y})"),
165+
}
166+
}
167+
}
168+
169+
/// The payment amount in msat. Either a value or a range.
170+
type Amount = ValueOrRange<u64>;
171+
/// The interval of seconds between payments. Either a value or a range.
172+
type Interval = ValueOrRange<u16>;
173+
132174
/// Data structure used to parse information from the simulation file. It allows source and destination to be
133175
/// [NodeId], which enables the use of public keys and aliases in the simulation description.
134176
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -146,9 +188,11 @@ pub struct ActivityParser {
146188
#[serde(default)]
147189
pub count: Option<u64>,
148190
/// The interval of the event, as in every how many seconds the payment is performed.
149-
pub interval_secs: u16,
191+
#[serde(with = "serializers::serde_value_or_range")]
192+
pub interval_secs: Interval,
150193
/// The amount of m_sat to used in this payment.
151-
pub amount_msat: u64,
194+
#[serde(with = "serializers::serde_value_or_range")]
195+
pub amount_msat: Amount,
152196
}
153197

154198
/// Data structure used internally by the simulator. Both source and destination are represented as [PublicKey] here.
@@ -164,9 +208,9 @@ pub struct ActivityDefinition {
164208
/// The number of payments to send over the course of the simulation.
165209
pub count: Option<u64>,
166210
/// The interval of the event, as in every how many seconds the payment is performed.
167-
pub interval_secs: u16,
211+
pub interval_secs: Interval,
168212
/// The amount of m_sat to used in this payment.
169-
pub amount_msat: u64,
213+
pub amount_msat: Amount,
170214
}
171215

172216
#[derive(Debug, Error)]
@@ -731,7 +775,7 @@ impl Simulation {
731775
description.destination.clone(),
732776
Duration::from_secs(description.start_secs.into()),
733777
description.count,
734-
Duration::from_secs(description.interval_secs.into()),
778+
description.interval_secs,
735779
description.amount_msat,
736780
);
737781

sim-lib/src/serializers.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,42 @@ pub mod serde_node_id {
4545
}
4646
}
4747

48+
pub mod serde_value_or_range {
49+
use super::*;
50+
use serde::de::Error;
51+
52+
use crate::ValueOrRange;
53+
54+
pub fn serialize<S, T>(x: &ValueOrRange<T>, serializer: S) -> Result<S::Ok, S::Error>
55+
where
56+
S: serde::Serializer,
57+
T: std::fmt::Display,
58+
{
59+
serializer.serialize_str(&match x {
60+
ValueOrRange::Value(p) => p.to_string(),
61+
ValueOrRange::Range(x, y) => format!("[{}, {}]", x, y),
62+
})
63+
}
64+
65+
pub fn deserialize<'de, D, T>(deserializer: D) -> Result<ValueOrRange<T>, D::Error>
66+
where
67+
D: serde::Deserializer<'de>,
68+
T: serde::Deserialize<'de> + std::cmp::PartialOrd + std::fmt::Display + Copy,
69+
{
70+
let a = ValueOrRange::deserialize(deserializer)?;
71+
if let ValueOrRange::Range(x, y) = a {
72+
if x >= y {
73+
return Err(D::Error::custom(format!(
74+
"Cannot parse range. Ranges must be strictly increasing (i.e. [x, y] with x > y). Received [{}, {}]",
75+
x, y
76+
)));
77+
}
78+
}
79+
80+
Ok(a)
81+
}
82+
}
83+
4884
pub fn deserialize_path<'de, D>(deserializer: D) -> Result<String, D::Error>
4985
where
5086
D: serde::Deserializer<'de>,

0 commit comments

Comments
 (0)