Grandpa validator set handoff justification (#1190)

* core: make block justification optional

* runtime: update wasm binaries

* core: optionally pass justification on finalize_block

* finality-grandpa: add channel to trigger authority set changes

this will allow the `BlockImport` to trigger an authority set change when
importing a change block that provides a justification (when syncing)

* finality-grandpa: move finalize_block to free function

* finality-grandpa: add GrandpaOracle for auth set liveness checking

this will be used by `BlockImport` to check whether the authority set for a
given block is still live, if the authority set isn't live then importing a
change block requires a justification.

* finality-grandpa: store justification on finalized transition blocks

* finality-grandpa: check justification on authority set change blocks

* finality-grandpa: poll grandpa liveness oracle every 10 seconds

* finality-grandpa: spawn grandpa oracle in service setup

* core: support multiple subscriptions per consensus gossip topic

* finality-grandpa: create and verify justifications

* finality-grandpa: update to local branch of grandpa

* finality-grandpa: update to finality-grandpa v0.5.0

* finality-grandpa: move grandpa oracle code

* finality-grandpa: fix canonality check

* finality-grandpa: clean up error handling

* finality-grandpa: fix canonical_at_height

* finality-grandpa: fix tests

* runtime: update wasm binaries

* core: add tests for finalizing block with justification

* finality-grandpa: improve validation of justifications

* core: remove unused IncompleteJustification block import error

* core: test multiple subscribers for same consensus gossip topic

* Revert "finality-grandpa: improve validation of justifications"

This reverts commit 51eb2c58c2219801e876af6d6c9371bdd9ff2477.

* finality-grandpa: fix commit validation

* finality-grandpa: fix commit ancestry validation

* finality-grandpa: use grandpa v0.5.1

* finality-grandpa: add docs

* finality-grandpa: fix failing test

* finality-grandpa: only allow a pending authority set change per fork

* finality-grandpa: fix validator set transition test
This commit is contained in:
André Silva
2018-12-08 05:34:59 +00:00
committed by Gav Wood
parent da822276dd
commit e779eeb2ec
29 changed files with 1115 additions and 389 deletions
+34 -9
View File
@@ -46,7 +46,7 @@ struct MessageEntry<B: BlockT> {
/// Consensus network protocol handler. Manages statements and candidate requests.
pub struct ConsensusGossip<B: BlockT> {
peers: HashMap<NodeIndex, PeerConsensus<(B::Hash, B::Hash)>>,
live_message_sinks: HashMap<B::Hash, mpsc::UnboundedSender<ConsensusMessage>>,
live_message_sinks: HashMap<B::Hash, Vec<mpsc::UnboundedSender<ConsensusMessage>>>,
messages: Vec<MessageEntry<B>>,
known_messages: HashSet<(B::Hash, B::Hash)>,
session_start: Option<B::Hash>,
@@ -150,7 +150,10 @@ impl<B: BlockT> ConsensusGossip<B> {
/// Prune old or no longer relevant consensus messages. Provide a predicate
/// for pruning, which returns `false` when the items with a given topic should be pruned.
pub fn collect_garbage<P: Fn(&B::Hash) -> bool>(&mut self, predicate: P) {
self.live_message_sinks.retain(|_, sink| !sink.is_closed());
self.live_message_sinks.retain(|_, sinks| {
sinks.retain(|sink| !sink.is_closed());
!sinks.is_empty()
});
let hashes = &mut self.known_messages;
let before = self.messages.len();
@@ -175,7 +178,7 @@ impl<B: BlockT> ConsensusGossip<B> {
for entry in self.messages.iter().filter(|e| e.topic == topic) {
tx.unbounded_send(entry.message.clone()).expect("receiver known to be live; qed");
}
self.live_message_sinks.insert(topic, tx);
self.live_message_sinks.entry(topic).or_default().push(tx);
rx
}
@@ -217,12 +220,14 @@ impl<B: BlockT> ConsensusGossip<B> {
use std::collections::hash_map::Entry;
peer.known_messages.insert((topic, message_hash));
if let Entry::Occupied(mut entry) = self.live_message_sinks.entry(topic) {
debug!(target: "gossip", "Pushing consensus message to sink for {}.", topic);
if let Err(e) = entry.get().unbounded_send(message.clone()) {
trace!(target:"gossip", "Error broadcasting message notification: {:?}", e);
}
if entry.get().is_closed() {
debug!(target: "gossip", "Pushing consensus message to sinks for {}.", topic);
entry.get_mut().retain(|sink| {
if let Err(e) = sink.unbounded_send(message.clone()) {
trace!(target:"gossip", "Error broadcasting message notification: {:?}", e);
}
!sink.is_closed()
});
if entry.get().is_empty() {
entry.remove_entry();
}
}
@@ -345,4 +350,24 @@ mod tests {
assert_eq!(consensus.messages.len(), 2);
}
#[test]
fn can_keep_multiple_subscribers_per_topic() {
use futures::Stream;
let mut consensus = ConsensusGossip::<Block>::new();
let message = vec![1, 2, 3];
let message_hash = HashFor::<Block>::hash(&message);
let topic = HashFor::<Block>::hash(&[1,2,3]);
consensus.register_message(message_hash, topic, || message.clone());
let stream1 = consensus.messages_for(topic);
let stream2 = consensus.messages_for(topic);
assert_eq!(stream1.wait().next(), Some(Ok(message.clone())));
assert_eq!(stream2.wait().next(), Some(Ok(message)));
}
}
+6 -36
View File
@@ -31,6 +31,7 @@ use parking_lot::{Condvar, Mutex, RwLock};
use network_libp2p::{NodeIndex, Severity};
use primitives::AuthorityId;
use runtime_primitives::Justification;
use runtime_primitives::traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero};
pub use blocks::BlockData;
@@ -57,7 +58,7 @@ pub trait Verifier<B: BlockT>: Send + Sync + Sized {
&self,
origin: BlockOrigin,
header: B::Header,
justification: Vec<u8>,
justification: Option<Justification>,
body: Option<Vec<B::Extrinsic>>
) -> Result<(ImportBlock<B>, Option<Vec<AuthorityId>>), String>;
}
@@ -339,8 +340,6 @@ enum BlockImportResult<H: ::std::fmt::Debug + PartialEq, N: ::std::fmt::Debug +
enum BlockImportError {
/// Block missed header, can't be imported
IncompleteHeader(Option<NodeIndex>),
/// Block missed justification, can't be imported
IncompleteJustification(Option<NodeIndex>),
/// Block verification failed, can't be imported
VerificationFailed(Option<NodeIndex>, String),
/// Block is known to be Bad
@@ -411,7 +410,7 @@ fn import_single_block<B: BlockT, V: Verifier<B>>(
let block = block.block;
let (header, justification) = match (block.header, block.justification) {
(Some(header), Some(justification)) => (header, justification),
(Some(header), justification) => (header, justification),
(None, _) => {
if let Some(peer) = peer {
debug!(target: "sync", "Header {} was not provided by {} ", block.hash, peer);
@@ -420,14 +419,6 @@ fn import_single_block<B: BlockT, V: Verifier<B>>(
}
return Err(BlockImportError::IncompleteHeader(peer)) //TODO: use persistent ID
},
(_, None) => {
if let Some(peer) = peer {
debug!(target: "sync", "Justification set for block {} was not provided by {} ", block.hash, peer);
} else {
debug!(target: "sync", "Justification set for block {} was not provided", block.hash);
}
return Err(BlockImportError::IncompleteJustification(peer)) //TODO: use persistent ID
}
};
let number = header.number().clone();
@@ -486,12 +477,6 @@ fn process_import_result<B: BlockT>(
link.block_imported(&hash, number);
1
},
Err(BlockImportError::IncompleteJustification(who)) => {
if let Some(peer) = who {
link.useless_peer(peer, "Sent block with incomplete justification to import");
}
0
},
Err(BlockImportError::IncompleteHeader(who)) => {
if let Some(peer) = who {
link.useless_peer(peer, "Sent block with incomplete header to import");
@@ -555,7 +540,7 @@ impl<B: BlockT> Verifier<B> for PassThroughVerifier {
&self,
origin: BlockOrigin,
header: B::Header,
justification: Vec<u8>,
justification: Option<Justification>,
body: Option<Vec<B::Extrinsic>>
) -> Result<(ImportBlock<B>, Option<Vec<AuthorityId>>), String> {
Ok((ImportBlock {
@@ -563,7 +548,7 @@ impl<B: BlockT> Verifier<B> for PassThroughVerifier {
header,
body,
finalized: self.0,
justification: justification,
justification,
post_digests: vec![],
auxiliary: Vec::new(),
}, None))
@@ -702,7 +687,7 @@ pub mod tests {
fn prepare_good_block() -> (client::Client<test_client::Backend, test_client::Executor, Block, test_client::runtime::RuntimeApi>, Hash, u64, BlockData<Block>) {
let client = test_client::new();
let block = client.new_block().unwrap().bake().unwrap();
client.justify_and_import(BlockOrigin::File, block).unwrap();
client.import(BlockOrigin::File, block).unwrap();
let (hash, number) = (client.block_hash(1).unwrap().unwrap(), 1);
let block = message::BlockData::<Block> {
@@ -745,16 +730,6 @@ pub mod tests {
);
}
#[test]
fn import_single_good_block_without_justification_fails() {
let (_, _, _, mut block) = prepare_good_block();
block.block.justification = None;
assert_eq!(
import_single_block(&test_client::new(), BlockOrigin::File, block, Arc::new(PassThroughVerifier(true))),
Err(BlockImportError::IncompleteJustification(Some(0)))
);
}
#[test]
fn process_import_result_works() {
let link = TestLink::new();
@@ -776,11 +751,6 @@ pub mod tests {
assert_eq!(link.total(), 1);
assert_eq!(link.disconnects.get(), 1);
let link = TestLink::new();
assert_eq!(process_import_result::<Block>(&link, Err(BlockImportError::IncompleteJustification(Some(0)))), 0);
assert_eq!(link.total(), 1);
assert_eq!(link.disconnects.get(), 1);
let link = TestLink::new();
assert_eq!(process_import_result::<Block>(&link, Err(BlockImportError::UnknownParent)), 0);
assert_eq!(link.total(), 1);
+1 -1
View File
@@ -255,7 +255,7 @@ impl<V: 'static + Verifier<Block>, D> Peer<V, D> {
body: Some(block.extrinsics),
receipt: None,
message_queue: None,
justification: Some(Vec::new()),
justification: None,
},
}]);
}