Skip to content

Commit be8af3d

Browse files
A0-4563: Added unit test for censorship resistance in Extender (#503)
* A0-4563: Added a UT in which one of the nodes is missing units * Adding some non-direct parents * fmt * Actually use first round * clippy * Bump consensus crate version * Review
1 parent 2581824 commit be8af3d

File tree

8 files changed

+131
-17
lines changed

8 files changed

+131
-17
lines changed

Cargo.lock

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

consensus/Cargo.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "aleph-bft"
3-
version = "0.39.0"
3+
version = "0.39.1"
44
edition = "2021"
55
authors = ["Cardinal Cryptography"]
66
categories = ["algorithms", "data-structures", "cryptography", "database"]

consensus/src/dag/mod.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -468,7 +468,7 @@ mod test {
468468
.take(5)
469469
.cloned()
470470
.collect();
471-
let fork = random_unit_with_parents(forker_id, &fork_parents);
471+
let fork = random_unit_with_parents(forker_id, &fork_parents, 3);
472472
let fork = Signed::sign(fork, &keychains[forker_id.0]);
473473
let unit = units
474474
.get(3)
@@ -552,7 +552,7 @@ mod test {
552552
.take(5)
553553
.cloned()
554554
.collect();
555-
let fork = random_unit_with_parents(forker_id, &fork_parents);
555+
let fork = random_unit_with_parents(forker_id, &fork_parents, 3);
556556
let fork = Signed::sign(fork, &keychains[forker_id.0]);
557557
let unit = units
558558
.get(3)

consensus/src/extension/election.rs

+31-2
Original file line numberDiff line numberDiff line change
@@ -217,8 +217,8 @@ mod test {
217217
units::Units,
218218
},
219219
units::{
220-
random_full_parent_reconstrusted_units_up_to, random_reconstructed_unit_with_parents,
221-
TestingDagUnit, Unit,
220+
minimal_reconstructed_dag_units_up_to, random_full_parent_reconstrusted_units_up_to,
221+
random_reconstructed_unit_with_parents, TestingDagUnit, Unit,
222222
},
223223
NodeCount,
224224
};
@@ -344,6 +344,7 @@ mod test {
344344
creator,
345345
&parents,
346346
&keychains[creator.0],
347+
round,
347348
));
348349
}
349350
}
@@ -356,4 +357,32 @@ mod test {
356357
}
357358
}
358359
}
360+
361+
#[test]
362+
#[ignore]
363+
// TODO(A0-4563) Uncomment after changes to parent voting code
364+
fn given_minimal_dag_with_orphaned_node_when_electing_then_orphaned_node_is_not_head() {
365+
use ElectionResult::*;
366+
let mut units = Units::new();
367+
let n_members = NodeCount(14);
368+
let max_round = 4;
369+
let session_id = 2137;
370+
let keychains = Keychain::new_vec(n_members);
371+
372+
let (dag, inactive_node_first_unit) =
373+
minimal_reconstructed_dag_units_up_to(max_round, n_members, session_id, &keychains);
374+
for round in dag {
375+
for unit in round {
376+
units.add_unit(unit);
377+
}
378+
}
379+
let election = RoundElection::for_round(0, &units).expect("we have enough rounds");
380+
match election {
381+
Pending(_) => panic!("should have elected"),
382+
Elected(head) => {
383+
// This should be the second unit in order, as the first was not popular.
384+
assert_ne!(head, inactive_node_first_unit.hash());
385+
}
386+
}
387+
}
359388
}

consensus/src/extension/extender.rs

