Skip to content

Commit 66a2eb7

Browse files
authored
Sg/fix duplicate txs (#572)
* fix: don't include same TXs in RB and EB * chore: update changelog * chore: release version 1.3.1
1 parent 0dcb37f commit 66a2eb7

File tree

6 files changed

+105
-28
lines changed

6 files changed

+105
-28
lines changed

sim-rs/CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# Changelog
22

3-
## Unreleased
3+
## v1.3.1
44

55
### Linear Leios
66
- Add some protocol-level tests
77
- Fix bug; transactions with conflicts referenced by EBs did not propagate far enough
8+
- Fix bug; transactions were included in both RBs and their endorsed EBs
89

910
### Other
1011

sim-rs/Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sim-rs/sim-cli/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sim-cli"
3-
version = "1.3.0"
3+
version = "1.3.1"
44
edition = "2024"
55
default-run = "sim-cli"
66
rust-version = "1.88"

sim-rs/sim-core/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "sim-core"
3-
version = "1.3.0"
3+
version = "1.3.1"
44
edition = "2024"
55
rust-version = "1.88"
66

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

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -538,22 +538,27 @@ impl LinearLeiosNode {
538538
return None;
539539
}
540540

541-
let eb_id = self.leios.ebs_by_rb.get(&rb_id)?;
542-
let votes = self.leios.votes_by_eb.get(eb_id)?;
541+
let eb_id = *self.leios.ebs_by_rb.get(&rb_id)?;
542+
let votes = self.leios.votes_by_eb.get(&eb_id)?;
543543
let total_votes = votes.values().copied().sum::<usize>();
544544
if (total_votes as u64) < self.sim_config.vote_threshold {
545545
// Not enough votes. No endorsement.
546546
return None;
547547
}
548+
let votes = votes.clone();
548549

549-
// We haven't necessarily finished validating this EB, or even received it and its contents.
550-
// That won't stop us from generating the endorsement, though it'll make us produce an empty block.
551-
if !self.is_eb_validated(*eb_id) {
552-
self.leios.incomplete_onchain_ebs.insert(*eb_id);
550+
if let Some(eb) = self.get_validated_eb(eb_id) {
551+
// If we're endorsing this EB, clear its TXs out of the mempool now
552+
// so that we don't include them in new blocks.
553+
self.remove_eb_txs_from_mempool(&eb);
554+
} else {
555+
// We haven't finished validating this EB, maybe even haven't received it and its contents.
556+
// That won't stop us from generating the endorsement, though it'll make us produce an empty block.
557+
self.leios.incomplete_onchain_ebs.insert(eb_id);
553558
}
554559

555560
Some(Endorsement {
556-
eb: *eb_id,
561+
eb: eb_id,
557562
size_bytes: self.sim_config.sizes.cert(votes.len()),
558563
votes: votes.clone(),
559564
})
@@ -1094,13 +1099,18 @@ impl LinearLeiosNode {
10941099
}
10951100

10961101
fn is_eb_validated(&self, eb_id: EndorserBlockId) -> bool {
1097-
matches!(
1098-
self.leios.ebs.get(&eb_id),
1102+
self.get_validated_eb(eb_id).is_some()
1103+
}
1104+
1105+
fn get_validated_eb(&self, eb_id: EndorserBlockId) -> Option<Arc<EndorserBlock>> {
1106+
match self.leios.ebs.get(&eb_id) {
10991107
Some(EndorserBlockView::Received {
1108+
eb,
11001109
validated: true,
11011110
..
1102-
})
1103-
)
1111+
}) => Some(eb.clone()),
1112+
_ => None,
1113+
}
11041114
}
11051115
}
11061116

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

Lines changed: 78 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::{
22
collections::{BTreeMap, HashMap},
33
sync::Arc,
4+
time::Duration,
45
};
56

67
use rand::{RngCore, SeedableRng};
@@ -30,6 +31,8 @@ fn new_sim_config(topology: RawTopology) -> Arc<SimConfiguration> {
3031
value: tx_size as f64,
3132
};
3233
params.tx_max_size_bytes = tx_size;
34+
// it takes two votes to certify an EB
35+
params.vote_threshold = 2;
3336
let topology = topology.into();
3437
Arc::new(SimConfiguration::build(params, topology).unwrap())
3538
}
@@ -154,6 +157,14 @@ impl TestDriver {
154157
tx
155158
}
156159

