Skip to content

Commit dc544fa

Browse files
authored
Merge pull request #173 from bjohnson5/168-start-and-count
Issue #168: Adding optional start and count values to sim.json file
2 parents 0763aea + 1f0163b commit dc544fa

File tree

5 files changed

+147
-14
lines changed

5 files changed

+147
-14
lines changed

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,31 @@ to execute. Note that `source` nodes *must* be contained in `nodes`,
141141
but destination nodes can be any public node in the network (though
142142
this may result in liquidity draining over time).
143143

144+
Required fields:
145+
```
146+
"source": the payer
147+
"destination": the payee
148+
"interval_secs": how often the payments should be sent
149+
"amount_msat": the amount of each payment
150+
```
151+
152+
Optional fields:
153+
```
154+
"start_secs": the time to start sending payments
155+
"count": the total number of payments to send
156+
```
157+
158+
> If `start_secs` is not provided the payments will begin as soon as the simulation starts (default=0)
159+
160+
> If `count` is not provided the payments will continue for as long as the simulation runs (default=None)
161+
144162
The example simulation file below sets up the following simulation:
145163
* Connect to `Alice` running LND to generate activity.
146164
* Connect to `Bob` running CLN to generate activity.
147165
* Dispatch 2000 msat payments from `Alice` to `Carol` every 1 seconds.
148166
* Dispatch 140000 msat payments from `Bob` to `Alice` every 50 seconds.
149167
* Dispatch 1000 msat payments from `Bob` to `Dave` every 2 seconds.
168+
* Dispatch 10 payments (5000 msat each) from `Erin` to `Frank` at 2 second intervals, starting 20 seconds into the sim.
150169
```
151170
{
152171
"nodes": [
@@ -162,6 +181,18 @@ The example simulation file below sets up the following simulation:
162181
"ca_cert": "/path/ca.pem",
163182
"client_cert": "/path/client.pem",
164183
"client_key": "/path/client-key.pem"
184+
},
185+
{
186+
"id": "Erin",
187+
"address": "https://localhost:10012",
188+
"macaroon": "/path/admin.macaroon",
189+
"cert": "/path/tls.cert"
190+
},
191+
{
192+
"id": "Frank",
193+
"address": "https://localhost:10014",
194+
"macaroon": "/path/admin.macaroon",
195+
"cert": "/path/tls.cert"
165196
}
166197
],
167198
"activity": [
@@ -182,6 +213,14 @@ The example simulation file below sets up the following simulation:
182213
"destination": "03232e245059a2e7f6e32d6c4bca97fc4cda935c553ea3693adb3265a19050c3bf",
183214
"interval_secs": 2,
184215
"amount_msat": 1000
216+
},
217+
{
218+
"source": "Erin",
219+
"destination": "Frank",
220+
"start_secs": 20,
221+
"count": 10,
222+
"interval_secs": 2,
223+
"amount_msat": 5000
185224
}
186225
]
187226
}

sim-cli/src/main.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,8 @@ async fn main() -> anyhow::Result<()> {
185185
validated_activities.push(ActivityDefinition {
186186
source,
187187
destination,
188+
start_secs: act.start_secs,
189+
count: act.count,
188190
interval_secs: act.interval_secs,
189191
amount_msat: act.amount_msat,
190192
});

sim-lib/src/defined_activity.rs

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,24 @@ use tokio::time::Duration;
55
#[derive(Clone)]
66
pub struct DefinedPaymentActivity {
77
destination: NodeInfo,
8+
start: Duration,
9+
count: Option<u64>,
810
wait: Duration,
911
amount: u64,
1012
}
1113

1214
impl DefinedPaymentActivity {
13-
pub fn new(destination: NodeInfo, wait: Duration, amount: u64) -> Self {
15+
pub fn new(
16+
destination: NodeInfo,
17+
start: Duration,
18+
count: Option<u64>,
19+
wait: Duration,
20+
amount: u64,
21+
) -> Self {
1422
DefinedPaymentActivity {
1523
destination,
24+
start,
25+
count,
1626
wait,
1727
amount,
1828
}
@@ -36,6 +46,14 @@ impl DestinationGenerator for DefinedPaymentActivity {
3646
}
3747

3848
impl PaymentGenerator for DefinedPaymentActivity {
49+
fn payment_start(&self) -> Duration {
50+
self.start
51+
}
52+
53+
fn payment_count(&self) -> Option<u64> {
54+
self.count
55+
}
56+
3957
fn next_payment_wait(&self) -> Duration {
4058
self.wait
4159
}
@@ -69,8 +87,13 @@ mod tests {
6987
let source = get_random_keypair();
7088
let payment_amt = 50;
7189

72-
let generator =
73-
DefinedPaymentActivity::new(node.clone(), Duration::from_secs(60), payment_amt);
90+
let generator = DefinedPaymentActivity::new(
91+
node.clone(),
92+
Duration::from_secs(0),
93+
None,
94+
Duration::from_secs(60),
95+
payment_amt,
96+
);
7497

7598
let (dest, dest_capacity) = generator.choose_destination(source.1);
7699
assert_eq!(node.pubkey, dest.pubkey);

sim-lib/src/lib.rs

Lines changed: 70 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ impl NodeId {
5050
crate::NodeId::PublicKey(pk) => {
5151
if pk != node_id {
5252
return Err(LightningError::ValidationError(format!(
53-
"the provided node id does not match the one returned by the backend ({} != {}).",
53+
"The provided node id does not match the one returned by the backend ({} != {}).",
5454
pk, node_id
5555
)));
5656
}
@@ -139,6 +139,12 @@ pub struct ActivityParser {
139139
/// The destination of the payment.
140140
#[serde(with = "serializers::serde_node_id")]
141141
pub destination: NodeId,
142+
/// The time in the simulation to start the payment.
143+
#[serde(default)]
144+
pub start_secs: u16,
145+
/// The number of payments to send over the course of the simulation.
146+
#[serde(default)]
147+
pub count: Option<u64>,
142148
/// The interval of the event, as in every how many seconds the payment is performed.
143149
pub interval_secs: u16,
144150
/// The amount of m_sat to used in this payment.
@@ -153,6 +159,10 @@ pub struct ActivityDefinition {
153159
pub source: NodeInfo,
154160
/// The destination of the payment.
155161
pub destination: NodeInfo,
162+
/// The time in the simulation to start the payment.
163+
pub start_secs: u16,
164+
/// The number of payments to send over the course of the simulation.
165+
pub count: Option<u64>,
156166
/// The interval of the event, as in every how many seconds the payment is performed.
157167
pub interval_secs: u16,
158168
/// The amount of m_sat to used in this payment.
@@ -261,6 +271,12 @@ pub trait DestinationGenerator: Send {
261271
pub struct PaymentGenerationError(String);
262272

263273
pub trait PaymentGenerator: Display + Send {
274+
/// Returns the time that the payments should start
275+
fn payment_start(&self) -> Duration;
276+
277+
/// Returns the number of payments that should be made
278+
fn payment_count(&self) -> Option<u64>;
279+
264280
/// Returns the number of seconds that a node should wait until firing its next payment.
265281
fn next_payment_wait(&self) -> time::Duration;
266282

@@ -554,9 +570,25 @@ impl Simulation {
554570
);
555571

556572
// Next, we'll spin up our actual producers that will be responsible for triggering the configured activity.
557-
self.dispatch_producers(activities, consumer_channels, &mut tasks)
573+
// The producers will use their own JoinSet so that the simulation can be shutdown if they all finish.
574+
let mut producer_tasks = JoinSet::new();
575+
self.dispatch_producers(activities, consumer_channels, &mut producer_tasks)
558576
.await?;
559577

578+
// Start a task that waits for the producers to finish.
579+
// If all producers finish, then there is nothing left to do and the simulation can be shutdown.
580+
let producer_trigger = self.shutdown_trigger.clone();
581+
tasks.spawn(async move {
582+
while let Some(res) = producer_tasks.join_next().await {
583+
if let Err(e) = res {
584+
log::error!("Producer exited with error: {e}.");
585+
}
586+
}
587+
log::info!("All producers finished. Shutting down.");
588+
producer_trigger.trigger()
589+
});
590+
591+
// Start a task that will shutdown the simulation if the total_time is met.
560592
if let Some(total_time) = self.total_time {
561593
let t = self.shutdown_trigger.clone();
562594
let l = self.shutdown_listener.clone();
@@ -639,7 +671,7 @@ impl Simulation {
639671
// csr: consume simulation results
640672
let csr_write_results = self.write_results.clone();
641673
tasks.spawn(async move {
642-
log::debug!("Staring simulation results consumer.");
674+
log::debug!("Starting simulation results consumer.");
643675
if let Err(e) = consume_simulation_results(
644676
result_logger,
645677
results_receiver,
@@ -667,6 +699,8 @@ impl Simulation {
667699
for description in self.activity.iter() {
668700
let activity_generator = DefinedPaymentActivity::new(
669701
description.destination.clone(),
702+
Duration::from_secs(description.start_secs.into()),
703+
description.count,
670704
Duration::from_secs(description.interval_secs.into()),
671705
description.amount_msat,
672706
);
@@ -777,9 +811,9 @@ impl Simulation {
777811
consume_events(ce_node, receiver, ce_output_sender, ce_listener).await
778812
{
779813
ce_shutdown.trigger();
780-
log::error!("Event consumer exited with error: {e:?}.");
814+
log::error!("Event consumer for node {node_info} exited with error: {e:?}.");
781815
} else {
782-
log::debug!("Event consumer for node {node_info} received shutdown signal.");
816+
log::debug!("Event consumer for node {node_info} completed successfully.");
783817
}
784818
});
785819
}
@@ -826,9 +860,9 @@ impl Simulation {
826860
.await
827861
{
828862
pe_shutdown.trigger();
829-
log::debug!("Event producer exited with error {e}.");
863+
log::debug!("Activity producer for {source} exited with error {e}.");
830864
} else {
831-
log::debug!("Random activity generator for {source} received shutdown signal.");
865+
log::debug!("Activity producer for {source} completed successfully.");
832866
}
833867
});
834868
}
@@ -918,9 +952,33 @@ async fn produce_events<N: DestinationGenerator + ?Sized, A: PaymentGenerator +
918952
sender: Sender<SimulationEvent>,
919953
listener: Listener,
920954
) -> Result<(), SimulationError> {
955+
let mut current_count = 0;
921956
loop {
922-
let wait = node_generator.next_payment_wait();
923-
log::debug!("Next payment for {source} in {:?} seconds.", wait);
957+
if let Some(c) = node_generator.payment_count() {
958+
if c == current_count {
959+
log::info!(
960+
"Payment count has been met for {source}: {c} payments. Stopping the activity."
961+
);
962+
return Ok(());
963+
}
964+
}
965+
966+
let wait: Duration = if current_count == 0 {
967+
let start = node_generator.payment_start();
968+
if start != Duration::from_secs(0) {
969+
log::debug!(
970+
"Using a start delay. The first payment for {source} will be at {:?}.",
971+
start
972+
);
973+
}
974+
start
975+
} else {
976+
log::debug!(
977+
"Next payment for {source} in {:?}.",
978+
node_generator.next_payment_wait()
979+
);
980+
node_generator.next_payment_wait()
981+
};
924982

925983
select! {
926984
biased;
@@ -948,14 +1006,15 @@ async fn produce_events<N: DestinationGenerator + ?Sized, A: PaymentGenerator +
9481006
},
9491007
};
9501008

951-
log::debug!("Generated random payment: {source} -> {}: {amount} msat.", destination);
1009+
log::debug!("Generated payment: {source} -> {}: {amount} msat.", destination);
9521010

9531011
// Send the payment, exiting if we can no longer send to the consumer.
9541012
let event = SimulationEvent::SendPayment(destination.clone(), amount);
9551013
if sender.send(event.clone()).await.is_err() {
956-
return Err(SimulationError::MpscChannelError (format!("Stopped random producer for {amount}: {source} -> {destination}.")));
1014+
return Err(SimulationError::MpscChannelError (format!("Stopped activity producer for {amount}: {source} -> {destination}.")));
9571015
}
9581016

1017+
current_count += 1;
9591018
},
9601019
}
9611020
}

sim-lib/src/random_activity.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,16 @@ fn events_per_month(source_capacity_msat: u64, multiplier: f64, expected_payment
194194
}
195195

196196
impl PaymentGenerator for RandomPaymentActivity {
197+
/// Returns the time that the payments should start. This will always be 0 for the RandomPaymentActivity type.
198+
fn payment_start(&self) -> Duration {
199+
Duration::from_secs(0)
200+
}
201+
202+
/// Returns the number of payments that should be made. This will always be None for the RandomPaymentActivity type.
203+
fn payment_count(&self) -> Option<u64> {
204+
None
205+
}
206+
197207
/// Returns the amount of time until the next payment should be scheduled for the node.
198208
fn next_payment_wait(&self) -> Duration {
199209
let mut rng = rand::thread_rng();

0 commit comments

Comments
 (0)