Manual Seal (#4143)

* instant/manual seal

unbounded queues are evil

Apply suggestions from code review

Co-Authored-By: Robert Habermeier <rphmeier@gmail.com>

add fork tests, docs, remove todos

moar docs

Update client/consensus/manual-seal/src/rpc.rs

Co-Authored-By: Robert Habermeier <rphmeier@gmail.com>

remove unbound generic, parameter, docs, deps, code style changes

Apply suggestions from code review

Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

code style chnges

remove unused deps, remove dep renames, check if block is empty before importing, use ? for error propagation

fix tests

log errors for instant seal

use debug

code style changes, updated copyright dates

use txpool::Pool instead of BasicPool, code style changes

fixed tests

* fix tests

* requested changes from review

* check inherents len

* rebase
This commit is contained in:
Seun LanLege
2020-01-27 10:59:40 +01:00
committed by GitHub
parent 2421576f91
commit 9cea98e950
18 changed files with 1521 additions and 469 deletions
@@ -0,0 +1,32 @@
[package]
name = "sc-consensus-manual-seal"
version = "0.8.0"
authors = ["Parity Technologies <admin@parity.io>"]
description = "Manual sealing engine for Substrate"
edition = "2018"
[dependencies]
derive_more = "0.99.2"
futures = "0.3.1"
jsonrpc-core = "14.0.5"
jsonrpc-core-client = "14.0.5"
jsonrpc-derive = "14.0.5"
log = "0.4.8"
parking_lot = "0.10"
serde = { version = "1.0", features=["derive"] }
sc-client = { path = "../../../client" }
sc-client-api = { path = "../../../client/api" }
sc-transaction-pool = { path = "../../transaction-pool", features = ["test-helpers"] }
sp-blockchain = { path = "../../../primitives/blockchain" }
sp-consensus = { package = "sp-consensus", path = "../../../primitives/consensus/common" }
sp-inherents = { path = "../../../primitives/inherents" }
sp-runtime = { path = "../../../primitives/runtime" }
sp-transaction-pool = { path = "../../../primitives/transaction-pool" }
[dev-dependencies]
sc-basic-authorship = { path = "../../basic-authorship" }
substrate-test-runtime-client = { path = "../../../test-utils/runtime/client" }
tokio = { version = "0.2", features = ["rt-core", "macros"] }
env_logger = "0.7.0"
tempfile = "3.1.0"
@@ -0,0 +1,98 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! A manual sealing engine: the engine listens for rpc calls to seal blocks and create forks.
//! This is suitable for a testing environment.
use sp_consensus::{Error as ConsensusError, ImportResult};
use sp_blockchain::Error as BlockchainError;
use sp_inherents::Error as InherentsError;
use futures::channel::{oneshot, mpsc::SendError};
/// Error code for rpc
mod codes {
pub const SERVER_SHUTTING_DOWN: i64 = 10_000;
pub const BLOCK_IMPORT_FAILED: i64 = 11_000;
pub const EMPTY_TRANSACTION_POOL: i64 = 12_000;
pub const BLOCK_NOT_FOUND: i64 = 13_000;
pub const CONSENSUS_ERROR: i64 = 14_000;
pub const INHERENTS_ERROR: i64 = 15_000;
pub const BLOCKCHAIN_ERROR: i64 = 16_000;
pub const UNKNOWN_ERROR: i64 = 20_000;
}
/// errors encountered by background block authorship task
#[derive(Debug, derive_more::Display, derive_more::From)]
pub enum Error {
/// An error occurred while importing the block
#[display(fmt = "Block import failed: {:?}", _0)]
BlockImportError(ImportResult),
/// Transaction pool is empty, cannot create a block
#[display(fmt = "Transaction pool is empty, set create_empty to true,\
if you want to create empty blocks")]
EmptyTransactionPool,
/// encountered during creation of Proposer.
#[display(fmt = "Consensus Error: {}", _0)]
ConsensusError(ConsensusError),
/// Failed to create Inherents data
#[display(fmt = "Inherents Error: {}", _0)]
InherentError(InherentsError),
/// error encountered during finalization
#[display(fmt = "Finalization Error: {}", _0)]
BlockchainError(BlockchainError),
/// Supplied parent_hash doesn't exist in chain
#[display(fmt = "Supplied parent_hash: {} doesn't exist in chain", _0)]
#[from(ignore)]
BlockNotFound(String),
/// Some string error
#[display(fmt = "{}", _0)]
#[from(ignore)]
StringError(String),
///send error
#[display(fmt = "Consensus process is terminating")]
Canceled(oneshot::Canceled),
///send error
#[display(fmt = "Consensus process is terminating")]
SendError(SendError),
/// Some other error.
#[display(fmt="Other error: {}", _0)]
Other(Box<dyn std::error::Error + Send>),
}
impl Error {
fn to_code(&self) -> i64 {
use Error::*;
match self {
BlockImportError(_) => codes::BLOCK_IMPORT_FAILED,
BlockNotFound(_) => codes::BLOCK_NOT_FOUND,
EmptyTransactionPool => codes::EMPTY_TRANSACTION_POOL,
ConsensusError(_) => codes::CONSENSUS_ERROR,
InherentError(_) => codes::INHERENTS_ERROR,
BlockchainError(_) => codes::BLOCKCHAIN_ERROR,
SendError(_) | Canceled(_) => codes::SERVER_SHUTTING_DOWN,
_ => codes::UNKNOWN_ERROR
}
}
}
impl std::convert::From<Error> for jsonrpc_core::Error {
fn from(error: Error) -> Self {
jsonrpc_core::Error {
code: jsonrpc_core::ErrorCode::ServerError(error.to_code()),
message: format!("{}", error),
data: None
}
}
}
@@ -0,0 +1,64 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Block finalization utilities
use crate::rpc;
use sp_runtime::{
Justification,
traits::Block as BlockT,
generic::BlockId,
};
use std::sync::Arc;
use sc_client_api::backend::Backend as ClientBackend;
/// params for block finalization.
pub struct FinalizeBlockParams<B: BlockT, CB> {
/// hash of the block
pub hash: <B as BlockT>::Hash,
/// sender to report errors/success to the rpc.
pub sender: rpc::Sender<()>,
/// finalization justification
pub justification: Option<Justification>,
/// client backend
pub backend: Arc<CB>,
}
/// finalizes a block in the backend with the given params.
pub async fn finalize_block<B, CB>(params: FinalizeBlockParams<B, CB>)
where
B: BlockT,
CB: ClientBackend<B>,
{
let FinalizeBlockParams {
hash,
mut sender,
justification,
backend: back_end,
..
} = params;
match back_end.finalize_block(BlockId::Hash(hash), justification) {
Err(e) => {
log::warn!("Failed to finalize block {:?}", e);
rpc::send_result(&mut sender, Err(e.into()))
}
Ok(()) => {
log::info!("Successfully finalized block: {}", hash);
rpc::send_result(&mut sender, Ok(()))
}
}
}
@@ -0,0 +1,467 @@
// Copyright 2020 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! A manual sealing engine: the engine listens for rpc calls to seal blocks and create forks.
//! This is suitable for a testing environment.
use sp_consensus::{
self, BlockImport, Environment, Proposer, BlockCheckParams,
ForkChoiceStrategy, BlockImportParams, BlockOrigin,
ImportResult, SelectChain,
import_queue::{
BasicQueue,
CacheKeyId,
Verifier,
BoxBlockImport,
},
};
use sp_inherents::InherentDataProviders;
use sp_runtime::{traits::Block as BlockT, Justification};
use sc_client_api::backend::Backend as ClientBackend;
use futures::prelude::*;
use sc_transaction_pool::txpool;
use std::collections::HashMap;
use std::sync::Arc;
pub mod rpc;
mod error;
mod finalize_block;
mod seal_new_block;
use finalize_block::{finalize_block, FinalizeBlockParams};
use seal_new_block::{seal_new_block, SealBlockParams};
pub use error::Error;
pub use rpc::{EngineCommand, CreatedBlock};
/// The synchronous block-import worker of the engine.
pub struct ManualSealBlockImport<I> {
inner: I,
}
impl<I> From<I> for ManualSealBlockImport<I> {
fn from(i: I) -> Self {
ManualSealBlockImport { inner: i }
}
}
impl<B, I> BlockImport<B> for ManualSealBlockImport<I>
where
B: BlockT,
I: BlockImport<B, Transaction = ()>,
{
type Error = I::Error;
type Transaction = ();
fn check_block(&mut self, block: BlockCheckParams<B>) -> Result<ImportResult, Self::Error>
{
self.inner.check_block(block)
}
fn import_block(
&mut self,
block: BlockImportParams<B, Self::Transaction>,
cache: HashMap<CacheKeyId, Vec<u8>>,
) -> Result<ImportResult, Self::Error> {
self.inner.import_block(block, cache)
}
}
/// The verifier for the manual seal engine; instantly finalizes.
struct ManualSealVerifier;
impl<B: BlockT> Verifier<B> for ManualSealVerifier {
fn verify(
&mut self,
origin: BlockOrigin,
header: B::Header,
justification: Option<Justification>,
body: Option<Vec<B::Extrinsic>>,
) -> Result<(BlockImportParams<B, ()>, Option<Vec<(CacheKeyId, Vec<u8>)>>), String> {
let import_params = BlockImportParams {
origin,
header,
justification,
post_digests: Vec::new(),
body,
storage_changes: None,
finalized: true,
auxiliary: Vec::new(),
intermediates: HashMap::new(),
fork_choice: Some(ForkChoiceStrategy::LongestChain),
allow_missing_state: false,
import_existing: false,
};
Ok((import_params, None))
}
}
/// Instantiate the import queue for the manual seal consensus engine.
pub fn import_queue<B: BlockT>(block_import: BoxBlockImport<B, ()>) -> BasicQueue<B, ()>
{
BasicQueue::new(
ManualSealVerifier,
block_import,
None,
None,
)
}
/// Creates the background authorship task for the manual seal engine.
pub async fn run_manual_seal<B, CB, E, A, C, S, T>(
mut block_import: BoxBlockImport<B, T>,
mut env: E,
backend: Arc<CB>,
pool: Arc<txpool::Pool<A>>,
mut seal_block_channel: S,
select_chain: C,
inherent_data_providers: InherentDataProviders,
)
where
B: BlockT + 'static,
CB: ClientBackend<B> + 'static,
E: Environment<B> + 'static,
E::Error: std::fmt::Display,
<E::Proposer as Proposer<B>>::Error: std::fmt::Display,
A: txpool::ChainApi<Block=B, Hash=<B as BlockT>::Hash> + 'static,
S: Stream<Item=EngineCommand<<B as BlockT>::Hash>> + Unpin + 'static,
C: SelectChain<B> + 'static,
{
while let Some(command) = seal_block_channel.next().await {
match command {
EngineCommand::SealNewBlock {
create_empty,
finalize,
parent_hash,
sender,
} => {
seal_new_block(
SealBlockParams {
sender,
parent_hash,
finalize,
create_empty,
env: &mut env,
select_chain: &select_chain,
block_import: &mut block_import,
inherent_data_provider: &inherent_data_providers,
pool: pool.clone(),
backend: backend.clone(),
}
).await;
}
EngineCommand::FinalizeBlock { hash, sender, justification } => {
finalize_block(
FinalizeBlockParams {
hash,
sender,
justification,
backend: backend.clone(),
}
).await
}
}
}
}
/// runs the background authorship task for the instant seal engine.
/// instant-seal creates a new block for every transaction imported into
/// the transaction pool.
pub async fn run_instant_seal<B, CB, E, A, C, T>(
block_import: BoxBlockImport<B, T>,
env: E,
backend: Arc<CB>,
pool: Arc<txpool::Pool<A>>,
select_chain: C,
inherent_data_providers: InherentDataProviders,
)
where
A: txpool::ChainApi<Block=B, Hash=<B as BlockT>::Hash> + 'static,
B: BlockT + 'static,
CB: ClientBackend<B> + 'static,
E: Environment<B> + 'static,
E::Error: std::fmt::Display,
<E::Proposer as Proposer<B>>::Error: std::fmt::Display,
C: SelectChain<B> + 'static
{
// instant-seal creates blocks as soon as transactions are imported
// into the transaction pool.
let seal_block_channel = pool.import_notification_stream()
.map(|_| {
EngineCommand::SealNewBlock {
create_empty: false,
finalize: false,
parent_hash: None,
sender: None,
}
});
run_manual_seal(
block_import,
env,
backend,
pool,
seal_block_channel,
select_chain,
inherent_data_providers,
).await
}
#[cfg(test)]
mod tests {
use super::*;
use substrate_test_runtime_client::{
DefaultTestClientBuilderExt,
TestClientBuilderExt,
AccountKeyring::*,
TestClientBuilder,
};
use sc_transaction_pool::{
BasicPool,
txpool::Options,
testing::*,
};
use sp_transaction_pool::TransactionPool;
use sp_runtime::generic::BlockId;
use sp_blockchain::HeaderBackend;
use sp_consensus::ImportedAux;
use sc_client::LongestChain;
use sp_inherents::InherentDataProviders;
use sc_basic_authorship::ProposerFactory;
fn api() -> TestApi {
let mut api = TestApi::default();
api.nonce_offset = 0;
api
}
#[tokio::test]
async fn instant_seal() {
let builder = TestClientBuilder::new();
let backend = builder.backend();
let client = Arc::new(builder.build());
let select_chain = LongestChain::new(backend.clone());
let inherent_data_providers = InherentDataProviders::new();
let pool = Arc::new(BasicPool::new(Options::default(), api()));
let env = ProposerFactory {
transaction_pool: pool.clone(),
client: client.clone(),
};
// this test checks that blocks are created as soon as transactions are imported into the pool.
let (sender, receiver) = futures::channel::oneshot::channel();
let mut sender = Arc::new(Some(sender));
let stream = pool.pool().import_notification_stream()
.map(move |_| {
// we're only going to submit one tx so this fn will only be called once.
let mut_sender = Arc::get_mut(&mut sender).unwrap();
let sender = std::mem::replace(mut_sender, None);
EngineCommand::SealNewBlock {
create_empty: false,
finalize: true,
parent_hash: None,
sender
}
});
let future = run_manual_seal(
Box::new(client.clone()),
env,
backend.clone(),
pool.pool().clone(),
stream,
select_chain,
inherent_data_providers,
);
std::thread::spawn(|| {
let mut rt = tokio::runtime::Runtime::new().unwrap();
// spawn the background authorship task
rt.block_on(future);
});
// submit a transaction to pool.
let result = pool.submit_one(&BlockId::Number(0), uxt(Alice, 0)).await;
// assert that it was successfully imported
assert!(result.is_ok());
// assert that the background task returns ok
let created_block = receiver.await.unwrap().unwrap();
assert_eq!(
created_block,
CreatedBlock {
hash: created_block.hash.clone(),
aux: ImportedAux {
header_only: false,
clear_justification_requests: false,
needs_justification: false,
bad_justification: false,
needs_finality_proof: false,
is_new_best: true,
}
}
);
// assert that there's a new block in the db.
assert!(backend.blockchain().header(BlockId::Number(1)).unwrap().is_some())
}
#[tokio::test]
async fn manual_seal_and_finalization() {
let builder = TestClientBuilder::new();
let backend = builder.backend();
let client = Arc::new(builder.build());
let select_chain = LongestChain::new(backend.clone());
let inherent_data_providers = InherentDataProviders::new();
let pool = Arc::new(BasicPool::new(Options::default(), api()));
let env = ProposerFactory {
transaction_pool: pool.clone(),
client: client.clone(),
};
// this test checks that blocks are created as soon as an engine command is sent over the stream.
let (mut sink, stream) = futures::channel::mpsc::channel(1024);
let future = run_manual_seal(
Box::new(client.clone()),
env,
backend.clone(),
pool.pool().clone(),
stream,
select_chain,
inherent_data_providers,
);
std::thread::spawn(|| {
let mut rt = tokio::runtime::Runtime::new().unwrap();
// spawn the background authorship task
rt.block_on(future);
});
// submit a transaction to pool.
let result = pool.submit_one(&BlockId::Number(0), uxt(Alice, 0)).await;
// assert that it was successfully imported
assert!(result.is_ok());
let (tx, rx) = futures::channel::oneshot::channel();
sink.send(EngineCommand::SealNewBlock {
parent_hash: None,
sender: Some(tx),
create_empty: false,
finalize: false,
}).await.unwrap();
let created_block = rx.await.unwrap().unwrap();
// assert that the background task returns ok
assert_eq!(
created_block,
CreatedBlock {
hash: created_block.hash.clone(),
aux: ImportedAux {
header_only: false,
clear_justification_requests: false,
needs_justification: false,
bad_justification: false,
needs_finality_proof: false,
is_new_best: true,
}
}
);
// assert that there's a new block in the db.
let header = backend.blockchain().header(BlockId::Number(1)).unwrap().unwrap();
let (tx, rx) = futures::channel::oneshot::channel();
sink.send(EngineCommand::FinalizeBlock {
sender: Some(tx),
hash: header.hash(),
justification: None
}).await.unwrap();
// assert that the background task returns ok
assert_eq!(rx.await.unwrap().unwrap(), ());
}
#[tokio::test]
async fn manual_seal_fork_blocks() {
let builder = TestClientBuilder::new();
let backend = builder.backend();
let client = Arc::new(builder.build());
let select_chain = LongestChain::new(backend.clone());
let inherent_data_providers = InherentDataProviders::new();
let pool = Arc::new(BasicPool::new(Options::default(), api()));
let env = ProposerFactory {
transaction_pool: pool.clone(),
client: client.clone(),
};
// this test checks that blocks are created as soon as an engine command is sent over the stream.
let (mut sink, stream) = futures::channel::mpsc::channel(1024);
let future = run_manual_seal(
Box::new(client.clone()),
env,
backend.clone(),
pool.pool().clone(),
stream,
select_chain,
inherent_data_providers,
);
std::thread::spawn(|| {
let mut rt = tokio::runtime::Runtime::new().unwrap();
// spawn the background authorship task
rt.block_on(future);
});
// submit a transaction to pool.
let result = pool.submit_one(&BlockId::Number(0), uxt(Alice, 0)).await;
// assert that it was successfully imported
assert!(result.is_ok());
let (tx, rx) = futures::channel::oneshot::channel();
sink.send(EngineCommand::SealNewBlock {
parent_hash: None,
sender: Some(tx),
create_empty: false,
finalize: false,
}).await.unwrap();
let created_block = rx.await.unwrap().unwrap();
// assert that the background task returns ok
assert_eq!(
created_block,
CreatedBlock {
hash: created_block.hash.clone(),
aux: ImportedAux {
header_only: false,
clear_justification_requests: false,
needs_justification: false,
bad_justification: false,
needs_finality_proof: false,
is_new_best: true
}
}
);
// assert that there's a new block in the db.
assert!(backend.blockchain().header(BlockId::Number(0)).unwrap().is_some());
assert!(pool.submit_one(&BlockId::Number(1), uxt(Alice, 1)).await.is_ok());
let (tx1, rx1) = futures::channel::oneshot::channel();
assert!(sink.send(EngineCommand::SealNewBlock {
parent_hash: Some(created_block.hash.clone()),
sender: Some(tx1),
create_empty: false,
finalize: false,
}).await.is_ok());
assert!(rx1.await.unwrap().is_ok());
assert!(backend.blockchain().header(BlockId::Number(1)).unwrap().is_some());
assert!(pool.submit_one(&BlockId::Number(2), uxt(Alice, 2)).await.is_ok());
let (tx2, rx2) = futures::channel::oneshot::channel();
assert!(sink.send(EngineCommand::SealNewBlock {
parent_hash: Some(created_block.hash),
sender: Some(tx2),
create_empty: false,
finalize: false,
}).await.is_ok());
let imported = rx2.await.unwrap().unwrap();
// assert that fork block is in the db
assert!(backend.blockchain().header(BlockId::Hash(imported.hash)).unwrap().is_some())
}
}
@@ -0,0 +1,163 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! RPC interface for the ManualSeal Engine.
use sp_consensus::ImportedAux;
use jsonrpc_core::Error;
use jsonrpc_derive::rpc;
use futures::{
channel::{mpsc, oneshot},
TryFutureExt,
FutureExt,
SinkExt
};
use serde::{Deserialize, Serialize};
use sp_runtime::Justification;
pub use self::gen_client::Client as ManualSealClient;
/// Future's type for jsonrpc
type FutureResult<T> = Box<dyn jsonrpc_core::futures::Future<Item = T, Error = Error> + Send>;
/// sender passed to the authorship task to report errors or successes.
pub type Sender<T> = Option<oneshot::Sender<std::result::Result<T, crate::Error>>>;
/// Message sent to the background authorship task, usually by RPC.
pub enum EngineCommand<Hash> {
/// Tells the engine to propose a new block
///
/// if create_empty == true, it will create empty blocks if there are no transactions
/// in the transaction pool.
///
/// if finalize == true, the block will be instantly finalized.
SealNewBlock {
/// if true, empty blocks(without extrinsics) will be created.
/// otherwise, will return Error::EmptyTransactionPool.
create_empty: bool,
/// instantly finalize this block?
finalize: bool,
/// specify the parent hash of the about-to-created block
parent_hash: Option<Hash>,
/// sender to report errors/success to the rpc.
sender: Sender<CreatedBlock<Hash>>,
},
/// Tells the engine to finalize the block with the supplied hash
FinalizeBlock {
/// hash of the block
hash: Hash,
/// sender to report errors/success to the rpc.
sender: Sender<()>,
/// finalization justification
justification: Option<Justification>,
}
}
/// RPC trait that provides methods for interacting with the manual-seal authorship task over rpc.
#[rpc]
pub trait ManualSealApi<Hash> {
/// Instructs the manual-seal authorship task to create a new block
#[rpc(name = "engine_createBlock")]
fn create_block(
&self,
create_empty: bool,
finalize: bool,
parent_hash: Option<Hash>
) -> FutureResult<CreatedBlock<Hash>>;
/// Instructs the manual-seal authorship task to finalize a block
#[rpc(name = "engine_finalizeBlock")]
fn finalize_block(
&self,
hash: Hash,
justification: Option<Justification>
) -> FutureResult<bool>;
}
/// A struct that implements the [`ManualSealApi`].
pub struct ManualSeal<Hash> {
import_block_channel: mpsc::Sender<EngineCommand<Hash>>,
}
/// return type of `engine_createBlock`
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct CreatedBlock<Hash> {
/// hash of the created block.
pub hash: Hash,
/// some extra details about the import operation
pub aux: ImportedAux
}
impl<Hash> ManualSeal<Hash> {
/// Create new `ManualSeal` with the given reference to the client.
pub fn new(import_block_channel: mpsc::Sender<EngineCommand<Hash>>) -> Self {
Self { import_block_channel }
}
}
impl<Hash: Send + 'static> ManualSealApi<Hash> for ManualSeal<Hash> {
fn create_block(
&self,
create_empty: bool,
finalize: bool,
parent_hash: Option<Hash>
) -> FutureResult<CreatedBlock<Hash>> {
let mut sink = self.import_block_channel.clone();
let future = async move {
let (sender, receiver) = oneshot::channel();
let command = EngineCommand::SealNewBlock {
create_empty,
finalize,
parent_hash,
sender: Some(sender),
};
sink.send(command).await?;
receiver.await?
}.boxed();
Box::new(future.map_err(Error::from).compat())
}
fn finalize_block(&self, hash: Hash, justification: Option<Justification>) -> FutureResult<bool> {
let mut sink = self.import_block_channel.clone();
let future = async move {
let (sender, receiver) = oneshot::channel();
sink.send(
EngineCommand::FinalizeBlock { hash, sender: Some(sender), justification }
).await?;
receiver.await?.map(|_| true)
};
Box::new(future.boxed().map_err(Error::from).compat())
}
}
/// report any errors or successes encountered by the authorship task back
/// to the rpc
pub fn send_result<T: std::fmt::Debug>(
sender: &mut Sender<T>,
result: std::result::Result<T, crate::Error>
) {
if let Some(sender) = sender.take() {
if let Err(err) = sender.send(result) {
log::warn!("Server is shutting down: {:?}", err)
}
} else {
// instant seal doesn't report errors over rpc, simply log them.
match result {
Ok(r) => log::info!("Instant Seal success: {:?}", r),
Err(e) => log::error!("Instant Seal encountered an error: {}", e)
}
}
}
@@ -0,0 +1,148 @@
// Copyright 2019 Parity Technologies (UK) Ltd.
// This file is part of Substrate.
// Substrate is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Substrate is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! Block sealing utilities
use crate::{Error, rpc};
use std::sync::Arc;
use sp_runtime::{
traits::{Block as BlockT, Header as HeaderT},
generic::BlockId,
};
use futures::prelude::*;
use sc_transaction_pool::txpool;
use rpc::CreatedBlock;
use sp_consensus::{
self, BlockImport, Environment, Proposer,
ForkChoiceStrategy, BlockImportParams, BlockOrigin,
ImportResult, SelectChain,
import_queue::BoxBlockImport,
};
use sp_blockchain::HeaderBackend;
use sc_client_api::backend::Backend as ClientBackend;
use std::collections::HashMap;
use std::time::Duration;
use sp_inherents::InherentDataProviders;
/// max duration for creating a proposal in secs
const MAX_PROPOSAL_DURATION: u64 = 10;
/// params for sealing a new block
pub struct SealBlockParams<'a, B: BlockT, C, CB, E, T, P: txpool::ChainApi> {
/// if true, empty blocks(without extrinsics) will be created.
/// otherwise, will return Error::EmptyTransactionPool.
pub create_empty: bool,
/// instantly finalize this block?
pub finalize: bool,
/// specify the parent hash of the about-to-created block
pub parent_hash: Option<<B as BlockT>::Hash>,
/// sender to report errors/success to the rpc.
pub sender: rpc::Sender<CreatedBlock<<B as BlockT>::Hash>>,
/// transaction pool
pub pool: Arc<txpool::Pool<P>>,
/// client backend
pub backend: Arc<CB>,
/// Environment trait object for creating a proposer
pub env: &'a mut E,
/// SelectChain object
pub select_chain: &'a C,
/// block import object
pub block_import: &'a mut BoxBlockImport<B, T>,
/// inherent data provider
pub inherent_data_provider: &'a InherentDataProviders,
}
/// seals a new block with the given params
pub async fn seal_new_block<B, SC, CB, E, T, P>(
SealBlockParams {
create_empty,
finalize,
pool,
parent_hash,
backend: back_end,
select_chain,
block_import,
env,
inherent_data_provider,
mut sender,
..
}: SealBlockParams<'_, B, SC, CB, E, T, P>
)
where
B: BlockT,
CB: ClientBackend<B>,
E: Environment<B>,
<E as Environment<B>>::Error: std::fmt::Display,
<E::Proposer as Proposer<B>>::Error: std::fmt::Display,
P: txpool::ChainApi<Block=B, Hash=<B as BlockT>::Hash>,
SC: SelectChain<B>,
{
let future = async {
if pool.status().ready == 0 && !create_empty {
return Err(Error::EmptyTransactionPool)
}
// get the header to build this new block on.
// use the parent_hash supplied via `EngineCommand`
// or fetch the best_block.
let header = match parent_hash {
Some(hash) => {
match back_end.blockchain().header(BlockId::Hash(hash))? {
Some(header) => header,
None => return Err(Error::BlockNotFound(format!("{}", hash))),
}
}
None => select_chain.best_chain()?
};
let mut proposer = env.init(&header)
.map_err(|err| Error::StringError(format!("{}", err))).await?;
let id = inherent_data_provider.create_inherent_data()?;
let inherents_len = id.len();
let proposal = proposer.propose(id, Default::default(), Duration::from_secs(MAX_PROPOSAL_DURATION), false.into())
.map_err(|err| Error::StringError(format!("{}", err))).await?;
if proposal.block.extrinsics().len() == inherents_len && !create_empty {
return Err(Error::EmptyTransactionPool)
}
let (header, body) = proposal.block.deconstruct();
let params = BlockImportParams {
origin: BlockOrigin::Own,
header: header.clone(),
justification: None,
post_digests: Vec::new(),
body: Some(body),
finalized: finalize,
storage_changes: None,
auxiliary: Vec::new(),
intermediates: HashMap::new(),
fork_choice: Some(ForkChoiceStrategy::LongestChain),
allow_missing_state: false,
import_existing: false,
};
match block_import.import_block(params, HashMap::new())? {
ImportResult::Imported(aux) => {
Ok(CreatedBlock { hash: <B as BlockT>::Header::hash(&header), aux })
},
other => Err(other.into()),
}
};
rpc::send_result(&mut sender, future.await)
}