160+
pub fn produce_txs<const N: usize>(
161+
&mut self,
162+
node_id: NodeId,
163+
conflict: bool,
164+
) -> [Arc<Transaction>; N] {
165+
[(); N].map(|_| self.produce_tx(node_id, conflict))
166+
}
167+
157168
pub fn win_next_rb_lottery(&mut self, node_id: NodeId, result: u64) {
158169
self.lottery
159170
.get(&node_id)
@@ -249,6 +260,13 @@ impl TestDriver {
249260
self.expect_cpu_task(node, CpuTask::EBBlockValidated(eb, self.time.now()));
250261
}
251262

263+
pub fn expect_vote_bundle_sent(&mut self, from: NodeId, to: NodeId, votes: Arc<VoteBundle>) {
264+
self.expect_message(from, to, Message::AnnounceVotes(votes.id));
265+
self.expect_message(to, from, Message::RequestVotes(votes.id));
266+
self.expect_message(from, to, Message::Votes(votes.clone()));
267+
self.expect_cpu_task(to, CpuTask::VTBundleValidated(from, votes));
268+
}
269+
252270
pub fn expect_message(
253271
&mut self,
254272
from: NodeId,
@@ -394,12 +412,10 @@ fn should_produce_rbs_and_ebs() {
394412
let node2 = sim.id_for("node-2");
395413

396414
// Node 1 produces three transactions, Node 2 should request them all
397-
let tx1_1 = sim.produce_tx(node1, false);
398-
sim.expect_tx_sent(node1, node2, tx1_1.clone());
399-
let tx1_2 = sim.produce_tx(node1, false);
400-
sim.expect_tx_sent(node1, node2, tx1_2.clone());
401-
let tx1_3 = sim.produce_tx(node1, false);
402-
sim.expect_tx_sent(node1, node2, tx1_3.clone());
415+
let [tx1_1, tx1_2, tx1_3] = sim.produce_txs(node1, false);
416+
for tx in [&tx1_1, &tx1_2, &tx1_3] {
417+
sim.expect_tx_sent(node1, node2, tx.clone());
418+
}
403419

404420
sim.win_next_rb_lottery(node1, 0);
405421
sim.next_slot();
@@ -451,9 +467,7 @@ fn should_repropagate_conflicting_transactions_from_eb() {
451467
let node3 = sim.id_for("node-3");
452468

453469
// Node 1 produces 3 transactions
454-
let tx1_1 = sim.produce_tx(node1, false);
455-
let tx1_2 = sim.produce_tx(node1, false);
456-
let tx1_3 = sim.produce_tx(node1, false);
470+
let [tx1_1, tx1_2, tx1_3] = sim.produce_txs(node1, false);
457471

458472
// Node 2 produces a transaction which conflicts with Node 1's final transaction
459473
let tx2 = sim.produce_tx(node2, true);
@@ -501,9 +515,7 @@ fn should_vote_for_eb() {
501515
let node1 = sim.id_for("node-1");
502516
let node2 = sim.id_for("node-2");
503517

504-
let txs = (0..3)
505-
.map(|_| sim.produce_tx(node1, false))
506-
.collect::<Vec<_>>();
518+
let txs: [_; 3] = sim.produce_txs(node1, false);
507519
for tx in &txs {
508520
sim.expect_tx_sent(node1, node2, tx.clone());
509521
}
@@ -521,3 +533,57 @@ fn should_vote_for_eb() {
521533
let vote = sim.expect_cpu_task_matching(node2, is_new_vote_task);
522534
assert_eq!(*vote.ebs.first_key_value().unwrap().0, eb.id());
523535
}
536+
537+
#[test]
538+
fn should_not_include_tx_twice() {
539+
let topology = new_topology(vec![
540+
("node-1", new_node(Some(1000), vec!["node-2"])),
541+
("node-2", new_node(Some(1000), vec!["node-1"])),
542+
]);
543+
let mut sim = TestDriver::new(topology);
544+
let node1 = sim.id_for("node-1");
545+
let node2 = sim.id_for("node-2");
546+
547+
let [rb_tx1, rb_tx2, eb_tx] = sim.produce_txs(node1, false);
548+
for tx in [&rb_tx1, &rb_tx2, &eb_tx] {
549+
sim.expect_tx_sent(node1, node2, tx.clone());
550+
}
551+
552+
sim.win_next_vote_lottery(node1, 0);
553+
sim.win_next_vote_lottery(node2, 0);
554+
555+
// Node 1 produces an RB containing two transactions, and an EB containing a third
556+
sim.win_next_rb_lottery(node1, 0);
557+
sim.next_slot();
558+
let (rb, eb) = sim.expect_cpu_task_matching(node1, is_new_rb_task);
559+
let eb = eb.expect("node did not produce EB");
560+
assert!(eb.txs.contains(&eb_tx));
561+
562+
// Node 2 receives and validates the RB and EB
563+
sim.expect_rb_and_eb_sent(node1, node2, rb.clone(), Some(eb.clone()));
564+
sim.expect_eb_validated(node2, eb.clone());
565+
566+
// Nodes 1 and 2 both vote for the EB
567+
sim.advance_time_to(sim.now() + (sim.config.header_diffusion_time * 3));
568+
let votes_1 = sim.expect_cpu_task_matching(node1, is_new_vote_task);
569+
sim.expect_vote_bundle_sent(node1, node2, votes_1);
570+
let votes_2 = sim.expect_cpu_task_matching(node2, is_new_vote_task);
571+
sim.expect_vote_bundle_sent(node2, node1, votes_2);
572+
573+
// After enough time has elapsed to include the EB in a new RB, Node 2 mints a new RB
574+
sim.advance_time_to(
575+
sim.now()
576+
+ Duration::from_secs(sim.config.linear_diffuse_stage_length)
577+
+ Duration::from_secs(sim.config.linear_vote_stage_length),
578+
);
579+
sim.win_next_rb_lottery(node2, 0);
580+
sim.next_slot();
581+
let (rb, new_eb) = sim.expect_cpu_task_matching(node2, is_new_rb_task);
582+
583+
// This RB endorses the previous EB (including its transaction on the chain)
584+
assert_eq!(rb.endorsement.as_ref().map(|e| e.eb), Some(eb.id()));
585+
586+
// And it does not include any transactions of its own
587+
assert!(rb.transactions.is_empty());
588+
assert_eq!(new_eb, None);
589+
}

0 commit comments

Comments
 (0)