mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-29 22:51:03 +00:00
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:
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user