Light GRANDPA import handler (#1669)

* GrandpaLightBlockImport

* extract authorities in AuraVerifier

* post-merge fix

* restore authorities cache

* license

* new finality proof draft

* generalized PendingJustifications

* finality proof messages

* fixed compilation

* pass verifier to import_finality_proof

* do not fetch remote proof from light import directly

* FinalityProofProvider

* fixed authorities cache test

* restored finality proof tests

* finality_proof docs

* use DB backend in test client

* justification_is_fetched_by_light_client_when_consensus_data_changes

* restore justification_is_fetched_by_light_client_when_consensus_data_changes

* some more tests

* added authorities-related TODO

* removed unneeded clear_finality_proof_requests field

* truncated some long lines

* more granular light import tests

* only provide finality proof if it is generated by the requested set

* post-merge fix

* finality_proof_is_none_if_first_justification_is_generated_by_unknown_set

* make light+grandpa test rely on finality proofs (instead of simple justifications)

* empty_finality_proof_is_returned_to_light_client_when_authority_set_is_different

* missing trait method impl

* fixed proof-of-finality docs

* one more doc fix

* fix docs

* initialize authorities cache (post-merge fix)

* fixed cache initialization (post-merge fix)

* post-fix merge: fix light + GRANDPA tests (bad way)

* proper fix of empty_finality_proof_is_returned_to_light_client_when_authority_set_is_different

* fixed easy grumbles

* import finality proofs in BlockImportWorker thread

* allow import of finality proofs for non-requested blocks

* limit number of fragments in finality proof

* GRANDPA post-merge fix

* BABE: pos-merge fix
This commit is contained in:
Svyatoslav Nikolsky
2019-05-13 12:36:52 +03:00
committed by Gavin Wood
parent 258f0835e4
commit 22586113ea
36 changed files with 3320 additions and 803 deletions
@@ -22,6 +22,8 @@ use std::borrow::Cow;
use std::collections::HashMap;
use crate::well_known_cache_keys;
use crate::import_queue::Verifier;
/// Block import result.
#[derive(Debug, PartialEq, Eq)]
pub enum ImportResult {
@@ -44,6 +46,8 @@ pub struct ImportedAux {
pub needs_justification: bool,
/// Received a bad justification.
pub bad_justification: bool,
/// Request a finality proof for the given block.
pub needs_finality_proof: bool,
}
impl Default for ImportedAux {
@@ -52,6 +56,7 @@ impl Default for ImportedAux {
clear_justification_requests: false,
needs_justification: false,
bad_justification: false,
needs_finality_proof: false,
}
}
}
@@ -202,3 +207,26 @@ pub trait JustificationImport<B: BlockT> {
justification: Justification,
) -> Result<(), Self::Error>;
}
/// Finality proof import trait.
pub trait FinalityProofImport<B: BlockT> {
type Error: ::std::error::Error + Send + 'static;
/// Called by the import queue when it is started.
fn on_start(&self, _link: &crate::import_queue::Link<B>) { }
/// Import a Block justification and finalize the given block. Returns finalized block or error.
fn import_finality_proof(
&self,
hash: B::Hash,
number: NumberFor<B>,
finality_proof: Vec<u8>,
verifier: &Verifier<B>,
) -> Result<(B::Hash, NumberFor<B>), Self::Error>;
}
/// Finality proof request builder.
pub trait FinalityProofRequestBuilder<B: BlockT>: Send {
/// Build data blob, associated with the request.
fn build_request_data(&self, hash: &B::Hash) -> Vec<u8>;
}
@@ -27,6 +27,7 @@
use crate::block_import::{
BlockImport, BlockOrigin, ImportBlock, ImportedAux, ImportResult, JustificationImport,
FinalityProofImport, FinalityProofRequestBuilder,
};
use crossbeam_channel::{self as channel, Receiver, Sender};
use parity_codec::Encode;
@@ -57,6 +58,12 @@ pub type SharedBlockImport<B> = Arc<dyn BlockImport<B, Error = ConsensusError> +
/// Shared justification import struct used by the queue.
pub type SharedJustificationImport<B> = Arc<dyn JustificationImport<B, Error=ConsensusError> + Send + Sync>;
/// Shared finality proof import struct used by the queue.
pub type SharedFinalityProofImport<B> = Arc<dyn FinalityProofImport<B, Error=ConsensusError> + Send + Sync>;
/// Shared finality proof request builder struct used by the queue.
pub type SharedFinalityProofRequestBuilder<B> = Arc<dyn FinalityProofRequestBuilder<B> + Send + Sync>;
/// Maps to the Origin used by the network.
pub type Origin = libp2p::PeerId;
@@ -76,7 +83,7 @@ pub struct IncomingBlock<B: BlockT> {
}
/// Verify a justification of a block
pub trait Verifier<B: BlockT>: Send + Sync + Sized {
pub trait Verifier<B: BlockT>: Send + Sync {
/// Verify the given data and return the ImportBlock and an optional
/// new set of validators to import. If not, err with an Error-Message
/// presented to the User in the logs.
@@ -104,6 +111,8 @@ pub trait ImportQueue<B: BlockT>: Send + Sync + ImportQueueClone<B> {
fn import_blocks(&self, origin: BlockOrigin, blocks: Vec<IncomingBlock<B>>);
/// Import a block justification.
fn import_justification(&self, who: Origin, hash: B::Hash, number: NumberFor<B>, justification: Justification);
/// Import block finality proof.
fn import_finality_proof(&self, who: Origin, hash: B::Hash, number: NumberFor<B>, finality_proof: Vec<u8>);
}
pub trait ImportQueueClone<B: BlockT> {
@@ -129,6 +138,7 @@ impl<B: BlockT> ImportQueueClone<B> for BasicQueue<B> {
}
}
/// "BasicQueue" is a wrapper around a channel sender to the "BlockImporter".
/// "BasicQueue" itself does not keep any state or do any importing work, and
/// can therefore be send to other threads.
@@ -153,11 +163,25 @@ impl<B: BlockT> BasicQueue<B> {
pub fn new<V: 'static + Verifier<B>>(
verifier: Arc<V>,
block_import: SharedBlockImport<B>,
justification_import: Option<SharedJustificationImport<B>>
justification_import: Option<SharedJustificationImport<B>>,
finality_proof_import: Option<SharedFinalityProofImport<B>>,
finality_proof_request_builder: Option<SharedFinalityProofRequestBuilder<B>>,
) -> Self {
let (result_sender, result_port) = channel::unbounded();
let worker_sender = BlockImportWorker::new(result_sender, verifier, block_import);
let importer_sender = BlockImporter::new(result_port, worker_sender, justification_import);
let worker_sender = BlockImportWorker::new(
result_sender,
verifier.clone(),
block_import,
finality_proof_import.clone(),
);
let importer_sender = BlockImporter::new(
result_port,
worker_sender,
verifier,
justification_import,
finality_proof_import,
finality_proof_request_builder,
);
Self {
sender: importer_sender,
@@ -210,25 +234,36 @@ impl<B: BlockT> ImportQueue<B> for BasicQueue<B> {
.send(BlockImportMsg::ImportJustification(who.clone(), hash, number, justification))
.expect("1. self is holding a sender to the Importer, 2. Importer should handle messages while there are senders around; qed");
}
fn import_finality_proof(&self, who: Origin, hash: B::Hash, number: NumberFor<B>, finality_proof: Vec<u8>) {
let _ = self
.sender
.send(BlockImportMsg::ImportFinalityProof(who, hash, number, finality_proof))
.expect("1. self is holding a sender to the Importer, 2. Importer should handle messages while there are senders around; qed");
}
}
pub enum BlockImportMsg<B: BlockT> {
ImportBlocks(BlockOrigin, Vec<IncomingBlock<B>>),
ImportJustification(Origin, B::Hash, NumberFor<B>, Justification),
ImportFinalityProof(Origin, B::Hash, NumberFor<B>, Vec<u8>),
Start(Box<Link<B>>, Sender<Result<(), std::io::Error>>),
Stop,
#[cfg(any(test, feature = "test-helpers"))]
Synchronize,
}
#[cfg_attr(test, derive(Debug, PartialEq))]
pub enum BlockImportWorkerMsg<B: BlockT> {
ImportBlocks(BlockOrigin, Vec<IncomingBlock<B>>),
Imported(
ImportedBlocks(
Vec<(
Result<BlockImportResult<NumberFor<B>>, BlockImportError>,
B::Hash,
)>,
),
ImportFinalityProof(Origin, B::Hash, NumberFor<B>, Vec<u8>),
ImportedFinalityProof(Origin, (B::Hash, NumberFor<B>), Result<(B::Hash, NumberFor<B>), ()>),
#[cfg(any(test, feature = "test-helpers"))]
Synchronize,
}
@@ -243,14 +278,20 @@ struct BlockImporter<B: BlockT> {
result_port: Receiver<BlockImportWorkerMsg<B>>,
worker_sender: Sender<BlockImportWorkerMsg<B>>,
link: Option<Box<dyn Link<B>>>,
verifier: Arc<Verifier<B>>,
justification_import: Option<SharedJustificationImport<B>>,
finality_proof_import: Option<SharedFinalityProofImport<B>>,
finality_proof_request_builder: Option<SharedFinalityProofRequestBuilder<B>>,
}
impl<B: BlockT> BlockImporter<B> {
fn new(
result_port: Receiver<BlockImportWorkerMsg<B>>,
worker_sender: Sender<BlockImportWorkerMsg<B>>,
verifier: Arc<Verifier<B>>,
justification_import: Option<SharedJustificationImport<B>>,
finality_proof_import: Option<SharedFinalityProofImport<B>>,
finality_proof_request_builder: Option<SharedFinalityProofRequestBuilder<B>>,
) -> Sender<BlockImportMsg<B>> {
trace!(target: "block_import", "Creating new Block Importer!");
let (sender, port) = channel::bounded(4);
@@ -262,7 +303,10 @@ impl<B: BlockT> BlockImporter<B> {
result_port,
worker_sender,
link: None,
verifier,
justification_import,
finality_proof_import,
finality_proof_request_builder,
};
while importer.run() {
// Importing until all senders have been dropped...
@@ -303,10 +347,19 @@ impl<B: BlockT> BlockImporter<B> {
BlockImportMsg::ImportJustification(who, hash, number, justification) => {
self.handle_import_justification(who, hash, number, justification)
},
BlockImportMsg::ImportFinalityProof(who, hash, number, finality_proof) => {
self.handle_import_finality_proof(who, hash, number, finality_proof)
},
BlockImportMsg::Start(link, sender) => {
if let Some(finality_proof_request_builder) = self.finality_proof_request_builder.take() {
link.set_finality_proof_request_builder(finality_proof_request_builder);
}
if let Some(justification_import) = self.justification_import.as_ref() {
justification_import.on_start(&*link);
}
if let Some(finality_proof_import) = self.finality_proof_import.as_ref() {
finality_proof_import.on_start(&*link);
}
self.link = Some(link);
let _ = sender.send(Ok(()));
},
@@ -332,14 +385,20 @@ impl<B: BlockT> BlockImporter<B> {
};
let results = match msg {
BlockImportWorkerMsg::Imported(results) => (results),
BlockImportWorkerMsg::ImportedBlocks(results) => (results),
BlockImportWorkerMsg::ImportedFinalityProof(who, request_block, finalization_result) => {
link.finality_proof_imported(who, request_block, finalization_result);
return true;
},
#[cfg(any(test, feature = "test-helpers"))]
BlockImportWorkerMsg::Synchronize => {
trace!(target: "sync", "Synchronizing link");
link.synchronized();
return true;
},
_ => unreachable!("Import Worker does not send ImportBlocks message; qed"),
BlockImportWorkerMsg::ImportBlocks(_, _)
| BlockImportWorkerMsg::ImportFinalityProof(_, _, _, _)
=> unreachable!("Import Worker does not send Import* message; qed"),
};
let mut has_error = false;
let mut hashes = vec![];
@@ -375,6 +434,11 @@ impl<B: BlockT> BlockImporter<B> {
link.report_peer(peer, BAD_JUSTIFICATION_REPUTATION_CHANGE);
}
}
if aux.needs_finality_proof {
trace!(target: "sync", "Block imported but requires finality proof {}: {:?}", number, hash);
link.request_finality_proof(&hash, number);
}
},
Err(BlockImportError::IncompleteHeader(who)) => {
if let Some(peer) = who {
@@ -422,6 +486,13 @@ impl<B: BlockT> BlockImporter<B> {
}
}
fn handle_import_finality_proof(&self, who: Origin, hash: B::Hash, number: NumberFor<B>, finality_proof: Vec<u8>) {
trace!(target: "sync", "Scheduling finality proof of {}/{} for import", number, hash);
self.worker_sender
.send(BlockImportWorkerMsg::ImportFinalityProof(who, hash, number, finality_proof))
.expect("1. This is holding a sender to the worker, 2. the worker should not quit while a sender is still held; qed");
}
fn handle_import_blocks(&mut self, origin: BlockOrigin, blocks: Vec<IncomingBlock<B>>) {
trace!(target: "sync", "Scheduling {} blocks for import", blocks.len());
self.worker_sender
@@ -433,6 +504,7 @@ impl<B: BlockT> BlockImporter<B> {
struct BlockImportWorker<B: BlockT, V: Verifier<B>> {
result_sender: Sender<BlockImportWorkerMsg<B>>,
block_import: SharedBlockImport<B>,
finality_proof_import: Option<SharedFinalityProofImport<B>>,
verifier: Arc<V>,
}
@@ -441,6 +513,7 @@ impl<B: BlockT, V: 'static + Verifier<B>> BlockImportWorker<B, V> {
result_sender: Sender<BlockImportWorkerMsg<B>>,
verifier: Arc<V>,
block_import: SharedBlockImport<B>,
finality_proof_import: Option<SharedFinalityProofImport<B>>,
) -> Sender<BlockImportWorkerMsg<B>> {
let (sender, port) = channel::bounded(4);
let _ = thread::Builder::new()
@@ -450,6 +523,7 @@ impl<B: BlockT, V: 'static + Verifier<B>> BlockImportWorker<B, V> {
result_sender,
verifier,
block_import,
finality_proof_import,
};
for msg in port.iter() {
// Working until all senders have been dropped...
@@ -457,12 +531,17 @@ impl<B: BlockT, V: 'static + Verifier<B>> BlockImportWorker<B, V> {
BlockImportWorkerMsg::ImportBlocks(origin, blocks) => {
worker.import_a_batch_of_blocks(origin, blocks);
},
BlockImportWorkerMsg::ImportFinalityProof(who, hash, number, proof) => {
worker.import_finality_proof(who, hash, number, proof);
},
#[cfg(any(test, feature = "test-helpers"))]
BlockImportWorkerMsg::Synchronize => {
trace!(target: "sync", "Sending sync message");
let _ = worker.result_sender.send(BlockImportWorkerMsg::Synchronize);
},
_ => unreachable!("Import Worker does not receive the Imported message; qed"),
BlockImportWorkerMsg::ImportedBlocks(_)
| BlockImportWorkerMsg::ImportedFinalityProof(_, _, _)
=> unreachable!("Import Worker does not receive the Imported* messages; qed"),
}
}
})
@@ -512,10 +591,31 @@ impl<B: BlockT, V: 'static + Verifier<B>> BlockImportWorker<B, V> {
let _ = self
.result_sender
.send(BlockImportWorkerMsg::Imported(results));
.send(BlockImportWorkerMsg::ImportedBlocks(results));
trace!(target: "sync", "Imported {} of {}", imported, count);
}
fn import_finality_proof(&self, who: Origin, hash: B::Hash, number: NumberFor<B>, finality_proof: Vec<u8>) {
let result = self.finality_proof_import.as_ref().map(|finality_proof_import| {
finality_proof_import.import_finality_proof(hash, number, finality_proof, &*self.verifier)
.map_err(|e| {
debug!(
"Finality proof import failed with {:?} for hash: {:?} number: {:?} coming from node: {:?}",
e,
hash,
number,
who,
);
})
}).unwrap_or(Err(()));
let _ = self
.result_sender
.send(BlockImportWorkerMsg::ImportedFinalityProof(who, (hash, number), result));
trace!(target: "sync", "Imported finality proof for {}/{}", number, hash);
}
}
/// Hooks that the verification queue can use to influence the synchronization
@@ -531,6 +631,21 @@ pub trait Link<B: BlockT>: Send {
fn clear_justification_requests(&self) {}
/// Request a justification for the given block.
fn request_justification(&self, _hash: &B::Hash, _number: NumberFor<B>) {}
/// Finality proof import result.
///
/// Even though we have asked for finality proof of block A, provider could return proof of
/// some earlier block B, if the proof for A was too large. The sync module should continue
/// asking for proof of A in this case.
fn finality_proof_imported(
&self,
_who: Origin,
_request_block: (B::Hash, NumberFor<B>),
_finalization_result: Result<(B::Hash, NumberFor<B>), ()>,
) {}
/// Request a finality proof for the given block.
fn request_finality_proof(&self, _hash: &B::Hash, _number: NumberFor<B>) {}
/// Remember finality proof request builder on start.
fn set_finality_proof_request_builder(&self, _request_builder: SharedFinalityProofRequestBuilder<B>) {}
/// Adjusts the reputation of the given peer.
fn report_peer(&self, _who: Origin, _reputation_change: i32) {}
/// Restart sync.
@@ -637,12 +752,14 @@ pub fn import_single_block<B: BlockT, V: Verifier<B>>(
#[cfg(test)]
mod tests {
use super::*;
use crate::block_import::ForkChoiceStrategy;
use libp2p::PeerId;
use test_client::runtime::{Block, Hash};
#[derive(Debug, PartialEq)]
enum LinkMsg {
BlockImported,
FinalityProofImported,
Disconnected,
Restarted,
}
@@ -664,6 +781,14 @@ mod tests {
fn block_imported(&self, _hash: &Hash, _number: NumberFor<Block>) {
let _ = self.sender.send(LinkMsg::BlockImported);
}
fn finality_proof_imported(
&self,
_: Origin,
_: (Hash, NumberFor<Block>),
_: Result<(Hash, NumberFor<Block>), ()>,
) {
let _ = self.sender.send(LinkMsg::FinalityProofImported);
}
fn report_peer(&self, _: Origin, _: i32) {
let _ = self.sender.send(LinkMsg::Disconnected);
}
@@ -672,12 +797,33 @@ mod tests {
}
}
impl<B: BlockT> Verifier<B> for () {
fn verify(
&self,
origin: BlockOrigin,
header: B::Header,
justification: Option<Justification>,
body: Option<Vec<B::Extrinsic>>,
) -> Result<(ImportBlock<B>, Option<Vec<AuthorityIdFor<B>>>), String> {
Ok((ImportBlock {
origin,
header,
body,
finalized: false,
justification,
post_digests: vec![],
auxiliary: Vec::new(),
fork_choice: ForkChoiceStrategy::LongestChain,
}, None))
}
}
#[test]
fn process_import_result_works() {
let (result_sender, result_port) = channel::unbounded();
let (worker_sender, _) = channel::unbounded();
let (link_sender, link_port) = channel::unbounded();
let importer_sender = BlockImporter::<Block>::new(result_port, worker_sender, None);
let importer_sender = BlockImporter::<Block>::new(result_port, worker_sender, Arc::new(()), None, None, None);
let link = TestLink::new(link_sender);
let (ack_sender, start_ack_port) = channel::bounded(4);
let _ = importer_sender.send(BlockImportMsg::Start(Box::new(link.clone()), ack_sender));
@@ -687,52 +833,101 @@ mod tests {
// Send a known
let results = vec![(Ok(BlockImportResult::ImportedKnown(Default::default())), Default::default())];
let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap();
let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap();
assert_eq!(link_port.recv(), Ok(LinkMsg::BlockImported));
// Send a second known
let results = vec![(Ok(BlockImportResult::ImportedKnown(Default::default())), Default::default())];
let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap();
let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap();
assert_eq!(link_port.recv(), Ok(LinkMsg::BlockImported));
// Send an unknown
let results = vec![(Ok(BlockImportResult::ImportedUnknown(Default::default(), Default::default(), None)), Default::default())];
let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap();
let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap();
assert_eq!(link_port.recv(), Ok(LinkMsg::BlockImported));
// Send an unknown with peer and bad justification
let peer_id = PeerId::random();
let results = vec![(Ok(BlockImportResult::ImportedUnknown(Default::default(),
ImportedAux { needs_justification: true, clear_justification_requests: false, bad_justification: true },
ImportedAux {
needs_justification: true,
clear_justification_requests: false,
bad_justification: true,
needs_finality_proof: false,
},
Some(peer_id.clone()))), Default::default())];
let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap();
let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap();
assert_eq!(link_port.recv(), Ok(LinkMsg::BlockImported));
assert_eq!(link_port.recv(), Ok(LinkMsg::Disconnected));
// Send an incomplete header
let results = vec![(Err(BlockImportError::IncompleteHeader(Some(peer_id.clone()))), Default::default())];
let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap();
let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap();
assert_eq!(link_port.recv(), Ok(LinkMsg::Disconnected));
assert_eq!(link_port.recv(), Ok(LinkMsg::Restarted));
// Send an unknown parent
let results = vec![(Err(BlockImportError::UnknownParent), Default::default())];
let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap();
let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap();
assert_eq!(link_port.recv(), Ok(LinkMsg::Restarted));
// Send a verification failed
let results = vec![(Err(BlockImportError::VerificationFailed(Some(peer_id.clone()), String::new())), Default::default())];
let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap();
let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap();
assert_eq!(link_port.recv(), Ok(LinkMsg::Disconnected));
assert_eq!(link_port.recv(), Ok(LinkMsg::Restarted));
// Send an error
let results = vec![(Err(BlockImportError::Error), Default::default())];
let _ = result_sender.send(BlockImportWorkerMsg::Imported(results)).ok().unwrap();
let _ = result_sender.send(BlockImportWorkerMsg::ImportedBlocks(results)).ok().unwrap();
assert_eq!(link_port.recv(), Ok(LinkMsg::Restarted));
// Drop the importer sender first, ensuring graceful shutdown.
drop(importer_sender);
}
#[test]
fn process_finality_proof_import_result_works() {
let (result_sender, result_port) = channel::unbounded();
let (worker_sender, worker_receiver) = channel::unbounded();
let (link_sender, link_port) = channel::unbounded();
let importer_sender = BlockImporter::<Block>::new(result_port, worker_sender, Arc::new(()), None, None, None);
let link = TestLink::new(link_sender);
let (ack_sender, start_ack_port) = channel::bounded(4);
let _ = importer_sender.send(BlockImportMsg::Start(Box::new(link.clone()), ack_sender));
let who = Origin::random();
// Ensure the importer handles Start before any result messages.
start_ack_port.recv().unwrap().unwrap();
// Send finality proof import request to BlockImporter
importer_sender.send(BlockImportMsg::ImportFinalityProof(
who.clone(),
Default::default(),
1,
vec![42],
)).unwrap();
// Wait until this request is redirected to the BlockImportWorker
assert_eq!(worker_receiver.recv(), Ok(BlockImportWorkerMsg::ImportFinalityProof(
who.clone(),
Default::default(),
1,
vec![42],
)));
// Send ack of proof import from BlockImportWorker to BlockImporter
result_sender.send(BlockImportWorkerMsg::ImportedFinalityProof(
who.clone(),
(Default::default(), 0),
Ok((Default::default(), 0)),
)).unwrap();
// Wait for finality proof import result
assert_eq!(link_port.recv(), Ok(LinkMsg::FinalityProofImported));
// Drop the importer sender first, ensuring graceful shutdown.
drop(importer_sender);
}
}
+2 -1
View File
@@ -49,7 +49,8 @@ const MAX_BLOCK_SIZE: usize = 4 * 1024 * 1024 + 512;
pub use self::error::{Error, ErrorKind};
pub use block_import::{
BlockImport, BlockOrigin, ForkChoiceStrategy, ImportedAux, ImportBlock, ImportResult, JustificationImport,
BlockImport, BlockOrigin, ForkChoiceStrategy, ImportedAux, ImportBlock, ImportResult,
JustificationImport, FinalityProofImport, FinalityProofRequestBuilder,
};
pub use select_chain::SelectChain;