test(fuzz): add `approve_new_view` & `receive_safe_block_with_aggregated_qc` transition (#208)
This commit is contained in:
parent
8fad13b0cc
commit
deeb3eeba0
|
@ -1,6 +1,6 @@
|
||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, HashSet};
|
||||||
|
|
||||||
use consensus_engine::{Block, LeaderProof, NodeId, Qc, StandardQc, TimeoutQc, View};
|
use consensus_engine::{AggregateQc, Block, LeaderProof, NodeId, Qc, StandardQc, TimeoutQc, View};
|
||||||
use proptest::prelude::*;
|
use proptest::prelude::*;
|
||||||
use proptest::strategy::BoxedStrategy;
|
use proptest::strategy::BoxedStrategy;
|
||||||
use proptest_state_machine::ReferenceStateMachine;
|
use proptest_state_machine::ReferenceStateMachine;
|
||||||
|
@ -71,6 +71,8 @@ impl ReferenceStateMachine for RefState {
|
||||||
state.transition_local_timeout(),
|
state.transition_local_timeout(),
|
||||||
state.transition_receive_timeout_qc_for_current_view(),
|
state.transition_receive_timeout_qc_for_current_view(),
|
||||||
state.transition_receive_timeout_qc_for_old_view(),
|
state.transition_receive_timeout_qc_for_old_view(),
|
||||||
|
state.transition_approve_new_view_with_latest_timeout_qc(),
|
||||||
|
state.transition_receive_safe_block_with_aggregated_qc(),
|
||||||
]
|
]
|
||||||
.boxed()
|
.boxed()
|
||||||
}
|
}
|
||||||
|
@ -99,6 +101,10 @@ impl ReferenceStateMachine for RefState {
|
||||||
Transition::ReceiveTimeoutQcForOldView(timeout_qc) => {
|
Transition::ReceiveTimeoutQcForOldView(timeout_qc) => {
|
||||||
timeout_qc.view < state.current_view()
|
timeout_qc.view < state.current_view()
|
||||||
}
|
}
|
||||||
|
Transition::ApproveNewViewWithLatestTimeoutQc(timeout_qc, _) => {
|
||||||
|
state.latest_timeout_qcs().contains(timeout_qc)
|
||||||
|
&& state.highest_voted_view < RefState::new_view_from(timeout_qc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -138,6 +144,11 @@ impl ReferenceStateMachine for RefState {
|
||||||
Transition::ReceiveTimeoutQcForOldView(_) => {
|
Transition::ReceiveTimeoutQcForOldView(_) => {
|
||||||
// Nothing to do because we expect the state doesn't change.
|
// Nothing to do because we expect the state doesn't change.
|
||||||
}
|
}
|
||||||
|
Transition::ApproveNewViewWithLatestTimeoutQc(timeout_qc, _) => {
|
||||||
|
let new_view = RefState::new_view_from(timeout_qc);
|
||||||
|
state.chain.entry(new_view).or_insert(ViewEntry::new());
|
||||||
|
state.highest_voted_view = new_view;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state
|
state
|
||||||
|
@ -228,23 +239,12 @@ impl RefState {
|
||||||
|
|
||||||
// Generate a Transition::ReceiveTimeoutQcForCurrentView
|
// Generate a Transition::ReceiveTimeoutQcForCurrentView
|
||||||
fn transition_receive_timeout_qc_for_current_view(&self) -> BoxedStrategy<Transition> {
|
fn transition_receive_timeout_qc_for_current_view(&self) -> BoxedStrategy<Transition> {
|
||||||
let view = self.current_view();
|
|
||||||
let high_qc = self
|
|
||||||
.chain
|
|
||||||
.iter()
|
|
||||||
.rev()
|
|
||||||
.find_map(|(_, entry)| entry.high_qc());
|
|
||||||
|
|
||||||
if let Some(high_qc) = high_qc {
|
|
||||||
Just(Transition::ReceiveTimeoutQcForCurrentView(TimeoutQc {
|
Just(Transition::ReceiveTimeoutQcForCurrentView(TimeoutQc {
|
||||||
view,
|
view: self.current_view(),
|
||||||
high_qc,
|
high_qc: self.high_qc(),
|
||||||
sender: SENDER,
|
sender: SENDER,
|
||||||
}))
|
}))
|
||||||
.boxed()
|
.boxed()
|
||||||
} else {
|
|
||||||
Just(Transition::Nop).boxed()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a Transition::ReceiveTimeoutQcForOldView
|
// Generate a Transition::ReceiveTimeoutQcForOldView
|
||||||
|
@ -271,6 +271,44 @@ impl RefState {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Generate a Transition::ApproveNewViewWithLatestTimeoutQc.
|
||||||
|
fn transition_approve_new_view_with_latest_timeout_qc(&self) -> BoxedStrategy<Transition> {
|
||||||
|
let latest_timeout_qcs: Vec<TimeoutQc> = self
|
||||||
|
.latest_timeout_qcs()
|
||||||
|
.iter()
|
||||||
|
.filter(|timeout_qc| self.highest_voted_view < RefState::new_view_from(timeout_qc))
|
||||||
|
.cloned()
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
if latest_timeout_qcs.is_empty() {
|
||||||
|
Just(Transition::Nop).boxed()
|
||||||
|
} else {
|
||||||
|
proptest::sample::select(latest_timeout_qcs)
|
||||||
|
.prop_map(move |timeout_qc| {
|
||||||
|
//TODO: set new_views
|
||||||
|
Transition::ApproveNewViewWithLatestTimeoutQc(timeout_qc, HashSet::new())
|
||||||
|
})
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a Transition::ReceiveSafeBlock, but with AggregatedQc.
|
||||||
|
fn transition_receive_safe_block_with_aggregated_qc(&self) -> BoxedStrategy<Transition> {
|
||||||
|
//TODO: more randomness
|
||||||
|
let current_view = self.current_view();
|
||||||
|
|
||||||
|
Just(Transition::ReceiveSafeBlock(Block {
|
||||||
|
id: rand::thread_rng().gen(),
|
||||||
|
view: current_view + 1,
|
||||||
|
parent_qc: Qc::Aggregated(AggregateQc {
|
||||||
|
high_qc: self.high_qc(),
|
||||||
|
view: current_view,
|
||||||
|
}),
|
||||||
|
leader_proof: LEADER_PROOF.clone(),
|
||||||
|
}))
|
||||||
|
.boxed()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn highest_voted_view(&self) -> View {
|
pub fn highest_voted_view(&self) -> View {
|
||||||
self.highest_voted_view
|
self.highest_voted_view
|
||||||
}
|
}
|
||||||
|
@ -289,6 +327,31 @@ impl RefState {
|
||||||
timeout_qc.view + 1
|
timeout_qc.view + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn high_qc(&self) -> StandardQc {
|
||||||
|
self.chain
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.find_map(|(_, entry)| entry.high_qc())
|
||||||
|
.unwrap() // doesn't fail because self.chain always contains at least a genesis block
|
||||||
|
}
|
||||||
|
|
||||||
|
fn latest_timeout_qcs(&self) -> Vec<TimeoutQc> {
|
||||||
|
let latest_timeout_qc_view_entry = self
|
||||||
|
.chain
|
||||||
|
.iter()
|
||||||
|
.rev()
|
||||||
|
.find(|(_, entry)| !entry.timeout_qcs.is_empty());
|
||||||
|
|
||||||
|
match latest_timeout_qc_view_entry {
|
||||||
|
Some((_, entry)) => entry
|
||||||
|
.timeout_qcs
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.collect::<Vec<TimeoutQc>>(),
|
||||||
|
None => vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn consecutive_block(parent: &Block) -> Block {
|
fn consecutive_block(parent: &Block) -> Block {
|
||||||
Block {
|
Block {
|
||||||
// use rand because we don't want this to be shrinked by proptest
|
// use rand because we don't want this to be shrinked by proptest
|
||||||
|
@ -304,6 +367,10 @@ impl RefState {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ViewEntry {
|
impl ViewEntry {
|
||||||
|
fn new() -> ViewEntry {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
self.blocks.is_empty() && self.timeout_qcs.is_empty()
|
self.blocks.is_empty() && self.timeout_qcs.is_empty()
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,6 +109,15 @@ impl StateMachineTest for ConsensusEngineTest {
|
||||||
// Check that the engine state didn't change.
|
// Check that the engine state didn't change.
|
||||||
assert_eq!(engine, prev_engine);
|
assert_eq!(engine, prev_engine);
|
||||||
|
|
||||||
|
ConsensusEngineTest { engine }
|
||||||
|
}
|
||||||
|
Transition::ApproveNewViewWithLatestTimeoutQc(timeout_qc, new_views) => {
|
||||||
|
let (engine, _) = state.engine.approve_new_view(timeout_qc.clone(), new_views);
|
||||||
|
assert_eq!(
|
||||||
|
engine.highest_voted_view(),
|
||||||
|
RefState::new_view_from(&timeout_qc)
|
||||||
|
);
|
||||||
|
|
||||||
ConsensusEngineTest { engine }
|
ConsensusEngineTest { engine }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
use consensus_engine::{Block, TimeoutQc};
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use consensus_engine::{Block, NewView, TimeoutQc};
|
||||||
|
|
||||||
// State transtitions that will be picked randomly
|
// State transtitions that will be picked randomly
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
|
@ -11,6 +13,6 @@ pub enum Transition {
|
||||||
LocalTimeout,
|
LocalTimeout,
|
||||||
ReceiveTimeoutQcForCurrentView(TimeoutQc),
|
ReceiveTimeoutQcForCurrentView(TimeoutQc),
|
||||||
ReceiveTimeoutQcForOldView(TimeoutQc),
|
ReceiveTimeoutQcForOldView(TimeoutQc),
|
||||||
//TODO: add more invalid transitions that must be rejected by consensus-engine
|
ApproveNewViewWithLatestTimeoutQc(TimeoutQc, HashSet<NewView>),
|
||||||
//TODO: add more transitions
|
//TODO: add more corner transitions
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue