Skip to content

Commit 7f980c8

Browse files
committed
sim-rs: fix bug with conflicting TXs
1 parent 2018d06 commit 7f980c8

File tree

4 files changed

+187
-15
lines changed

4 files changed

+187
-15
lines changed

sim-rs/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## Unreleased
4+
5+
### Linear Leios
6+
- Add some protocol-level tests
7+
- Fix bug; transactions with conflicts referenced by EBs did not propagate far enough
8+
39
## v1.3.0
410

511
### Linear Leios

sim-rs/sim-core/src/clock/mock.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ impl MockClockCoordinator {
5151
)
5252
}
5353

54+
pub fn now(&self) -> Timestamp {
55+
self.time.load(std::sync::atomic::Ordering::Acquire)
56+
}
57+
5458
pub fn advance_time(&mut self, until: Timestamp) {
5559
while let Ok(event) = self.rx.try_recv() {
5660
match event {

sim-rs/sim-core/src/sim/linear_leios.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1004,7 +1004,7 @@ impl LinearLeiosNode {
10041004

10051005
if missing_txs.is_empty() {
10061006
self.queued
1007-
.schedule_cpu_task(CpuTask::EBBlockValidated(eb, seen));
1007+
.schedule_cpu_task(CpuTask::EBBlockValidated(eb.clone(), seen));
10081008
} else {
10091009
for tx_id in missing_txs {
10101010
self.leios
@@ -1014,6 +1014,23 @@ impl LinearLeiosNode {
10141014
.push(eb.id());
10151015
}
10161016
}
1017+
1018+
if matches!(
1019+
self.sim_config.variant,
1020+
LeiosVariant::LinearWithTxReferences
1021+
) {
1022+
// If the EB references any TXs which we already have, but are not in our mempool,
1023+
// we must have failed to add them to the mempool due to conflicts.
1024+
// Announce those TXs to our peers, since we didn't before.
1025+
for tx in &eb.txs {
1026+
if !self.has_tx(tx.id) || self.praos.mempool.contains_key(&tx.id) {
1027+
continue;
1028+
}
1029+
for peer in &self.consumers {
1030+
self.queued.send_to(*peer, Message::AnnounceTx(tx.id));
1031+
}
1032+
}
1033+
}
10171034
}
10181035

10191036
fn try_validating_eb(&mut self, eb_id: EndorserBlockId) {

sim-rs/sim-core/src/sim/tests/linear_leios.rs

Lines changed: 159 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,16 @@ use crate::{
1717
};
1818

1919
fn new_sim_config(topology: RawTopology) -> Arc<SimConfiguration> {
20-
let params =
20+
let mut params: crate::config::RawParameters =
2121
serde_yaml::from_slice(include_bytes!("../../../../parameters/config.default.yaml"))
2222
.unwrap();
23+
params.leios_variant = crate::config::LeiosVariant::LinearWithTxReferences;
24+
// every transaction fills up exactly half of an RB
25+
let tx_size = params.rb_body_max_size_bytes / 2;
26+
params.tx_size_bytes_distribution = crate::config::DistributionConfig::Constant {
27+
value: tx_size as f64,
28+
};
29+
params.tx_max_size_bytes = tx_size;
2330
let topology = topology.into();
2431
Arc::new(SimConfiguration::build(params, topology).unwrap())
2532
}
@@ -154,6 +161,49 @@ impl TestDriver {
154161
}
155162
}
156163

164+
pub fn expect_tx_sent(&mut self, from: NodeId, to: NodeId, tx: Arc<Transaction>) {
165+
self.expect_message(from, to, Message::AnnounceTx(tx.id));
166+
self.expect_message(to, from, Message::RequestTx(tx.id));
167+
self.expect_message(from, to, Message::Tx(tx.clone()));
168+
self.expect_cpu_task(to, CpuTask::TransactionValidated(from, tx));
169+
}
170+
171+
pub fn expect_tx_not_sent(&mut self, from: NodeId, to: NodeId, tx: Arc<Transaction>) {
172+
self.expect_no_message(from, to, Message::AnnounceTx(tx.id));
173+
}
174+
175+
pub fn expect_rb_and_eb_sent(
176+
&mut self,
177+
from: NodeId,
178+
to: NodeId,
179+
rb: Arc<LinearRankingBlock>,
180+
eb: Option<Arc<LinearEndorserBlock>>,
181+
) {
182+
self.expect_message(from, to, Message::AnnounceRBHeader(rb.header.id));
183+
self.expect_message(to, from, Message::RequestRBHeader(rb.header.id));
184+
self.expect_message(
185+
from,
186+
to,
187+
Message::RBHeader(rb.header.clone(), true, eb.is_some()),
188+
);
189+
self.expect_cpu_task(
190+
to,
191+
CpuTask::RBHeaderValidated(from, rb.header.clone(), true, eb.is_some()),
192+
);
193+
self.expect_message(to, from, Message::RequestRB(rb.header.id));
194+
self.expect_message(from, to, Message::RB(rb.clone()));
195+
self.expect_cpu_task(to, CpuTask::RBBlockValidated(rb));
196+
if let Some(eb) = eb {
197+
self.expect_message(to, from, Message::RequestEB(eb.id()));
198+
self.expect_message(from, to, Message::EB(eb.clone()));
199+
self.expect_cpu_task(to, CpuTask::EBHeaderValidated(from, eb));
200+
}
201+
}
202+
203+
pub fn expect_eb_validated(&mut self, node: NodeId, eb: Arc<LinearEndorserBlock>) {
204+
self.expect_cpu_task(node, CpuTask::EBBlockValidated(eb, self.time.now()));
205+
}
206+
157207
pub fn expect_message(
158208
&mut self,
159209
from: NodeId,
@@ -172,7 +222,12 @@ impl TestDriver {
172222
});
173223
assert!(
174224
found,
175-
"message {message:?} was not sent from {from} to {to}"
225+
"message {message:?} was not sent from {from} to {to}\npending messages: {:?}",
226+
queued
227+
.messages
228+
.iter()
229+
.filter(|(t, _)| t == &to)
230+
.collect::<Vec<_>>(),
176231
);
177232
let events = self
178233
.nodes
@@ -182,6 +237,20 @@ impl TestDriver {
182237
self.queued.entry(to).or_default().merge(events);
183238
}
184239

240+
pub fn expect_no_message(
241+
&mut self,
242+
from: NodeId,
243+
to: NodeId,
244+
message: <LinearLeiosNode as NodeImpl>::Message,
245+
) {
246+
let Some(queued) = self.queued.get(&from) else {
247+
return;
248+
};
249+
for (t, m) in &queued.messages {
250+
assert_ne!((t, m), (&to, &message));
251+
}
252+
}
253+
185254
pub fn expect_cpu_task(&mut self, node: NodeId, task: <LinearLeiosNode as NodeImpl>::Task) {
186255
self.expect_cpu_task_matching(node, |t| if *t == task { Some(t.clone()) } else { None });
187256
}
@@ -212,11 +281,14 @@ impl TestDriver {
212281
}
213282
}
214283

215-
fn is_new_rb_task(task: &CpuTask) -> Option<(LinearRankingBlock, Option<LinearEndorserBlock>)> {
284+
fn is_new_rb_task(
285+
task: &CpuTask,
286+
) -> Option<(Arc<LinearRankingBlock>, Option<Arc<LinearEndorserBlock>>)> {
216287
match task {
217-
CpuTask::RBBlockGenerated(rb, eb) => {
218-
Some((rb.clone(), eb.as_ref().map(|(eb, _)| eb.clone())))
219-
}
288+
CpuTask::RBBlockGenerated(rb, eb) => Some((
289+
Arc::new(rb.clone()),
290+
eb.as_ref().map(|(eb, _)| Arc::new(eb.clone())),
291+
)),
220292
_ => None,
221293
}
222294
}
@@ -233,17 +305,11 @@ fn should_propagate_transactions() {
233305

234306
// Node 1 produces a transaction, node 2 should request it
235307
let tx1 = sim.produce_tx(node1, false);
236-
sim.expect_message(node1, node2, Message::AnnounceTx(tx1.id));
237-
sim.expect_message(node2, node1, Message::RequestTx(tx1.id));
238-
sim.expect_message(node1, node2, Message::Tx(tx1.clone()));
239-
sim.expect_cpu_task(node2, CpuTask::TransactionValidated(node1, tx1.clone()));
308+
sim.expect_tx_sent(node1, node2, tx1.clone());
240309

241310
// Node 2 produces a transaction, node 1 should request it
242311
let tx2 = sim.produce_tx(node2, false);
243-
sim.expect_message(node2, node1, Message::AnnounceTx(tx2.id));
244-
sim.expect_message(node1, node2, Message::RequestTx(tx2.id));
245-
sim.expect_message(node2, node1, Message::Tx(tx2.clone()));
246-
sim.expect_cpu_task(node1, CpuTask::TransactionValidated(node2, tx2.clone()));
312+
sim.expect_tx_sent(node2, node1, tx2.clone());
247313

248314
// When node 1 produces an RB, it should include both TXs
249315
sim.win_next_rb_lottery(node1, 0);
@@ -252,3 +318,82 @@ fn should_propagate_transactions() {
252318
assert_eq!(new_rb.transactions, vec![tx1, tx2]);
253319
assert_eq!(new_eb, None);
254320
}
321+
322+
#[test]
323+
fn should_not_propagate_conflicting_transactions() {
324+
let topology = new_topology(vec![
325+
("node-1", new_node(Some(1000), vec!["node-2"])),
326+
("node-2", new_node(Some(1000), vec!["node-1"])),
327+
]);
328+
let mut sim = TestDriver::new(topology);
329+
let node1 = sim.id_for("node-1");
330+
let node2 = sim.id_for("node-2");
331+
332+
// Node 1 and 2 produce conflicting transactions
333+
let tx1 = sim.produce_tx(node1, false);
334+
let tx2 = sim.produce_tx(node2, true);
335+
336+
// Each node should send its TX to the other node,
337+
sim.expect_tx_sent(node1, node2, tx1.clone());
338+
sim.expect_tx_sent(node2, node1, tx2.clone());
339+
340+
// When node 1 produces an RB, it should include only its own TX
341+
sim.win_next_rb_lottery(node1, 0);
342+
sim.next_slot();
343+
let (new_rb, new_eb) = sim.expect_cpu_task_matching(node1, is_new_rb_task);
344+
assert_eq!(new_rb.transactions, vec![tx1]);
345+
assert_eq!(new_eb, None);
346+
}
347+
348+
#[test]
349+
fn should_repropagate_conflicting_transactions_from_eb() {
350+
let topology = new_topology(vec![
351+
("node-1", new_node(Some(1000), vec!["node-2"])),
352+
("node-2", new_node(Some(1000), vec!["node-1", "node-3"])),
353+
("node-3", new_node(Some(1000), vec!["node-2"])),
354+
]);
355+
let mut sim = TestDriver::new(topology);
356+
let node1 = sim.id_for("node-1");
357+
let node2 = sim.id_for("node-2");
358+
let node3 = sim.id_for("node-3");
359+
360+
// Node 1 produces 3 transactions
361+
let tx1_1 = sim.produce_tx(node1, false);
362+
let tx1_2 = sim.produce_tx(node1, false);
363+
let tx1_3 = sim.produce_tx(node1, false);
364+
365+
// Node 2 produces a transaction which conflicts with Node 1's final transaction
366+
let tx2 = sim.produce_tx(node2, true);
367+
// Node 2 sends its transactions to nodes 1 and 3
368+
sim.expect_tx_sent(node2, node1, tx2.clone());
369+
sim.expect_tx_sent(node2, node3, tx2.clone());
370+
371+
// Node 1 sends all of its transactions to node 2
372+
sim.expect_tx_sent(node1, node2, tx1_1.clone());
373+
sim.expect_tx_sent(node1, node2, tx1_2.clone());
374+
sim.expect_tx_sent(node1, node2, tx1_3.clone());
375+
376+
// Node 2 sends the first two transactions to node 3, but not the conflicting third
377+
sim.expect_tx_sent(node2, node3, tx1_1.clone());
378+
sim.expect_tx_sent(node2, node3, tx1_2.clone());
379+
sim.expect_tx_not_sent(node2, node3, tx1_3.clone());
380+
381+
// Now, Node 1 produces an RB (with an EB, because there are enough transactions)
382+
sim.win_next_rb_lottery(node1, 0);
383+
sim.next_slot();
384+
let (rb, eb) = sim.expect_cpu_task_matching(node1, is_new_rb_task);
385+
let eb = eb.expect("node did not produce EB");
386+
assert_eq!(rb.transactions, vec![tx1_1, tx1_2]);
387+
assert_eq!(eb.txs, vec![tx1_3.clone()]);
388+
389+
// That RB and EB propagate from node 1 to node 2
390+
sim.expect_rb_and_eb_sent(node1, node2, rb.clone(), Some(eb.clone()));
391+
// Node 2 fully validates the EB, because node 1 has all TXs
392+
sim.expect_eb_validated(node2, eb.clone());
393+
// And Node 2 propagates it to Node 3
394+
sim.expect_rb_and_eb_sent(node2, node3, rb.clone(), Some(eb.clone()));
395+
396+
// and NOW Node 2 will tell Node 3 about the EB's conflicting TX
397+
sim.expect_tx_sent(node2, node3, tx1_3);
398+
sim.expect_eb_validated(node3, eb);
399+
}

0 commit comments

Comments
 (0)