Skip to content

Commit ec8670c

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 3001a22 commit ec8670c

File tree

2 files changed

+101
-7
lines changed

2 files changed

+101
-7
lines changed

sim-lib/src/lib.rs

+65-7
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 serde::{Deserialize, Serialize};
89
use std::collections::HashSet;
910
use std::fmt::{Display, Formatter};
@@ -86,6 +87,52 @@ pub struct SimParams {
8687
pub activity: Vec<ActivityParser>,
8788
}
8889

90+
/// Either a value or a range parsed from the simulation file.
91+
#[derive(Debug, Clone, Serialize, Deserialize)]
92+
#[serde(untagged)]
93+
pub enum ValueOrRange<T> {
94+
Value(T),
95+
Range(T, T),
96+
}
97+
98+
impl<T> ValueOrRange<T>
99+
where
100+
T: std::cmp::PartialOrd + rand_distr::uniform::SampleUniform + Copy,
101+
{
102+
/// Get the enclosed value. If value is defined aa a range, sample from it uniformly at random.
103+
pub fn value(&self) -> T {
104+
match self {
105+
ValueOrRange::Value(x) => *x,
106+
ValueOrRange::Range(x, y) => {
107+
let mut rng = rand::thread_rng();
108+
rng.gen_range(*x..*y)
109+
}
110+
}
111+
}
112+
113+
/// Whether this is a range or not
114+
pub fn is_range(&self) -> bool {
115+
matches!(self, ValueOrRange::Range(..))
116+
}
117+
}
118+
119+
impl<T> Display for ValueOrRange<T>
120+
where
121+
T: Display,
122+
{
123+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
124+
match self {
125+
ValueOrRange::Value(x) => write!(f, "{x}"),
126+
ValueOrRange::Range(x, y) => write!(f, "({x}-{y})"),
127+
}
128+
}
129+
}
130+
131+
/// The payment amount in msat. Either a value or a range.
132+
type Amount = ValueOrRange<u64>;
133+
/// The interval of seconds between payments. Either a value or a range.
134+
type Interval = ValueOrRange<u16>;
135+
89136
/// Data structure used to parse information from the simulation file. It allows source and destination to be
90137
/// [NodeId], which enables the use of public keys and aliases in the simulation description.
91138
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -97,9 +144,10 @@ pub struct ActivityParser {
97144
#[serde(with = "serializers::serde_node_id")]
98145
pub destination: NodeId,
99146
// The interval of the event, as in every how many seconds the payment is performed.
100-
pub interval_secs: u16,
147+
pub interval_secs: Interval,
101148
// The amount of m_sat to used in this payment.
102-
pub amount_msat: u64,
149+
#[serde(with = "serializers::serde_value_or_range")]
150+
pub amount_msat: Amount,
103151
}
104152

105153
/// Data structure used internally by the simulator. Both source and destination are represented as [PublicKey] here.
@@ -111,9 +159,9 @@ pub struct ActivityDefinition {
111159
// The destination of the payment.
112160
pub destination: NodeInfo,
113161
// The interval of the event, as in every how many seconds the payment is performed.
114-
pub interval_secs: u16,
162+
pub interval_secs: Interval,
115163
// The amount of m_sat to used in this payment.
116-
pub amount_msat: u64,
164+
pub amount_msat: Amount,
117165
}
118166

119167
#[derive(Debug, Error)]
@@ -762,7 +810,8 @@ async fn produce_events(
762810
shutdown: Trigger,
763811
listener: Listener,
764812
) {
765-
let interval = time::Duration::from_secs(act.interval_secs as u64);
813+
let mut interval = time::Duration::from_secs(act.interval_secs.value() as u64);
814+
let mut amt = act.amount_msat.value();
766815

767816
log::debug!(
768817
"Started producer for {} every {}s: {} -> {}.",
@@ -776,8 +825,17 @@ async fn produce_events(
776825
tokio::select! {
777826
biased;
778827
_ = time::sleep(interval) => {
779-
// Consumer was dropped
780-
if sender.send(SimulationEvent::SendPayment(act.destination.clone(), act.amount_msat)).await.is_err() {
828+
// Resample if needed
829+
if act.interval_secs.is_range() {
830+
interval = time::Duration::from_secs(act.interval_secs.value() as u64);
831+
log::debug!("Resampling interval. New value: {}", interval.as_secs());
832+
}
833+
if act.amount_msat.is_range() {
834+
amt = act.amount_msat.value();
835+
log::debug!("Resampling payment amount. New value: {}", amt);
836+
}
837+
if sender.send(SimulationEvent::SendPayment(act.destination.clone(), amt)).await.is_err() {
838+
// Consumer was dropped
781839
log::debug!(
782840
"Stopped producer for {}: {} -> {}. Consumer cannot be reached.",
783841
act.amount_msat,

sim-lib/src/serializers.rs

+36
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)