Storing multiple Justifications per block (#7640)

* primitives/runtime: initial changes on supporting multiple Justifications

* primitives/runtime: make Justifications strongly typed

* Encode/decode Justifications

* primitives/runtime: add Justification type

* backend: apply_finality and finalize_block takes a single Justification

* manual-seal: create engine id and let rpc take encoded justification

* backend: skeleton functions for appending justifications

* backend: initial implementation append_justification

Initial implementation of append_justification on the Backend trait, and also remove unused skeleton
functions for append_justificaton on Finaziler trait.
k

* backend: guard against duplicate consensus engine id

* client/db: add check for block finality

* client/api: add append_justification to in_mem db

* client/light: add no-op append_justification

* network: fix decode call for Justification

* network: only send a single Justification in BlockData

* network: minor comment update

* protocol: update field names to distinguish single justification

* client: further field renames to plural

* client: update function names to plural justifications

* client/db: upgrade existing database for new format

* network: remove dependency on grandpa crate

* db: fix check for finalized block

* grandpa: check for multiple grandpa justifications hwne importing

* backend: update Finalizer trait to take multiple Justifications

* db: remove debugging statements in migration code

* manual-seal: update note about engine id

* db: fix check for finalized block

* client: update variable name to reflect it is now plural

* grandpa: fix incorrect empty Justications in test

* primitives: make Justifications opaque to avoid being empty

* network: fix detecting empty Justification

* runtime: doc strings for Justifications functions

* runtime: add into_justifications

* primitives: check for duplicates in when adding to Justifications

* network/test: use real grandpa engine id in test

* client: fix reviewer comments

* primitives: rename Justifications::push to append

* backend: revert changes to Finalizer trait

* backend: revert mark_finalized

* backend: revert changes to finalize_block

* backend: revert finalized_blocks

* db: add a quick early return for performance

* client: minor reviewer comments

* service/test: use local ConsensusEngineId

* network: add link to issue for sending multiple Justifications

* Apply suggestions from code review

Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>

* Apply suggestions from code review

Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>

* network: tweaks to review suggestions

* network: revert change to BlockData for backwards compatibility

* Apply suggestion from code review

Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>

* Apply suggestions from code review

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>

* primitives: update doc comment for Justifications

* client/db/upgrade: avoid grandpa crate dependency

* consensus: revert to single Justification for import_justification

* primitives: improve justifications docs

* style cleanups

* use and_then

* client: rename JUSTIFICATIONS db column

* network: revert to using FRNK in network-test

Co-authored-by: Pierre Krieger <pierre.krieger1708@gmail.com>
Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
Co-authored-by: André Silva <andrerfosilva@gmail.com>
This commit is contained in:
Jon Häggblad
2021-03-17 22:18:16 +01:00
committed by GitHub
parent c7d32ba9a6
commit 0d6884b919
43 changed files with 635 additions and 270 deletions
@@ -21,7 +21,7 @@ use std::sync::Arc;
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use sp_runtime::generic::BlockId;
use sp_runtime::Justification;
use sp_runtime::Justifications;
use log::warn;
use parking_lot::RwLock;
@@ -84,8 +84,8 @@ pub trait HeaderBackend<Block: BlockT>: Send + Sync {
pub trait Backend<Block: BlockT>: HeaderBackend<Block> + HeaderMetadata<Block, Error=Error> {
/// Get block body. Returns `None` if block is not found.
fn body(&self, id: BlockId<Block>) -> Result<Option<Vec<<Block as BlockT>::Extrinsic>>>;
/// Get block justification. Returns `None` if justification does not exist.
fn justification(&self, id: BlockId<Block>) -> Result<Option<Justification>>;
/// Get block justifications. Returns `None` if no justification exists.
fn justifications(&self, id: BlockId<Block>) -> Result<Option<Justifications>>;
/// Get last finalized block hash.
fn last_finalized(&self) -> Result<Block::Hash>;
/// Returns data cache reference, if it is enabled on this backend.
@@ -18,7 +18,7 @@
//! Block import helpers.
use sp_runtime::traits::{Block as BlockT, DigestItemFor, Header as HeaderT, NumberFor, HashFor};
use sp_runtime::Justification;
use sp_runtime::{Justification, Justifications};
use serde::{Serialize, Deserialize};
use std::borrow::Cow;
use std::collections::HashMap;
@@ -128,8 +128,8 @@ pub struct BlockImportParams<Block: BlockT, Transaction> {
/// re-executed in a runtime that checks digest equivalence -- the
/// post-runtime digests are pushed back on after.
pub header: Block::Header,
/// Justification provided for this block from the outside.
pub justification: Option<Justification>,
/// Justification(s) provided for this block from the outside.
pub justifications: Option<Justifications>,
/// Digest items that have been added after the runtime for external
/// work, like a consensus signature.
pub post_digests: Vec<DigestItemFor<Block>>,
@@ -174,7 +174,7 @@ impl<Block: BlockT, Transaction> BlockImportParams<Block, Transaction> {
) -> Self {
Self {
origin, header,
justification: None,
justifications: None,
post_digests: Vec::new(),
body: None,
storage_changes: None,
@@ -219,7 +219,7 @@ impl<Block: BlockT, Transaction> BlockImportParams<Block, Transaction> {
BlockImportParams {
origin: self.origin,
header: self.header,
justification: self.justification,
justifications: self.justifications,
post_digests: self.post_digests,
body: self.body,
storage_changes: None,
@@ -28,7 +28,7 @@
use std::collections::HashMap;
use sp_runtime::{Justification, traits::{Block as BlockT, Header as _, NumberFor}};
use sp_runtime::{Justifications, traits::{Block as BlockT, Header as _, NumberFor}};
use crate::{
error::Error as ConsensusError,
@@ -68,8 +68,8 @@ pub struct IncomingBlock<B: BlockT> {
pub header: Option<<B as BlockT>::Header>,
/// Block body if requested.
pub body: Option<Vec<<B as BlockT>::Extrinsic>>,
/// Justification if requested.
pub justification: Option<Justification>,
/// Justification(s) if requested.
pub justifications: Option<Justifications>,
/// The peer, we received this from
pub origin: Option<Origin>,
/// Allow importing the block skipping state verification if parent state is missing.
@@ -90,7 +90,7 @@ pub trait Verifier<B: BlockT>: Send + Sync {
&mut self,
origin: BlockOrigin,
header: B::Header,
justification: Option<Justification>,
justifications: Option<Justifications>,
body: Option<Vec<B::Extrinsic>>,
) -> Result<(BlockImportParams<B, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String>;
}
@@ -102,13 +102,13 @@ pub trait Verifier<B: BlockT>: Send + Sync {
pub trait ImportQueue<B: BlockT>: Send {
/// Import bunch of blocks.
fn import_blocks(&mut self, origin: BlockOrigin, blocks: Vec<IncomingBlock<B>>);
/// Import a block justification.
fn import_justification(
/// Import block justifications.
fn import_justifications(
&mut self,
who: Origin,
hash: B::Hash,
number: NumberFor<B>,
justification: Justification
justifications: Justifications
);
/// Polls for actions to perform on the network.
///
@@ -182,8 +182,8 @@ pub(crate) fn import_single_block_metered<B: BlockT, V: Verifier<B>, Transaction
) -> Result<BlockImportResult<NumberFor<B>>, BlockImportError> {
let peer = block.origin;
let (header, justification) = match (block.header, block.justification) {
(Some(header), justification) => (header, justification),
let (header, justifications) = match (block.header, block.justifications) {
(Some(header), justifications) => (header, justifications),
(None, _) => {
if let Some(ref peer) = peer {
debug!(target: "sync", "Header {} was not provided by {} ", block.hash, peer);
@@ -238,7 +238,7 @@ pub(crate) fn import_single_block_metered<B: BlockT, V: Verifier<B>, Transaction
}
let started = wasm_timer::Instant::now();
let (mut import_block, maybe_keys) = verifier.verify(block_origin, header, justification, block.body)
let (mut import_block, maybe_keys) = verifier.verify(block_origin, header, justifications, block.body)
.map_err(|msg| {
if let Some(ref peer) = peer {
trace!(target: "sync", "Verifying {}({}) from {} failed: {}", number, hash, peer, msg);
@@ -18,7 +18,7 @@
use std::{pin::Pin, time::Duration, marker::PhantomData};
use futures::{prelude::*, task::Context, task::Poll};
use futures_timer::Delay;
use sp_runtime::{Justification, traits::{Block as BlockT, Header as HeaderT, NumberFor}};
use sp_runtime::{Justification, Justifications, traits::{Block as BlockT, Header as HeaderT, NumberFor}};
use sp_utils::mpsc::{TracingUnboundedSender, tracing_unbounded, TracingUnboundedReceiver};
use prometheus_endpoint::Registry;
@@ -112,22 +112,24 @@ impl<B: BlockT, Transaction: Send> ImportQueue<B> for BasicQueue<B, Transaction>
}
}
fn import_justification(
fn import_justifications(
&mut self,
who: Origin,
hash: B::Hash,
number: NumberFor<B>,
justification: Justification,
justifications: Justifications,
) {
let res = self.justification_sender.unbounded_send(
worker_messages::ImportJustification(who, hash, number, justification),
);
if res.is_err() {
log::error!(
target: "sync",
"import_justification: Background import task is no longer alive"
for justification in justifications {
let res = self.justification_sender.unbounded_send(
worker_messages::ImportJustification(who, hash, number, justification),
);
if res.is_err() {
log::error!(
target: "sync",
"import_justification: Background import task is no longer alive"
);
}
}
}
@@ -281,7 +283,7 @@ impl<B: BlockT> BlockImportWorker<B> {
who: Origin,
hash: B::Hash,
number: NumberFor<B>,
justification: Justification
justification: Justification,
) {
let started = wasm_timer::Instant::now();
let success = self.justification_import.as_mut().map(|justification_import| {
@@ -442,7 +444,7 @@ mod tests {
&mut self,
origin: BlockOrigin,
header: Header,
_justification: Option<Justification>,
_justifications: Option<Justifications>,
_body: Option<Vec<Extrinsic>>,
) -> Result<(BlockImportParams<Block, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
Ok((BlockImportParams::new(origin, header), None))
@@ -541,7 +543,7 @@ mod tests {
hash,
header: Some(header),
body: None,
justification: None,
justifications: None,
origin: None,
allow_missing_state: false,
import_existing: false,
@@ -554,12 +556,11 @@ mod tests {
let mut import_justification = || {
let hash = Hash::random();
block_on(finality_sender.send(worker_messages::ImportJustification(
libp2p::PeerId::random(),
hash,
1,
Vec::new(),
(*b"TEST", Vec::new()),
)))
.unwrap();
@@ -30,7 +30,7 @@ use crate::traits::{
self, Member, Block as BlockT, Header as HeaderT, MaybeSerialize, MaybeMallocSizeOf,
NumberFor,
};
use crate::Justification;
use crate::Justifications;
/// Something to identify a block.
#[derive(PartialEq, Eq, Clone, RuntimeDebug)]
@@ -112,5 +112,5 @@ pub struct SignedBlock<Block> {
/// Full block.
pub block: Block,
/// Block justification.
pub justification: Option<Justification>,
pub justifications: Option<Justifications>,
}
+59 -1
View File
@@ -96,7 +96,65 @@ pub use either::Either;
/// the block itself would allow swapping justifications to change the block's hash
/// (and thus fork the chain). Sending a `Justification` alongside a block instead
/// bypasses this problem.
pub type Justification = Vec<u8>;
///
/// Each justification is provided as an encoded blob, and is tagged with an ID
/// to identify the consensus engine that generated the proof (we might have
/// multiple justifications from different engines for the same block).
pub type Justification = (ConsensusEngineId, EncodedJustification);
/// The encoded justification specific to a consensus engine.
pub type EncodedJustification = Vec<u8>;
/// Collection of justifications for a given block, multiple justifications may
/// be provided by different consensus engines for the same block.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)]
pub struct Justifications(Vec<Justification>);
impl Justifications {
/// Return an iterator over the justifications.
pub fn iter(&self) -> impl Iterator<Item = &Justification> {
self.0.iter()
}
/// Append a justification. Returns false if a justification with the same
/// `ConsensusEngineId` already exists, in which case the justification is
/// not inserted.
pub fn append(&mut self, justification: Justification) -> bool {
if self.get(justification.0).is_some() {
return false;
}
self.0.push(justification);
true
}
/// Return the encoded justification for the given consensus engine, if it
/// exists.
pub fn get(&self, engine_id: ConsensusEngineId) -> Option<&EncodedJustification> {
self.iter().find(|j| j.0 == engine_id).map(|j| &j.1)
}
/// Return a copy of the encoded justification for the given consensus
/// engine, if it exists.
pub fn into_justification(self, engine_id: ConsensusEngineId) -> Option<EncodedJustification> {
self.into_iter().find(|j| j.0 == engine_id).map(|j| j.1)
}
}
impl IntoIterator for Justifications {
type Item = Justification;
type IntoIter = sp_std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl From<Justification> for Justifications {
fn from(justification: Justification) -> Self {
Self(vec![justification])
}
}
use traits::{Verify, Lazy};