grandpa: round catchup messages (#2801)

* grandpa: initial structure for catch up messages

* grandpa: answer catch up requests

* grandpa: inject catch up messages into global stream

* grandpa: keep track of pending catch up request

* grandpa: block catchup until all referenced blocks are imported

* grandpa: unify catch up and commit streams

* grandpa: simplify communication stream/sink types

* grandpa: note gossip validator on catch up message import

* grandpa: fix cost on catch up message validation

* grandpa: check signatures on catch up messages

* grandpa: clean up catch up request handling state

* grandpa: adjust costs on invalid catch up requests

* grandpa: release lock before pushing catch up message

* grandpa: validate catch up request against peer view

* grandpa: catch up docs

* grandpa: fix tests

* grandpa: until_imported: add tests for catch up messages

* grandpa: add tests for catch up message gossip validation

* grandpa: integrate HistoricalVotes changes

* grandpa: add test for neighbor packet triggering catch up

* grandpa: add test for full voter catch up

* grandpa: depend on finality-grandpa 0.8 from crates

* granda: use finality-grandpa test helpers

* grandpa: add PSM cost for answering catch up requests

* grandpa: code style fixes

Co-Authored-By: Robert Habermeier <rphmeier@gmail.com>

* grandpa: more trailing commas

* grandpa: lower cost of invalid catch up requests near set change

* grandpa: process catch up sending on import of neighbor message

* grandpa: add comments on HistoricalVotes

* grandpa: use finality-grandpa v0.8.1 from crates.io

* grandpa: fix test compilation
This commit is contained in:
André Silva
2019-07-04 20:40:16 +01:00
committed by GitHub
parent 90f214f000
commit d5bc7325b9
11 changed files with 1381 additions and 240 deletions
@@ -26,6 +26,7 @@ use std::sync::Arc;
use keyring::AuthorityKeyring;
use parity_codec::Encode;
use crate::environment::SharedVoterSetState;
use super::gossip::{self, GossipValidator};
use super::{AuthorityId, VoterSet, Round, SetId};
@@ -92,6 +93,18 @@ impl super::Network<Block> for TestNetwork {
}
}
impl network_gossip::ValidatorContext<Block> for TestNetwork {
fn broadcast_topic(&mut self, _: Hash, _: bool) { }
fn broadcast_message(&mut self, _: Hash, _: Vec<u8>, _: bool) { }
fn send_message(&mut self, who: &network::PeerId, data: Vec<u8>) {
<Self as super::Network<Block>>::send_message(self, vec![who.clone()], data);
}
fn send_topic(&mut self, _: &network::PeerId, _: Hash, _: bool) { }
}
struct Tester {
net_handle: super::NetworkBridge<Block, TestNetwork>,
gossip_validator: Arc<GossipValidator<Block>>,
@@ -125,8 +138,38 @@ fn config() -> crate::Config {
}
}
// dummy voter set state
fn voter_set_state() -> SharedVoterSetState<Block> {
use crate::authorities::AuthoritySet;
use crate::environment::{CompletedRound, CompletedRounds, HasVoted, VoterSetState};
use grandpa::round::State as RoundState;
use substrate_primitives::H256;
let state = RoundState::genesis((H256::zero(), 0));
let base = state.prevote_ghost.unwrap();
let voters = AuthoritySet::genesis(Vec::new());
let set_state = VoterSetState::Live {
completed_rounds: CompletedRounds::new(
CompletedRound {
state,
number: 0,
votes: Vec::new(),
base,
},
0,
&voters,
),
current_round: HasVoted::No,
};
set_state.into()
}
// needs to run in a tokio runtime.
fn make_test_network() -> impl Future<Item=Tester,Error=()> {
fn make_test_network() -> (
impl Future<Item=Tester,Error=()>,
TestNetwork,
) {
let (tx, rx) = mpsc::unbounded();
let net = TestNetwork { sender: tx };
@@ -145,15 +188,18 @@ fn make_test_network() -> impl Future<Item=Tester,Error=()> {
let (bridge, startup_work) = super::NetworkBridge::new(
net.clone(),
config(),
None,
voter_set_state(),
Exit,
);
startup_work.map(move |()| Tester {
gossip_validator: bridge.validator.clone(),
net_handle: bridge,
events: rx,
})
(
startup_work.map(move |()| Tester {
gossip_validator: bridge.validator.clone(),
net_handle: bridge,
events: rx,
}),
net,
)
}
fn make_ids(keys: &[AuthorityKeyring]) -> Vec<(AuthorityId, u64)> {
@@ -217,7 +263,7 @@ fn good_commit_leads_to_relay() {
let id = network::PeerId::random();
let global_topic = super::global_topic::<Block>(set_id);
let test = make_test_network()
let test = make_test_network().0
.and_then(move |tester| {
// register a peer.
tester.gossip_validator.new_peer(&mut NoopContext, &id, network::config::Roles::FULL);
@@ -228,7 +274,7 @@ fn good_commit_leads_to_relay() {
let (commits_in, _) = tester.net_handle.global_communication(SetId(1), voter_set, false);
{
let (action, _) = tester.gossip_validator.do_validate(&id, &encoded_commit[..]);
let (action, ..) = tester.gossip_validator.do_validate(&id, &encoded_commit[..]);
match action {
gossip::Action::ProcessAndDiscard(t, _) => assert_eq!(t, global_topic),
_ => panic!("wrong expected outcome from initial commit validation"),
@@ -257,8 +303,12 @@ fn good_commit_leads_to_relay() {
// when the commit comes in, we'll tell the callback it was good.
let handle_commit = commits_in.into_future()
.map(|(item, _)| {
let (_, _, mut callback) = item.unwrap();
(callback)(super::CommitProcessingOutcome::Good);
match item.unwrap() {
grandpa::voter::CommunicationIn::Commit(_, _, mut callback) => {
callback.run(grandpa::voter::CommitProcessingOutcome::good());
},
_ => panic!("commit expected"),
}
})
.map_err(|_| panic!("could not process commit"));
@@ -328,7 +378,7 @@ fn bad_commit_leads_to_report() {
let id = network::PeerId::random();
let global_topic = super::global_topic::<Block>(set_id);
let test = make_test_network()
let test = make_test_network().0
.and_then(move |tester| {
// register a peer.
tester.gossip_validator.new_peer(&mut NoopContext, &id, network::config::Roles::FULL);
@@ -339,7 +389,7 @@ fn bad_commit_leads_to_report() {
let (commits_in, _) = tester.net_handle.global_communication(SetId(1), voter_set, false);
{
let (action, _) = tester.gossip_validator.do_validate(&id, &encoded_commit[..]);
let (action, ..) = tester.gossip_validator.do_validate(&id, &encoded_commit[..]);
match action {
gossip::Action::ProcessAndDiscard(t, _) => assert_eq!(t, global_topic),
_ => panic!("wrong expected outcome from initial commit validation"),
@@ -368,8 +418,12 @@ fn bad_commit_leads_to_report() {
// when the commit comes in, we'll tell the callback it was good.
let handle_commit = commits_in.into_future()
.map(|(item, _)| {
let (_, _, mut callback) = item.unwrap();
(callback)(super::CommitProcessingOutcome::Bad);
match item.unwrap() {
grandpa::voter::CommunicationIn::Commit(_, _, mut callback) => {
callback.run(grandpa::voter::CommitProcessingOutcome::bad());
},
_ => panic!("commit expected"),
}
})
.map_err(|_| panic!("could not process commit"));
@@ -393,3 +447,61 @@ fn bad_commit_leads_to_report() {
current_thread::block_on_all(test).unwrap();
}
#[test]
fn peer_with_higher_view_leads_to_catch_up_request() {
let id = network::PeerId::random();
let (tester, mut net) = make_test_network();
let test = tester
.and_then(move |tester| {
// register a peer.
tester.gossip_validator.new_peer(&mut NoopContext, &id, network::config::Roles::FULL);
Ok((tester, id))
})
.and_then(move |(tester, id)| {
// send neighbor message at round 10 and height 50
let result = tester.gossip_validator.validate(
&mut net,
&id,
&gossip::GossipMessage::<Block>::from(gossip::NeighborPacket {
set_id: SetId(0),
round: Round(10),
commit_finalized_height: 50,
}).encode(),
);
// neighbor packets are always discard
match result {
network_gossip::ValidationResult::Discard => {},
_ => panic!("wrong expected outcome from neighbor validation"),
}
// a catch up request should be sent to the peer for round - 1
tester.filter_network_events(move |event| match event {
Event::SendMessage(peers, message) => {
assert_eq!(
peers,
vec![id.clone()],
);
assert_eq!(
message,
gossip::GossipMessage::<Block>::CatchUpRequest(
gossip::CatchUpRequestMessage {
set_id: SetId(0),
round: Round(9),
}
).encode(),
);
true
},
_ => false,
})
.map_err(|_| panic!("could not watch for peer send message"))
.map(|_| ())
});
current_thread::block_on_all(test).unwrap();
}