mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 20:01:08 +00:00
Initial GRANDPA integration (#866)
* implement grandpa client * consensus gossip with arbitrary topics * defer GRANDPA messages until referenced blocks imported * set up communication for voter in a transparent way * instantiate GRANDPA voter * keep last round state on disk * switch back to crates.io finality-grandpa * update cargo.lock * use new `collect_garbage` API * update sync test framework and make public * test that observers can observe * fix warning * use more idiomatic predicate for collecting garbage in gossip * kill spaces * fix date
This commit is contained in:
committed by
Gav Wood
parent
1cc0e3b6ea
commit
68b4be14ff
@@ -54,7 +54,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>>,
|
||||
message_sink: Option<(mpsc::UnboundedSender<ConsensusMessage<B>>, B::Hash)>,
|
||||
live_message_sinks: HashMap<B::Hash, mpsc::UnboundedSender<ConsensusMessage<B>>>,
|
||||
messages: Vec<MessageEntry<B>>,
|
||||
message_hashes: HashSet<B::Hash>,
|
||||
}
|
||||
@@ -64,7 +64,7 @@ impl<B: BlockT> ConsensusGossip<B> where B::Header: HeaderT<Number=u64> {
|
||||
pub fn new() -> Self {
|
||||
ConsensusGossip {
|
||||
peers: HashMap::new(),
|
||||
message_sink: None,
|
||||
live_message_sinks: HashMap::new(),
|
||||
messages: Default::default(),
|
||||
message_hashes: Default::default(),
|
||||
}
|
||||
@@ -72,7 +72,7 @@ impl<B: BlockT> ConsensusGossip<B> where B::Header: HeaderT<Number=u64> {
|
||||
|
||||
/// Closes all notification streams.
|
||||
pub fn abort(&mut self) {
|
||||
self.message_sink = None;
|
||||
self.live_message_sinks.clear();
|
||||
}
|
||||
|
||||
/// Handle new connected peer.
|
||||
@@ -150,23 +150,23 @@ impl<B: BlockT> ConsensusGossip<B> where B::Header: HeaderT<Number=u64> {
|
||||
}
|
||||
|
||||
/// Handles incoming chain-specific message and repropagates
|
||||
pub fn on_chain_specific(&mut self, protocol: &mut Context<B>, who: NodeIndex, message: Vec<u8>, parent_hash: B::Hash) {
|
||||
pub fn on_chain_specific(&mut self, protocol: &mut Context<B>, who: NodeIndex, message: Vec<u8>, topic: B::Hash) {
|
||||
debug!(target: "gossip", "received chain-specific gossip message");
|
||||
if let Some((hash, message)) = self.handle_incoming(protocol, who, ConsensusMessage::ChainSpecific(message, parent_hash)) {
|
||||
if let Some((hash, message)) = self.handle_incoming(protocol, who, ConsensusMessage::ChainSpecific(message, topic)) {
|
||||
debug!(target: "gossip", "handled incoming chain-specific message");
|
||||
// propagate to other peers.
|
||||
self.multicast(protocol, message, Some(hash));
|
||||
}
|
||||
}
|
||||
|
||||
/// Get a stream of messages relevant to consensus on top of a given parent hash.
|
||||
pub fn messages_for(&mut self, parent_hash: B::Hash) -> mpsc::UnboundedReceiver<ConsensusMessage<B>> {
|
||||
/// Get a stream of messages relevant to consensus for the given topic.
|
||||
pub fn messages_for(&mut self, topic: B::Hash) -> mpsc::UnboundedReceiver<ConsensusMessage<B>> {
|
||||
let (sink, stream) = mpsc::unbounded();
|
||||
|
||||
for entry in self.messages.iter() {
|
||||
let message_matches = match entry.message {
|
||||
ConsensusMessage::Bft(ref msg) => msg.parent_hash == parent_hash,
|
||||
ConsensusMessage::ChainSpecific(_, ref h) => h == &parent_hash,
|
||||
ConsensusMessage::Bft(ref msg) => msg.parent_hash == topic,
|
||||
ConsensusMessage::ChainSpecific(_, ref h) => h == &topic,
|
||||
};
|
||||
|
||||
if message_matches {
|
||||
@@ -174,14 +174,14 @@ impl<B: BlockT> ConsensusGossip<B> where B::Header: HeaderT<Number=u64> {
|
||||
}
|
||||
}
|
||||
|
||||
self.message_sink = Some((sink, parent_hash));
|
||||
self.live_message_sinks.insert(topic, sink);
|
||||
stream
|
||||
}
|
||||
|
||||
/// Multicast a chain-specific message to other authorities.
|
||||
pub fn multicast_chain_specific(&mut self, protocol: &mut Context<B>, message: Vec<u8>, parent_hash: B::Hash) {
|
||||
pub fn multicast_chain_specific(&mut self, protocol: &mut Context<B>, message: Vec<u8>, topic: B::Hash) {
|
||||
trace!(target:"gossip", "sending chain-specific message");
|
||||
self.multicast(protocol, ConsensusMessage::ChainSpecific(message, parent_hash), None);
|
||||
self.multicast(protocol, ConsensusMessage::ChainSpecific(message, topic), None);
|
||||
}
|
||||
|
||||
/// Multicast a BFT message to other authorities
|
||||
@@ -196,20 +196,21 @@ impl<B: BlockT> ConsensusGossip<B> where B::Header: HeaderT<Number=u64> {
|
||||
self.peers.remove(&who);
|
||||
}
|
||||
|
||||
/// Prune old or no longer relevant consensus messages.
|
||||
/// Supply an optional block hash where consensus is known to have concluded.
|
||||
pub fn collect_garbage(&mut self, best_hash: Option<&B::Hash>) {
|
||||
/// 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());
|
||||
|
||||
let hashes = &mut self.message_hashes;
|
||||
let before = self.messages.len();
|
||||
let now = Instant::now();
|
||||
self.messages.retain(|entry| {
|
||||
if entry.instant + MESSAGE_LIFETIME >= now &&
|
||||
best_hash.map_or(true, |parent_hash|
|
||||
match entry.message {
|
||||
ConsensusMessage::Bft(ref msg) => &msg.parent_hash != parent_hash,
|
||||
ConsensusMessage::ChainSpecific(_, ref h) => h != parent_hash,
|
||||
})
|
||||
{
|
||||
let topic = match entry.message {
|
||||
ConsensusMessage::Bft(ref msg) => &msg.parent_hash,
|
||||
ConsensusMessage::ChainSpecific(_, ref h) => h,
|
||||
};
|
||||
|
||||
if entry.instant + MESSAGE_LIFETIME >= now && predicate(topic) {
|
||||
true
|
||||
} else {
|
||||
hashes.remove(&entry.hash);
|
||||
@@ -223,7 +224,7 @@ impl<B: BlockT> ConsensusGossip<B> where B::Header: HeaderT<Number=u64> {
|
||||
}
|
||||
|
||||
fn handle_incoming(&mut self, protocol: &mut Context<B>, who: NodeIndex, message: ConsensusMessage<B>) -> Option<(B::Hash, ConsensusMessage<B>)> {
|
||||
let (hash, parent, message) = match message {
|
||||
let (hash, topic, message) = match message {
|
||||
ConsensusMessage::Bft(msg) => {
|
||||
let parent = msg.parent_hash;
|
||||
let generic = GenericMessage::BftMessage(msg);
|
||||
@@ -236,13 +237,13 @@ impl<B: BlockT> ConsensusGossip<B> where B::Header: HeaderT<Number=u64> {
|
||||
}
|
||||
)
|
||||
}
|
||||
ConsensusMessage::ChainSpecific(msg, parent) => {
|
||||
ConsensusMessage::ChainSpecific(msg, topic) => {
|
||||
let generic = GenericMessage::ChainSpecific(msg);
|
||||
(
|
||||
::protocol::hash_message::<B>(&generic),
|
||||
parent,
|
||||
topic,
|
||||
match generic {
|
||||
GenericMessage::ChainSpecific(msg) => ConsensusMessage::ChainSpecific(msg, parent),
|
||||
GenericMessage::ChainSpecific(msg) => ConsensusMessage::ChainSpecific(msg, topic),
|
||||
_ => panic!("`generic` is known to be the `ChainSpecific` variant; qed"),
|
||||
}
|
||||
)
|
||||
@@ -254,14 +255,14 @@ impl<B: BlockT> ConsensusGossip<B> where B::Header: HeaderT<Number=u64> {
|
||||
return None;
|
||||
}
|
||||
|
||||
match (protocol.client().info(), protocol.client().header(&BlockId::Hash(parent))) {
|
||||
match (protocol.client().info(), protocol.client().header(&BlockId::Hash(topic))) {
|
||||
(_, Err(e)) | (Err(e), _) => {
|
||||
debug!(target:"gossip", "Error reading blockchain: {:?}", e);
|
||||
return None;
|
||||
},
|
||||
(Ok(info), Ok(Some(header))) => {
|
||||
if header.number() < &info.chain.best_number {
|
||||
trace!(target:"gossip", "Ignored ancient message from {}, hash={}", who, parent);
|
||||
trace!(target:"gossip", "Ignored ancient message from {}, hash={}", who, topic);
|
||||
return None;
|
||||
}
|
||||
},
|
||||
@@ -269,16 +270,17 @@ impl<B: BlockT> ConsensusGossip<B> where B::Header: HeaderT<Number=u64> {
|
||||
}
|
||||
|
||||
if let Some(ref mut peer) = self.peers.get_mut(&who) {
|
||||
use std::collections::hash_map::Entry;
|
||||
peer.known_messages.insert(hash);
|
||||
if let Some((sink, parent_hash)) = self.message_sink.take() {
|
||||
if parent == parent_hash {
|
||||
debug!(target: "gossip", "Pushing relevant consensus message to sink.");
|
||||
if let Err(e) = sink.unbounded_send(message.clone()) {
|
||||
trace!(target:"gossip", "Error broadcasting message notification: {:?}", e);
|
||||
}
|
||||
if let Entry::Occupied(mut entry) = self.live_message_sinks.entry(topic) {
|
||||
debug!(target: "gossip", "Pushing relevant consensus message to sink.");
|
||||
if let Err(e) = entry.get().unbounded_send(message.clone()) {
|
||||
trace!(target:"gossip", "Error broadcasting message notification: {:?}", e);
|
||||
}
|
||||
|
||||
self.message_sink = Some((sink, parent_hash));
|
||||
if entry.get().is_closed() {
|
||||
entry.remove_entry();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
trace!(target:"gossip", "Ignored statement from unregistered peer {}", who);
|
||||
@@ -344,7 +346,7 @@ mod tests {
|
||||
consensus.message_hashes.insert(m2_hash);
|
||||
|
||||
// nothing to collect
|
||||
consensus.collect_garbage(None);
|
||||
consensus.collect_garbage(|_topic| true);
|
||||
assert_eq!(consensus.messages.len(), 2);
|
||||
assert_eq!(consensus.message_hashes.len(), 2);
|
||||
|
||||
@@ -357,13 +359,13 @@ mod tests {
|
||||
digest: Default::default(),
|
||||
};
|
||||
|
||||
consensus.collect_garbage(Some(&H256::default()));
|
||||
consensus.collect_garbage(|&topic| topic != Default::default());
|
||||
assert_eq!(consensus.messages.len(), 2);
|
||||
assert_eq!(consensus.message_hashes.len(), 2);
|
||||
|
||||
// header that matches one of the messages
|
||||
header.parent_hash = prev_hash;
|
||||
consensus.collect_garbage(Some(&prev_hash));
|
||||
consensus.collect_garbage(|topic| topic != &prev_hash);
|
||||
assert_eq!(consensus.messages.len(), 1);
|
||||
assert_eq!(consensus.message_hashes.len(), 1);
|
||||
assert!(consensus.message_hashes.contains(&m2_hash));
|
||||
@@ -371,7 +373,7 @@ mod tests {
|
||||
// make timestamp expired
|
||||
consensus.messages.clear();
|
||||
push_msg!(m2_hash, now - MESSAGE_LIFETIME, m2);
|
||||
consensus.collect_garbage(None);
|
||||
consensus.collect_garbage(|_topic| true);
|
||||
assert!(consensus.messages.is_empty());
|
||||
assert!(consensus.message_hashes.is_empty());
|
||||
}
|
||||
|
||||
@@ -232,7 +232,7 @@ enum SyncLink<'a, B: 'a + BlockT, E: 'a + ExecuteInContext<B>> {
|
||||
/// Indirect link (through service).
|
||||
Indirect(&'a RwLock<ChainSync<B>>, &'a Client<B>, &'a E),
|
||||
/// Direct references are given.
|
||||
#[cfg(test)]
|
||||
#[cfg(any(test, feature = "test-helpers"))]
|
||||
Direct(&'a mut ChainSync<B>, &'a mut Context<B>),
|
||||
}
|
||||
|
||||
@@ -402,7 +402,7 @@ impl<'a, B: 'static + BlockT, E: 'a + ExecuteInContext<B>> SyncLink<'a, B, E> {
|
||||
/// Execute closure with locked ChainSync.
|
||||
fn with_sync<F: Fn(&mut ChainSync<B>, &mut Context<B>)>(&mut self, closure: F) {
|
||||
match *self {
|
||||
#[cfg(test)]
|
||||
#[cfg(any(test, feature = "test-helpers"))]
|
||||
SyncLink::Direct(ref mut sync, ref mut protocol) =>
|
||||
closure(*sync, *protocol),
|
||||
SyncLink::Indirect(ref sync, _, ref service) =>
|
||||
@@ -417,7 +417,7 @@ impl<'a, B: 'static + BlockT, E: 'a + ExecuteInContext<B>> SyncLink<'a, B, E> {
|
||||
impl<'a, B: 'static + BlockT, E: ExecuteInContext<B>> SyncLinkApi<B> for SyncLink<'a, B, E> {
|
||||
fn chain(&self) -> &Client<B> {
|
||||
match *self {
|
||||
#[cfg(test)]
|
||||
#[cfg(any(test, feature = "test-helpers"))]
|
||||
SyncLink::Direct(_, ref protocol) => protocol.client(),
|
||||
SyncLink::Indirect(_, ref chain, _) => *chain,
|
||||
}
|
||||
@@ -447,6 +447,40 @@ impl<'a, B: 'static + BlockT, E: ExecuteInContext<B>> SyncLinkApi<B> for SyncLin
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Blocks import queue that is importing blocks in the same thread.
|
||||
/// The boolean value indicates whether blocks should be imported without instant finality.
|
||||
#[cfg(any(test, feature = "test-helpers"))]
|
||||
pub struct SyncImportQueue(pub bool);
|
||||
|
||||
#[cfg(any(test, feature = "test-helpers"))]
|
||||
impl<B: 'static + BlockT> ImportQueue<B> for SyncImportQueue {
|
||||
fn clear(&self) { }
|
||||
|
||||
fn stop(&self) { }
|
||||
|
||||
fn status(&self) -> ImportQueueStatus<B> {
|
||||
ImportQueueStatus {
|
||||
importing_count: 0,
|
||||
best_importing_number: Zero::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_importing(&self, _hash: &B::Hash) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn import_blocks(&self, sync: &mut ChainSync<B>, protocol: &mut Context<B>, blocks: (BlockOrigin, Vec<BlockData<B>>)) {
|
||||
struct DummyExecuteInContext;
|
||||
|
||||
impl<B: 'static + BlockT> ExecuteInContext<B> for DummyExecuteInContext {
|
||||
fn execute_in_context<F: Fn(&mut Context<B>)>(&self, _closure: F) { }
|
||||
}
|
||||
|
||||
import_many_blocks(&mut SyncLink::Direct::<_, DummyExecuteInContext>(sync, protocol), None, blocks, self.0);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use client;
|
||||
@@ -457,35 +491,6 @@ pub mod tests {
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use super::*;
|
||||
|
||||
/// Blocks import queue that is importing blocks in the same thread.
|
||||
/// The boolean value indicates whether blocks should be imported without instant finality.
|
||||
pub struct SyncImportQueue(pub bool);
|
||||
struct DummyExecuteInContext;
|
||||
|
||||
impl<B: 'static + BlockT> ExecuteInContext<B> for DummyExecuteInContext {
|
||||
fn execute_in_context<F: Fn(&mut Context<B>)>(&self, _closure: F) { }
|
||||
}
|
||||
|
||||
impl<B: 'static + BlockT> ImportQueue<B> for SyncImportQueue {
|
||||
fn clear(&self) { }
|
||||
|
||||
fn stop(&self) { }
|
||||
|
||||
fn status(&self) -> ImportQueueStatus<B> {
|
||||
ImportQueueStatus {
|
||||
importing_count: 0,
|
||||
best_importing_number: Zero::zero(),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_importing(&self, _hash: &B::Hash) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn import_blocks(&self, sync: &mut ChainSync<B>, protocol: &mut Context<B>, blocks: (BlockOrigin, Vec<BlockData<B>>)) {
|
||||
import_many_blocks(&mut SyncLink::Direct::<_, DummyExecuteInContext>(sync, protocol), None, blocks, self.0);
|
||||
}
|
||||
}
|
||||
|
||||
struct TestLink {
|
||||
chain: Arc<Client<Block>>,
|
||||
|
||||
@@ -37,9 +37,14 @@ extern crate rand;
|
||||
#[macro_use] extern crate error_chain;
|
||||
#[macro_use] extern crate parity_codec_derive;
|
||||
|
||||
#[cfg(test)] extern crate env_logger;
|
||||
#[cfg(test)] extern crate substrate_keyring as keyring;
|
||||
#[cfg(test)] extern crate substrate_test_client as test_client;
|
||||
#[cfg(test)]
|
||||
extern crate env_logger;
|
||||
|
||||
#[cfg(any(test, feature = "test-helpers"))]
|
||||
extern crate substrate_keyring as keyring;
|
||||
|
||||
#[cfg(any(test, feature = "test-helpers"))]
|
||||
extern crate substrate_test_client as test_client;
|
||||
|
||||
mod service;
|
||||
mod sync;
|
||||
@@ -55,7 +60,8 @@ pub mod error;
|
||||
pub mod message;
|
||||
pub mod specialization;
|
||||
|
||||
#[cfg(test)] mod test;
|
||||
#[cfg(any(test, feature = "test-helpers"))]
|
||||
pub mod test;
|
||||
|
||||
pub use chain::Client as ClientHandle;
|
||||
pub use service::{Service, FetchFuture, ConsensusService, BftMessageStream,
|
||||
|
||||
@@ -14,6 +14,9 @@
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#![allow(missing_docs)]
|
||||
|
||||
#[cfg(test)]
|
||||
mod sync;
|
||||
|
||||
use std::collections::{VecDeque, HashSet, HashMap};
|
||||
@@ -22,7 +25,6 @@ use std::sync::Arc;
|
||||
use parking_lot::RwLock;
|
||||
use client;
|
||||
use client::block_builder::BlockBuilder;
|
||||
use runtime_primitives::traits::Block as BlockT;
|
||||
use runtime_primitives::generic::BlockId;
|
||||
use io::SyncIo;
|
||||
use protocol::{Context, Protocol};
|
||||
@@ -31,27 +33,45 @@ use config::ProtocolConfig;
|
||||
use service::TransactionPool;
|
||||
use network_libp2p::{NodeIndex, SessionInfo, Severity};
|
||||
use keyring::Keyring;
|
||||
use codec::Encode;
|
||||
use import_queue::tests::SyncImportQueue;
|
||||
use codec::{Encode, Decode};
|
||||
use import_queue::SyncImportQueue;
|
||||
use test_client::{self, TestClient};
|
||||
use test_client::runtime::{Block, Hash, Transfer, Extrinsic};
|
||||
use specialization::Specialization;
|
||||
use consensus_gossip::ConsensusGossip;
|
||||
|
||||
pub struct DummySpecialization;
|
||||
pub use test_client::runtime::{Block, Hash, Transfer, Extrinsic};
|
||||
|
||||
/// The test specialization.
|
||||
pub struct DummySpecialization {
|
||||
/// Consensus gossip handle.
|
||||
pub gossip: ConsensusGossip<Block>,
|
||||
}
|
||||
|
||||
#[derive(Encode, Decode)]
|
||||
pub struct GossipMessage {
|
||||
/// The topic to classify under.
|
||||
pub topic: Hash,
|
||||
/// The data to send.
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Specialization<Block> for DummySpecialization {
|
||||
fn status(&self) -> Vec<u8> { vec![] }
|
||||
|
||||
fn on_connect(&mut self, _ctx: &mut Context<Block>, _peer_id: NodeIndex, _status: ::message::Status<Block>) {
|
||||
|
||||
fn on_connect(&mut self, ctx: &mut Context<Block>, peer_id: NodeIndex, status: ::message::Status<Block>) {
|
||||
self.gossip.new_peer(ctx, peer_id, status.roles);
|
||||
}
|
||||
|
||||
fn on_disconnect(&mut self, _ctx: &mut Context<Block>, _peer_id: NodeIndex) {
|
||||
|
||||
fn on_disconnect(&mut self, ctx: &mut Context<Block>, peer_id: NodeIndex) {
|
||||
self.gossip.peer_disconnected(ctx, peer_id);
|
||||
}
|
||||
|
||||
fn on_message(&mut self, _ctx: &mut Context<Block>, _peer_id: NodeIndex, _message: ::message::Message<Block>) {
|
||||
|
||||
fn on_message(&mut self, ctx: &mut Context<Block>, peer_id: NodeIndex, message: ::message::Message<Block>) {
|
||||
if let ::message::generic::Message::ChainSpecific(data) = message {
|
||||
let gossip_message = GossipMessage::decode(&mut &data[..])
|
||||
.expect("gossip messages all in known format; qed");
|
||||
self.gossip.on_chain_specific(ctx, peer_id, data, gossip_message.topic)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,19 +193,30 @@ impl Peer {
|
||||
fn flush(&self) {
|
||||
}
|
||||
|
||||
fn generate_blocks<F>(&self, count: usize, mut edit_block: F)
|
||||
where F: FnMut(&mut BlockBuilder<test_client::Backend, test_client::Executor, Block, Blake2Hasher>)
|
||||
/// Push a message into the gossip network and relay to peers.
|
||||
/// `TestNet::sync_step` needs to be called to ensure it's propagated.
|
||||
pub fn gossip_message(&self, topic: Hash, data: Vec<u8>) {
|
||||
self.sync.with_spec(&mut TestIo::new(&self.queue, None), |spec, ctx| {
|
||||
let message = GossipMessage { topic, data }.encode();
|
||||
spec.gossip.multicast_chain_specific(ctx, message, topic);
|
||||
})
|
||||
}
|
||||
|
||||
/// Add blocks to the peer -- edit the block before adding
|
||||
pub fn generate_blocks<F>(&self, count: usize, mut edit_block: F)
|
||||
where F: FnMut(&mut BlockBuilder<test_client::Backend, test_client::Executor, Block, Blake2Hasher>)
|
||||
{
|
||||
for _ in 0 .. count {
|
||||
let mut builder = self.client.new_block().unwrap();
|
||||
edit_block(&mut builder);
|
||||
let block = builder.bake().unwrap();
|
||||
trace!("Generating {}, (#{})", block.hash(), block.header.number);
|
||||
trace!("Generating {}, (#{}, parent={})", block.header.hash(), block.header.number, block.header.parent_hash);
|
||||
self.client.justify_and_import(client::BlockOrigin::File, block).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn push_blocks(&self, count: usize, with_tx: bool) {
|
||||
/// Push blocks to the peer (simplified: with or without a TX)
|
||||
pub fn push_blocks(&self, count: usize, with_tx: bool) {
|
||||
let mut nonce = 0;
|
||||
if with_tx {
|
||||
self.generate_blocks(count, |builder| {
|
||||
@@ -203,6 +234,18 @@ impl Peer {
|
||||
self.generate_blocks(count, |_| ());
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute a function with specialization for this peer.
|
||||
pub fn with_spec<F, U>(&self, f: F) -> U
|
||||
where F: FnOnce(&mut DummySpecialization, &mut Context<Block>) -> U
|
||||
{
|
||||
self.sync.with_spec(&mut TestIo::new(&self.queue, None), f)
|
||||
}
|
||||
|
||||
/// Get a reference to the client.
|
||||
pub fn client(&self) -> &Arc<client::Client<test_client::Backend, test_client::Executor, Block>> {
|
||||
&self.client
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EmptyTransactionPool;
|
||||
@@ -226,11 +269,13 @@ pub struct TestNet {
|
||||
}
|
||||
|
||||
impl TestNet {
|
||||
fn new(n: usize) -> Self {
|
||||
/// Create new test network with this many peers.
|
||||
pub fn new(n: usize) -> Self {
|
||||
Self::new_with_config(n, ProtocolConfig::default())
|
||||
}
|
||||
|
||||
fn new_with_config(n: usize, config: ProtocolConfig) -> Self {
|
||||
/// Create new test network with peers and given config.
|
||||
pub fn new_with_config(n: usize, config: ProtocolConfig) -> Self {
|
||||
let mut net = TestNet {
|
||||
peers: Vec::new(),
|
||||
started: false,
|
||||
@@ -243,11 +288,23 @@ impl TestNet {
|
||||
net
|
||||
}
|
||||
|
||||
/// Add a peer.
|
||||
pub fn add_peer(&mut self, config: &ProtocolConfig) {
|
||||
let client = Arc::new(test_client::new());
|
||||
let tx_pool = Arc::new(EmptyTransactionPool);
|
||||
let import_queue = Arc::new(SyncImportQueue(false));
|
||||
let sync = Protocol::new(config.clone(), client.clone(), import_queue, None, tx_pool, DummySpecialization).unwrap();
|
||||
let specialization = DummySpecialization {
|
||||
gossip: ConsensusGossip::new(),
|
||||
};
|
||||
let sync = Protocol::new(
|
||||
config.clone(),
|
||||
client.clone(),
|
||||
import_queue,
|
||||
None,
|
||||
tx_pool,
|
||||
specialization
|
||||
).unwrap();
|
||||
|
||||
self.peers.push(Arc::new(Peer {
|
||||
sync: sync,
|
||||
client: client,
|
||||
@@ -255,10 +312,12 @@ impl TestNet {
|
||||
}));
|
||||
}
|
||||
|
||||
/// Get reference to peer.
|
||||
pub fn peer(&self, i: usize) -> &Peer {
|
||||
&self.peers[i]
|
||||
}
|
||||
|
||||
/// Start network.
|
||||
fn start(&mut self) {
|
||||
if self.started {
|
||||
return;
|
||||
@@ -274,7 +333,8 @@ impl TestNet {
|
||||
self.started = true;
|
||||
}
|
||||
|
||||
fn sync_step(&mut self) {
|
||||
/// Do one step of routing.
|
||||
pub fn route(&mut self) {
|
||||
for peer in 0..self.peers.len() {
|
||||
let packet = self.peers[peer].pending_message();
|
||||
if let Some(packet) = packet {
|
||||
@@ -294,20 +354,32 @@ impl TestNet {
|
||||
self.peers[*d].on_disconnect(peer as NodeIndex);
|
||||
}
|
||||
}
|
||||
|
||||
self.sync_step_peer(peer);
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_step_peer(&mut self, peer_num: usize) {
|
||||
self.peers[peer_num].sync_step();
|
||||
/// Route messages between peers until all queues are empty.
|
||||
pub fn route_until_complete(&mut self) {
|
||||
while !self.done() {
|
||||
self.route()
|
||||
}
|
||||
}
|
||||
|
||||
fn restart_peer(&mut self, i: usize) {
|
||||
/// Do a step of synchronization.
|
||||
pub fn sync_step(&mut self) {
|
||||
self.route();
|
||||
|
||||
for peer in &mut self.peers {
|
||||
peer.sync_step();
|
||||
}
|
||||
}
|
||||
|
||||
/// Restart sync for a peer.
|
||||
pub fn restart_peer(&mut self, i: usize) {
|
||||
self.peers[i].restart_sync();
|
||||
}
|
||||
|
||||
fn sync(&mut self) -> u32 {
|
||||
/// Perform synchronization until complete.
|
||||
pub fn sync(&mut self) -> u32 {
|
||||
self.start();
|
||||
let mut total_steps = 0;
|
||||
while !self.done() {
|
||||
@@ -317,14 +389,16 @@ impl TestNet {
|
||||
total_steps
|
||||
}
|
||||
|
||||
fn sync_steps(&mut self, count: usize) {
|
||||
/// Do the given amount of sync steps.
|
||||
pub fn sync_steps(&mut self, count: usize) {
|
||||
self.start();
|
||||
for _ in 0..count {
|
||||
self.sync_step();
|
||||
}
|
||||
}
|
||||
|
||||
fn done(&self) -> bool {
|
||||
/// Whether all peers have synced.
|
||||
pub fn done(&self) -> bool {
|
||||
self.peers.iter().all(|p| p.is_done())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user