+27
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ impl<U: UnitWithParents> Extender<U> {
7070

7171
#[cfg(test)]
7272
mod test {
73+
use crate::units::{minimal_reconstructed_dag_units_up_to, Unit};
7374
use crate::{
7475
extension::extender::Extender, units::random_full_parent_reconstrusted_units_up_to,
7576
NodeCount, Round,
@@ -97,4 +98,30 @@ mod test {
9798
assert_eq!(batch.len(), n_members.0);
9899
}
99100
}
101+
102+
#[test]
103+
#[ignore]
104+
// TODO(A0-4563) Uncomment after changes to parent voting code
105+
fn given_minimal_dag_with_orphaned_node_when_producing_batches_have_correct_length() {
106+
let mut extender = Extender::new();
107+
let n_members = NodeCount(4);
108+
let threshold = n_members.consensus_threshold();
109+
let max_round: Round = 4;
110+
let session_id = 2137;
111+
let keychains = Keychain::new_vec(n_members);
112+
let mut batches = Vec::new();
113+
let (dag, _) =
114+
minimal_reconstructed_dag_units_up_to(max_round, n_members, session_id, &keychains);
115+
for round in dag {
116+
for unit in round {
117+
batches.append(&mut extender.add_unit(unit));
118+
}
119+
}
120+
assert_eq!(batches.len(), (max_round - 3).into());
121+
assert_eq!(batches[0].len(), 1);
122+
assert_eq!(batches[0][0].round(), 0);
123+
for batch in batches.iter().skip(1) {
124+
assert_eq!(batch.len(), threshold.0);
125+
}
126+
}
100127
}

consensus/src/units/mod.rs

+6-5
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ mod validator;
1515
pub(crate) use store::*;
1616
#[cfg(test)]
1717
pub use testing::{
18-
create_preunits, creator_set, full_unit_to_unchecked_signed_unit, preunit_to_full_unit,
19-
preunit_to_signed_unit, preunit_to_unchecked_signed_unit,
20-
random_full_parent_reconstrusted_units_up_to, random_full_parent_units_up_to,
21-
random_reconstructed_unit_with_parents, random_unit_with_parents, DagUnit as TestingDagUnit,
22-
FullUnit as TestingFullUnit, SignedUnit as TestingSignedUnit, WrappedSignedUnit,
18+
create_preunits, creator_set, full_unit_to_unchecked_signed_unit,
19+
minimal_reconstructed_dag_units_up_to, preunit_to_full_unit, preunit_to_signed_unit,
20+
preunit_to_unchecked_signed_unit, random_full_parent_reconstrusted_units_up_to,
21+
random_full_parent_units_up_to, random_reconstructed_unit_with_parents,
22+
random_unit_with_parents, DagUnit as TestingDagUnit, FullUnit as TestingFullUnit,
23+
SignedUnit as TestingSignedUnit, WrappedSignedUnit,
2324
};
2425
pub use validator::{ValidationError, Validator};
2526

consensus/src/units/testing.rs

+62-5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use crate::units::TestingDagUnit;
12
use crate::{
23
creation::Creator as GenericCreator,
34
dag::ReconstructedUnit,
@@ -9,6 +10,7 @@ use crate::{
910
NodeCount, NodeIndex, NodeMap, Round, SessionId, Signed,
1011
};
1112
use aleph_bft_mock::{Data, Hash64, Hasher64, Keychain, Signature};
13+
use rand::prelude::IteratorRandom;
1214

1315
type ControlHash = GenericControlHash<Hasher64>;
1416
type Creator = GenericCreator<Hasher64>;
@@ -149,10 +151,10 @@ fn parent_map<U: Unit<Hasher = Hasher64>>(parents: &Vec<U>) -> NodeMap<Hash64> {
149151
pub fn random_unit_with_parents<U: Unit<Hasher = Hasher64>>(
150152
creator: NodeIndex,
151153
parents: &Vec<U>,
154+
round: Round,
152155
) -> FullUnit {
153156
let representative_parent = parents.last().expect("there are parents");
154157
let session_id = representative_parent.session_id();
155-
let round = representative_parent.round() + 1;
156158
let parent_map = parent_map(parents);
157159
let control_hash = ControlHash::new(&parent_map);
158160
preunit_to_full_unit(PreUnit::new(creator, round, control_hash), session_id)
@@ -162,9 +164,10 @@ pub fn random_reconstructed_unit_with_parents<U: Unit<Hasher = Hasher64>>(
162164
creator: NodeIndex,
163165
parents: &Vec<U>,
164166
keychain: &Keychain,
167+
round: Round,
165168
) -> DagUnit {
166169
ReconstructedUnit::with_parents(
167-
full_unit_to_signed_unit(random_unit_with_parents(creator, parents), keychain),
170+
full_unit_to_signed_unit(random_unit_with_parents(creator, parents, round), keychain),
168171
parent_map(parents),
169172
)
170173
.expect("correct parents")
@@ -176,18 +179,20 @@ pub fn random_full_parent_units_up_to(
176179
session_id: SessionId,
177180
) -> Vec<Vec<FullUnit>> {
178181
let mut result = vec![random_initial_units(n_members, session_id)];
179-
for _ in 0..round {
182+
for r in 1..=round {
180183
let units = n_members
181184
.into_iterator()
182185
.map(|node_id| {
183-
random_unit_with_parents(node_id, result.last().expect("previous round present"))
186+
random_unit_with_parents(node_id, result.last().expect("previous round present"), r)
184187
})
185188
.collect();
186189
result.push(units);
187190
}
188191
result
189192
}
190193

194+
/// Constructs a DAG so that in each round (except round 0) it has all N parents, where N is number
195+
/// of nodes in the DAG
191196
pub fn random_full_parent_reconstrusted_units_up_to(
192197
round: Round,
193198
n_members: NodeCount,
@@ -197,18 +202,70 @@ pub fn random_full_parent_reconstrusted_units_up_to(
197202
let mut result = vec![random_initial_reconstructed_units(
198203
n_members, session_id, keychains,
199204
)];
200-
for _ in 0..round {
205+
for r in 1..=round {
201206
let units = n_members
202207
.into_iterator()
203208
.map(|node_id| {
204209
random_reconstructed_unit_with_parents(
205210
node_id,
206211
result.last().expect("previous round present"),
207212
&keychains[node_id.0],
213+
r,
208214
)
209215
})
210216
.collect();
211217
result.push(units);
212218
}
213219
result
214220
}
221+
222+
/// Constructs a DAG so that in each round (except round 0) it has at least 2N/3 + 1 parents, where
223+
/// N is number of nodes in the DAG. At least one node from N/3 group has some non-direct parents.
224+
pub fn minimal_reconstructed_dag_units_up_to(
225+
round: Round,
226+
n_members: NodeCount,
227+
session_id: SessionId,
228+
keychains: &[Keychain],
229+
) -> (Vec<Vec<DagUnit>>, DagUnit) {
230+
let mut rng = rand::thread_rng();
231+
let threshold = n_members.consensus_threshold().0;
232+
233+
let mut dag = vec![random_initial_reconstructed_units(
234+
n_members, session_id, keychains,
235+
)];
236+
let inactive_node_first_and_last_seen_unit = dag
237+
.last()
238+
.expect("previous round present")
239+
.last()
240+
.expect("there is at least one node")
241+
.clone();
242+
let inactive_node = inactive_node_first_and_last_seen_unit.creator();
243+
for r in 1..=round {
244+
let mut parents: Vec<TestingDagUnit> = dag
245+
.last()
246+
.expect("previous round present")
247+
.clone()
248+
.into_iter()
249+
.filter(|unit| unit.creator() != inactive_node)
250+
.choose_multiple(&mut rng, threshold)
251+
.into_iter()
252+
.collect();
253+
if r == round {
254+
let ancestor_unit = dag
255+
.first()
256+
.expect("first round present")
257+
.get(inactive_node.0)
258+
.expect("inactive node unit present");
259+
parents.push(ancestor_unit.clone());
260+
}
261+
let units = n_members
262+
.into_iterator()
263+
.filter(|node_id| node_id != &inactive_node)
264+
.map(|node_id| {
265+
random_reconstructed_unit_with_parents(node_id, &parents, &keychains[node_id.0], r)
266+
})
267+
.collect();
268+
dag.push(units);
269+
}
270+
(dag, inactive_node_first_and_last_seen_unit)
271+
}

consensus/src/units/validator.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ mod tests {
254254
.take(4)
255255
.cloned()
256256
.collect();
257-
let unit = random_unit_with_parents(creator_id, &parents);
257+
let unit = random_unit_with_parents(creator_id, &parents, 1);
258258
let preunit = unit.as_pre_unit().clone();
259259
let keychain = Keychain::new(n_members, creator_id);
260260
let unchecked_unit = full_unit_to_unchecked_signed_unit(unit, &keychain);

0 commit comments

Comments
 (0)