Skip to content

Commit 8ae6b33

Browse files
committed
sim-rs: support weighted TX production, stake pools don't generate TXs
1 parent eb80e15 commit 8ae6b33

File tree

5 files changed

+72
-8
lines changed

5 files changed

+72
-8
lines changed

data/simulation/topology.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ export interface Node<Location> {
2727
* What fraction of TXs (from 0 to 1) should introduce conflicts with transactions which were produced before?
2828
* Only supported by Rust simulation. */
2929
"tx-conflict-fraction"?: number | null;
30+
/**
31+
* How likely is this node to generate transactions, compared to its peers?
32+
* Default is 0 for nodes with stake, 1 for nodes with no stake.
33+
* Only supported by Rust simulation.
34+
*/
35+
"tx-generation-weight"?: number | null;
3036
/** If not null, the node will behave according to the given Behaviour.
3137
*
3238
* Only supported by Haskell simulation.

data/simulation/topology.schema.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,10 @@
5252
"tx-conflict-fraction": {
5353
"description": "What fraction of TXs (from 0 to 1) should introduce conflicts with transactions which were produced before?\nOnly supported by Rust simulation.",
5454
"type": "number"
55+
},
56+
"tx-generation-weight": {
57+
"description": "How likely is this node to generate transactions, compared to its peers?\nDefault is 0 for nodes with stake, 1 for nodes with no stake.\nOnly supported by Rust simulation.",
58+
"type": "number"
5559
}
5660
},
5761
"type": "object"
@@ -95,6 +99,10 @@
9599
"tx-conflict-fraction": {
96100
"description": "What fraction of TXs (from 0 to 1) should introduce conflicts with transactions which were produced before?\nOnly supported by Rust simulation.",
97101
"type": "number"
102+
},
103+
"tx-generation-weight": {
104+
"description": "How likely is this node to generate transactions, compared to its peers?\nDefault is 0 for nodes with stake, 1 for nodes with no stake.\nOnly supported by Rust simulation.",
105+
"type": "number"
98106
}
99107
},
100108
"type": "object"

sim-rs/sim-cli/src/bin/gen-test-data/strategy/utils.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ impl GraphBuilder {
171171
location: RawNodeLocation::Coords(n.location),
172172
cpu_core_count: n.cores,
173173
tx_conflict_fraction: None,
174+
tx_generation_weight: None,
174175
producers: BTreeMap::new(),
175176
};
176177
(name, node)

sim-rs/sim-core/src/config.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,8 @@ pub struct RawNode {
182182
pub cpu_core_count: Option<u64>,
183183
#[serde(skip_serializing_if = "Option::is_none")]
184184
pub tx_conflict_fraction: Option<f64>,
185+
#[serde(skip_serializing_if = "Option::is_none")]
186+
pub tx_generation_weight: Option<u64>,
185187
pub producers: BTreeMap<String, RawLinkInfo>,
186188
}
187189

@@ -260,6 +262,7 @@ impl From<RawTopology> for Topology {
260262
cpu_multiplier: 1.0,
261263
cores: node.cpu_core_count,
262264
tx_conflict_fraction: node.tx_conflict_fraction,
265+
tx_generation_weight: node.tx_generation_weight,
263266
consumers: vec![],
264267
},
265268
);
@@ -563,6 +566,7 @@ pub struct NodeConfiguration {
563566
pub cpu_multiplier: f64,
564567
pub cores: Option<u64>,
565568
pub tx_conflict_fraction: Option<f64>,
569+
pub tx_generation_weight: Option<u64>,
566570
pub consumers: Vec<NodeId>,
567571
}
568572

sim-rs/sim-core/src/sim/tx.rs

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::{
1414
struct NodeState {
1515
sink: mpsc::UnboundedSender<Arc<Transaction>>,
1616
tx_conflict_fraction: Option<f64>,
17+
tx_generation_weight: u64,
1718
}
1819

1920
pub struct TransactionProducer {
@@ -36,10 +37,14 @@ impl TransactionProducer {
3637
.iter()
3738
.map(|node| {
3839
let sink = node_tx_sinks.remove(&node.id).unwrap();
39-
let state = NodeState {
40-
sink,
41-
tx_conflict_fraction: node.tx_conflict_fraction,
42-
};
40+
let state =
41+
NodeState {
42+
sink,
43+
tx_conflict_fraction: node.tx_conflict_fraction,
44+
tx_generation_weight: node
45+
.tx_generation_weight
46+
.unwrap_or(if node.stake > 0 { 0 } else { 1 }),
47+
};
4348
(node.id, state)
4449
})
4550
.collect();
@@ -60,7 +65,6 @@ impl TransactionProducer {
6065
self.clock.wait_forever().await;
6166
return Ok(());
6267
};
63-
let node_count = self.nodes.len();
6468
let mut next_tx_id = 0;
6569
let mut next_tx_at = Timestamp::zero();
6670
let mut next_input_id = 0;
@@ -71,10 +75,15 @@ impl TransactionProducer {
7175
next_tx_at = start;
7276
};
7377

78+
let node_weights = self.nodes.iter().filter_map(|(id, node)| {
79+
let weight = node.tx_generation_weight;
80+
(weight != 0).then_some((*id, weight))
81+
});
82+
let node_lookup = WeightedLookup::new(node_weights);
83+
7484
loop {
75-
let node_index = rng.random_range(0..node_count);
76-
let node_id = NodeId::new(node_index);
77-
let node = self.nodes.get(&node_id).unwrap();
85+
let node_id = node_lookup.sample(rng).unwrap();
86+
let node = self.nodes.get(node_id).unwrap();
7887

7988
let conflict_fraction = node
8089
.tx_conflict_fraction
@@ -116,3 +125,39 @@ impl TransactionProducer {
116125
}
117126
}
118127
}
128+
129+
struct WeightedLookup<T> {
130+
elements: Vec<(T, u64)>,
131+
total_weight: u64,
132+
}
133+
134+
impl<T> WeightedLookup<T> {
135+
pub fn new(weights: impl IntoIterator<Item = (T, u64)>) -> Self {
136+
let elements: Vec<_> = weights
137+
.into_iter()
138+
.scan(0, |cum_weight, (element, weight)| {
139+
*cum_weight += weight;
140+
Some((element, *cum_weight))
141+
})
142+
.collect();
143+
let total_weight = elements
144+
.last()
145+
.map(|(_, weight)| *weight)
146+
.unwrap_or_default();
147+
Self {
148+
elements,
149+
total_weight,
150+
}
151+
}
152+
153+
pub fn sample<R: Rng>(&self, rng: &mut R) -> Option<&T> {
154+
let choice = rng.random_range(0..self.total_weight);
155+
match self
156+
.elements
157+
.binary_search_by_key(&choice, |(_, weight)| *weight)
158+
{
159+
Ok(index) => self.elements.get(index).map(|(el, _)| el),
160+
Err(index) => self.elements.get(index).map(|(el, _)| el),
161+
}
162+
}
163+
}

0 commit comments

Comments
 (0)