mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-26 18:07:58 +00:00
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:
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}]);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user