|
|
|
@@ -17,7 +17,6 @@
|
|
|
|
|
//! This service uses BFT consensus provided by the substrate.
|
|
|
|
|
|
|
|
|
|
extern crate parking_lot;
|
|
|
|
|
extern crate node_api;
|
|
|
|
|
extern crate node_transaction_pool as transaction_pool;
|
|
|
|
|
extern crate node_runtime;
|
|
|
|
|
extern crate node_primitives;
|
|
|
|
@@ -26,6 +25,7 @@ extern crate substrate_bft as bft;
|
|
|
|
|
extern crate parity_codec as codec;
|
|
|
|
|
extern crate substrate_primitives as primitives;
|
|
|
|
|
extern crate sr_primitives as runtime_primitives;
|
|
|
|
|
extern crate srml_system;
|
|
|
|
|
extern crate substrate_client as client;
|
|
|
|
|
|
|
|
|
|
extern crate exit_future;
|
|
|
|
@@ -45,12 +45,15 @@ extern crate substrate_keyring;
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
use std::time::{self, Duration, Instant};
|
|
|
|
|
|
|
|
|
|
use client::{Client as SubstrateClient, CallExecutor};
|
|
|
|
|
use codec::{Decode, Encode};
|
|
|
|
|
use node_api::Api;
|
|
|
|
|
use node_primitives::{AccountId, Hash, Block, BlockId, BlockNumber, Header, Timestamp, SessionKey};
|
|
|
|
|
use runtime_primitives::generic::Era;
|
|
|
|
|
use primitives::{AuthorityId, ed25519};
|
|
|
|
|
use transaction_pool::TransactionPool;
|
|
|
|
|
use node_primitives::{AccountId, Timestamp, SessionKey, InherentData};
|
|
|
|
|
use node_runtime::Runtime;
|
|
|
|
|
use primitives::{AuthorityId, ed25519, Blake2Hasher};
|
|
|
|
|
use runtime_primitives::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, As};
|
|
|
|
|
use runtime_primitives::generic::{BlockId, Era};
|
|
|
|
|
use srml_system::Trait as SystemT;
|
|
|
|
|
use transaction_pool::{TransactionPool, Client as TPClient};
|
|
|
|
|
use tokio::runtime::TaskExecutor;
|
|
|
|
|
use tokio::timer::Delay;
|
|
|
|
|
|
|
|
|
@@ -58,7 +61,7 @@ use futures::prelude::*;
|
|
|
|
|
use futures::future;
|
|
|
|
|
use parking_lot::RwLock;
|
|
|
|
|
|
|
|
|
|
pub use self::error::{ErrorKind, Error};
|
|
|
|
|
pub use self::error::{ErrorKind, Error, Result};
|
|
|
|
|
pub use self::offline_tracker::OfflineTracker;
|
|
|
|
|
pub use service::Service;
|
|
|
|
|
|
|
|
|
@@ -73,33 +76,133 @@ pub type SharedOfflineTracker = Arc<RwLock<OfflineTracker>>;
|
|
|
|
|
// block size limit.
|
|
|
|
|
const MAX_TRANSACTIONS_SIZE: usize = 4 * 1024 * 1024;
|
|
|
|
|
|
|
|
|
|
/// Build new blocks.
|
|
|
|
|
pub trait BlockBuilder<Block: BlockT> {
|
|
|
|
|
/// Push an extrinsic onto the block. Fails if the extrinsic is invalid.
|
|
|
|
|
fn push_extrinsic(&mut self, extrinsic: <Block as BlockT>::Extrinsic) -> Result<()>;
|
|
|
|
|
|
|
|
|
|
/// Bake the block with provided extrinsics.
|
|
|
|
|
fn bake(self) -> Result<Block>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Local client abstraction for the consensus.
|
|
|
|
|
pub trait AuthoringApi: Send + Sync {
|
|
|
|
|
/// The block used for this API type.
|
|
|
|
|
type Block: BlockT;
|
|
|
|
|
/// The block builder for this API type.
|
|
|
|
|
type BlockBuilder: BlockBuilder<Self::Block>;
|
|
|
|
|
|
|
|
|
|
/// Get the value of the randomness beacon at a given block.
|
|
|
|
|
fn random_seed(&self, at: &BlockId<Self::Block>) -> Result<<Self::Block as BlockT>::Hash>;
|
|
|
|
|
|
|
|
|
|
/// Get validators at a given block.
|
|
|
|
|
fn validators(&self, at: &BlockId<Self::Block>) -> Result<Vec<AccountId>>;
|
|
|
|
|
|
|
|
|
|
/// Build a block on top of the given, with inherent extrinsics pre-pushed.
|
|
|
|
|
fn build_block(&self, at: &BlockId<Self::Block>, inherent_data: InherentData) -> Result<Self::BlockBuilder>;
|
|
|
|
|
|
|
|
|
|
/// Get the nonce (né index) of an account at a block.
|
|
|
|
|
fn index(&self, at: &BlockId<Self::Block>, account: AccountId) -> Result<u64>;
|
|
|
|
|
|
|
|
|
|
/// Attempt to produce the (encoded) inherent extrinsics for a block being built upon the given.
|
|
|
|
|
/// This may vary by runtime and will fail if a runtime doesn't follow the same API.
|
|
|
|
|
fn inherent_extrinsics(&self, at: &BlockId<Self::Block>, inherent_data: InherentData) -> Result<Vec<<Self::Block as BlockT>::Extrinsic>>;
|
|
|
|
|
|
|
|
|
|
/// Evaluate a block. Returns true if the block is good, false if it is known to be bad,
|
|
|
|
|
/// and an error if we can't evaluate for some reason.
|
|
|
|
|
fn evaluate_block(&self, at: &BlockId<Self::Block>, block: Self::Block) -> Result<bool>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<B, E, Block> BlockBuilder<Block> for client::block_builder::BlockBuilder<B, E, Block, Blake2Hasher> where
|
|
|
|
|
B: client::backend::Backend<Block, Blake2Hasher> + Send + Sync + 'static,
|
|
|
|
|
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + Clone + 'static,
|
|
|
|
|
Block: BlockT
|
|
|
|
|
{
|
|
|
|
|
fn push_extrinsic(&mut self, extrinsic: <Block as BlockT>::Extrinsic) -> Result<()> {
|
|
|
|
|
client::block_builder::BlockBuilder::push(self, extrinsic).map_err(Into::into)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn bake(self) -> Result<Block> {
|
|
|
|
|
client::block_builder::BlockBuilder::bake(self).map_err(Into::into)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<B, E, Block> AuthoringApi for SubstrateClient<B, E, Block> where
|
|
|
|
|
B: client::backend::Backend<Block, Blake2Hasher> + Send + Sync + 'static,
|
|
|
|
|
E: CallExecutor<Block, Blake2Hasher> + Send + Sync + Clone + 'static,
|
|
|
|
|
Block: BlockT,
|
|
|
|
|
{
|
|
|
|
|
type Block = Block;
|
|
|
|
|
type BlockBuilder = client::block_builder::BlockBuilder<B, E, Block, Blake2Hasher>;
|
|
|
|
|
|
|
|
|
|
fn random_seed(&self, at: &BlockId<Block>) -> Result<<Self::Block as BlockT>::Hash> {
|
|
|
|
|
self.call_api_at(at, "random_seed", &()).map_err(Into::into)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validators(&self, at: &BlockId<Block>) -> Result<Vec<AccountId>> {
|
|
|
|
|
self.call_api_at(at, "validators", &()).map_err(Into::into)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn build_block(&self, at: &BlockId<Block>, inherent_data: InherentData) -> Result<Self::BlockBuilder> {
|
|
|
|
|
let runtime_version = self.runtime_version_at(at)?;
|
|
|
|
|
|
|
|
|
|
let mut block_builder = self.new_block_at(at)?;
|
|
|
|
|
if runtime_version.has_api(*b"inherent", 1) {
|
|
|
|
|
for inherent in self.inherent_extrinsics(at, inherent_data)? {
|
|
|
|
|
block_builder.push(inherent)?;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(block_builder)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn index(&self, at: &BlockId<Block>, account: AccountId) -> Result<u64> {
|
|
|
|
|
self.call_api_at(at, "account_nonce", &account).map_err(Into::into)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn inherent_extrinsics(&self, at: &BlockId<Self::Block>, inherent_data: InherentData) -> Result<Vec<<Block as BlockT>::Extrinsic>> {
|
|
|
|
|
self.call_api_at(at, "inherent_extrinsics", &inherent_data).map_err(Into::into)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn evaluate_block(&self, at: &BlockId<Self::Block>, block: Self::Block) -> Result<bool> {
|
|
|
|
|
let res: client::error::Result<()> = self.call_api_at(at, "execute_block", &block);
|
|
|
|
|
match res {
|
|
|
|
|
Ok(()) => Ok(true),
|
|
|
|
|
Err(err) => match err.kind() {
|
|
|
|
|
&client::error::ErrorKind::Execution(_) => Ok(false),
|
|
|
|
|
_ => Err(err.into())
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// A long-lived network which can create BFT message routing processes on demand.
|
|
|
|
|
pub trait Network {
|
|
|
|
|
/// The block used for this API type.
|
|
|
|
|
type Block: BlockT;
|
|
|
|
|
/// The input stream of BFT messages. Should never logically conclude.
|
|
|
|
|
type Input: Stream<Item=bft::Communication<Block>,Error=Error>;
|
|
|
|
|
type Input: Stream<Item=bft::Communication<Self::Block>,Error=Error>;
|
|
|
|
|
/// The output sink of BFT messages. Messages sent here should eventually pass to all
|
|
|
|
|
/// current authorities.
|
|
|
|
|
type Output: Sink<SinkItem=bft::Communication<Block>,SinkError=Error>;
|
|
|
|
|
type Output: Sink<SinkItem=bft::Communication<Self::Block>,SinkError=Error>;
|
|
|
|
|
|
|
|
|
|
/// Instantiate input and output streams.
|
|
|
|
|
fn communication_for(
|
|
|
|
|
&self,
|
|
|
|
|
validators: &[SessionKey],
|
|
|
|
|
local_id: SessionKey,
|
|
|
|
|
parent_hash: Hash,
|
|
|
|
|
parent_hash: <Self::Block as BlockT>::Hash,
|
|
|
|
|
task_executor: TaskExecutor
|
|
|
|
|
) -> (Self::Input, Self::Output);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Proposer factory.
|
|
|
|
|
pub struct ProposerFactory<N, P>
|
|
|
|
|
where
|
|
|
|
|
P: Api + Send + Sync + 'static
|
|
|
|
|
pub struct ProposerFactory<N, C> where
|
|
|
|
|
C: AuthoringApi + TPClient,
|
|
|
|
|
{
|
|
|
|
|
/// The client instance.
|
|
|
|
|
pub client: Arc<P>,
|
|
|
|
|
pub client: Arc<C>,
|
|
|
|
|
/// The transaction pool.
|
|
|
|
|
pub transaction_pool: Arc<TransactionPool<P>>,
|
|
|
|
|
pub transaction_pool: Arc<TransactionPool<C>>,
|
|
|
|
|
/// The backing network handle.
|
|
|
|
|
pub network: N,
|
|
|
|
|
/// handle to remote task executor
|
|
|
|
@@ -108,32 +211,31 @@ pub struct ProposerFactory<N, P>
|
|
|
|
|
pub offline: SharedOfflineTracker,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<N, P> bft::Environment<Block> for ProposerFactory<N, P>
|
|
|
|
|
where
|
|
|
|
|
N: Network,
|
|
|
|
|
P: Api + Send + Sync + 'static,
|
|
|
|
|
impl<N, C> bft::Environment<<C as AuthoringApi>::Block> for ProposerFactory<N, C> where
|
|
|
|
|
N: Network<Block=<C as AuthoringApi>::Block>,
|
|
|
|
|
C: AuthoringApi + TPClient<Block=<C as AuthoringApi>::Block>,
|
|
|
|
|
<<C as AuthoringApi>::Block as BlockT>::Hash:
|
|
|
|
|
Into<<Runtime as SystemT>::Hash> + PartialEq<primitives::H256> + Into<primitives::H256>
|
|
|
|
|
{
|
|
|
|
|
type Proposer = Proposer<P>;
|
|
|
|
|
type Proposer = Proposer<C>;
|
|
|
|
|
type Input = N::Input;
|
|
|
|
|
type Output = N::Output;
|
|
|
|
|
type Error = Error;
|
|
|
|
|
|
|
|
|
|
fn init(
|
|
|
|
|
&self,
|
|
|
|
|
parent_header: &Header,
|
|
|
|
|
parent_header: &<<C as AuthoringApi>::Block as BlockT>::Header,
|
|
|
|
|
authorities: &[AuthorityId],
|
|
|
|
|
sign_with: Arc<ed25519::Pair>,
|
|
|
|
|
) -> Result<(Self::Proposer, Self::Input, Self::Output), Error> {
|
|
|
|
|
use runtime_primitives::traits::{Hash as HashT, BlakeTwo256};
|
|
|
|
|
|
|
|
|
|
) -> Result<(Self::Proposer, Self::Input, Self::Output)> {
|
|
|
|
|
// force delay in evaluation this long.
|
|
|
|
|
const FORCE_DELAY: Timestamp = 5;
|
|
|
|
|
|
|
|
|
|
let parent_hash = parent_header.hash().into();
|
|
|
|
|
let parent_hash = parent_header.hash();
|
|
|
|
|
|
|
|
|
|
let id = BlockId::hash(parent_hash);
|
|
|
|
|
let random_seed = self.client.random_seed(&id)?;
|
|
|
|
|
let random_seed = BlakeTwo256::hash(&*random_seed);
|
|
|
|
|
let random_seed = <<<C as AuthoringApi>::Block as BlockT>::Header as HeaderT>::Hashing::hash(random_seed.as_ref());
|
|
|
|
|
|
|
|
|
|
let validators = self.client.validators(&id)?;
|
|
|
|
|
self.offline.write().note_new_block(&validators[..]);
|
|
|
|
@@ -154,7 +256,7 @@ impl<N, P> bft::Environment<Block> for ProposerFactory<N, P>
|
|
|
|
|
local_key: sign_with,
|
|
|
|
|
parent_hash,
|
|
|
|
|
parent_id: id,
|
|
|
|
|
parent_number: parent_header.number,
|
|
|
|
|
parent_number: *parent_header.number(),
|
|
|
|
|
random_seed,
|
|
|
|
|
transaction_pool: self.transaction_pool.clone(),
|
|
|
|
|
offline: self.offline.clone(),
|
|
|
|
@@ -167,43 +269,42 @@ impl<N, P> bft::Environment<Block> for ProposerFactory<N, P>
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// The proposer logic.
|
|
|
|
|
pub struct Proposer<C: Api + Send + Sync> {
|
|
|
|
|
pub struct Proposer<C: AuthoringApi + TPClient> {
|
|
|
|
|
client: Arc<C>,
|
|
|
|
|
start: Instant,
|
|
|
|
|
local_key: Arc<ed25519::Pair>,
|
|
|
|
|
parent_hash: Hash,
|
|
|
|
|
parent_id: BlockId,
|
|
|
|
|
parent_number: BlockNumber,
|
|
|
|
|
random_seed: Hash,
|
|
|
|
|
parent_hash: <<C as AuthoringApi>::Block as BlockT>::Hash,
|
|
|
|
|
parent_id: BlockId<<C as AuthoringApi>::Block>,
|
|
|
|
|
parent_number: <<<C as AuthoringApi>::Block as BlockT>::Header as HeaderT>::Number,
|
|
|
|
|
random_seed: <<C as AuthoringApi>::Block as BlockT>::Hash,
|
|
|
|
|
transaction_pool: Arc<TransactionPool<C>>,
|
|
|
|
|
offline: SharedOfflineTracker,
|
|
|
|
|
validators: Vec<AccountId>,
|
|
|
|
|
minimum_timestamp: u64,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<C: Api + Send + Sync> Proposer<C> {
|
|
|
|
|
impl<C: AuthoringApi + TPClient> Proposer<C> {
|
|
|
|
|
fn primary_index(&self, round_number: usize, len: usize) -> usize {
|
|
|
|
|
use primitives::uint::U256;
|
|
|
|
|
|
|
|
|
|
let big_len = U256::from(len);
|
|
|
|
|
let offset = U256::from_big_endian(&self.random_seed.0) % big_len;
|
|
|
|
|
let offset = U256::from_big_endian(self.random_seed.as_ref()) % big_len;
|
|
|
|
|
let offset = offset.low_u64() as usize + round_number;
|
|
|
|
|
offset % len
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl<C> bft::Proposer<Block> for Proposer<C>
|
|
|
|
|
where
|
|
|
|
|
C: Api + Send + Sync,
|
|
|
|
|
impl<C> bft::Proposer<<C as AuthoringApi>::Block> for Proposer<C> where
|
|
|
|
|
C: AuthoringApi + TPClient<Block=<C as AuthoringApi>::Block>,
|
|
|
|
|
<<C as AuthoringApi>::Block as BlockT>::Hash:
|
|
|
|
|
Into<<Runtime as SystemT>::Hash> + PartialEq<primitives::H256> + Into<primitives::H256>
|
|
|
|
|
{
|
|
|
|
|
type Create = Result<Block, Error>;
|
|
|
|
|
type Create = Result<<C as AuthoringApi>::Block>;
|
|
|
|
|
type Error = Error;
|
|
|
|
|
type Evaluate = Box<Future<Item=bool, Error=Error>>;
|
|
|
|
|
|
|
|
|
|
fn propose(&self) -> Result<Block, Error> {
|
|
|
|
|
use node_api::BlockBuilder;
|
|
|
|
|
use runtime_primitives::traits::{Hash as HashT, BlakeTwo256};
|
|
|
|
|
use node_primitives::InherentData;
|
|
|
|
|
fn propose(&self) -> Result<<C as AuthoringApi>::Block> {
|
|
|
|
|
use runtime_primitives::traits::BlakeTwo256;
|
|
|
|
|
|
|
|
|
|
const MAX_VOTE_OFFLINE_SECONDS: Duration = Duration::from_secs(60);
|
|
|
|
|
|
|
|
|
@@ -259,10 +360,10 @@ impl<C> bft::Proposer<Block> for Proposer<C>
|
|
|
|
|
let block = block_builder.bake()?;
|
|
|
|
|
|
|
|
|
|
info!("Proposing block [number: {}; hash: {}; parent_hash: {}; extrinsics: [{}]]",
|
|
|
|
|
block.header.number,
|
|
|
|
|
Hash::from(block.header.hash()),
|
|
|
|
|
block.header.parent_hash,
|
|
|
|
|
block.extrinsics.iter()
|
|
|
|
|
block.header().number(),
|
|
|
|
|
<<C as AuthoringApi>::Block as BlockT>::Hash::from(block.header().hash()),
|
|
|
|
|
block.header().parent_hash(),
|
|
|
|
|
block.extrinsics().iter()
|
|
|
|
|
.map(|xt| format!("{}", BlakeTwo256::hash_of(xt)))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ")
|
|
|
|
@@ -281,7 +382,7 @@ impl<C> bft::Proposer<Block> for Proposer<C>
|
|
|
|
|
Ok(substrate_block)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn evaluate(&self, unchecked_proposal: &Block) -> Self::Evaluate {
|
|
|
|
|
fn evaluate(&self, unchecked_proposal: &<C as AuthoringApi>::Block) -> Self::Evaluate {
|
|
|
|
|
debug!(target: "bft", "evaluating block on top of parent ({}, {:?})", self.parent_number, self.parent_hash);
|
|
|
|
|
|
|
|
|
|
let current_timestamp = current_timestamp();
|
|
|
|
@@ -359,10 +460,9 @@ impl<C> bft::Proposer<Block> for Proposer<C>
|
|
|
|
|
proposer
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn import_misbehavior(&self, misbehavior: Vec<(AuthorityId, bft::Misbehavior<Hash>)>) {
|
|
|
|
|
fn import_misbehavior(&self, misbehavior: Vec<(AuthorityId, bft::Misbehavior<<<C as AuthoringApi>::Block as BlockT>::Hash>)>) {
|
|
|
|
|
use rhododendron::Misbehavior as GenericMisbehavior;
|
|
|
|
|
use runtime_primitives::bft::{MisbehaviorKind, MisbehaviorReport};
|
|
|
|
|
use node_primitives::UncheckedExtrinsic as GenericExtrinsic;
|
|
|
|
|
use node_runtime::{Call, UncheckedExtrinsic, ConsensusCall};
|
|
|
|
|
|
|
|
|
|
let local_id = self.local_key.public().0.into();
|
|
|
|
@@ -371,7 +471,7 @@ impl<C> bft::Proposer<Block> for Proposer<C>
|
|
|
|
|
.filter(|tx| tx.verified.sender == local_id)
|
|
|
|
|
.last()
|
|
|
|
|
.map(|tx| Ok(tx.verified.index()))
|
|
|
|
|
.unwrap_or_else(|| self.client.index(&self.parent_id, local_id))
|
|
|
|
|
.unwrap_or_else(|| AuthoringApi::index(self.client.as_ref(), &self.parent_id, local_id))
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
match cur_index {
|
|
|
|
@@ -389,16 +489,16 @@ impl<C> bft::Proposer<Block> for Proposer<C>
|
|
|
|
|
|
|
|
|
|
for (target, misbehavior) in misbehavior {
|
|
|
|
|
let report = MisbehaviorReport {
|
|
|
|
|
parent_hash: self.parent_hash,
|
|
|
|
|
parent_number: self.parent_number,
|
|
|
|
|
parent_hash: self.parent_hash.into(),
|
|
|
|
|
parent_number: self.parent_number.as_(),
|
|
|
|
|
target,
|
|
|
|
|
misbehavior: match misbehavior {
|
|
|
|
|
GenericMisbehavior::ProposeOutOfTurn(_, _, _) => continue,
|
|
|
|
|
GenericMisbehavior::DoublePropose(_, _, _) => continue,
|
|
|
|
|
GenericMisbehavior::DoublePrepare(round, (h1, s1), (h2, s2))
|
|
|
|
|
=> MisbehaviorKind::BftDoublePrepare(round as u32, (h1, s1.signature), (h2, s2.signature)),
|
|
|
|
|
=> MisbehaviorKind::BftDoublePrepare(round as u32, (h1.into(), s1.signature), (h2.into(), s2.signature)),
|
|
|
|
|
GenericMisbehavior::DoubleCommit(round, (h1, s1), (h2, s2))
|
|
|
|
|
=> MisbehaviorKind::BftDoubleCommit(round as u32, (h1, s1.signature), (h2, s2.signature)),
|
|
|
|
|
=> MisbehaviorKind::BftDoubleCommit(round as u32, (h1.into(), s1.signature), (h2.into(), s2.signature)),
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
let payload = (next_index, Call::Consensus(ConsensusCall::report_misbehavior(report)), Era::immortal(), self.client.genesis_hash());
|
|
|
|
@@ -410,8 +510,9 @@ impl<C> bft::Proposer<Block> for Proposer<C>
|
|
|
|
|
signature: Some((node_runtime::RawAddress::Id(local_id), signature, payload.0, Era::immortal())),
|
|
|
|
|
function: payload.1,
|
|
|
|
|
};
|
|
|
|
|
let uxt: GenericExtrinsic = Decode::decode(&mut extrinsic.encode().as_slice()).expect("Encoded extrinsic is valid");
|
|
|
|
|
self.transaction_pool.submit_one(&BlockId::hash(self.parent_hash), uxt)
|
|
|
|
|
let uxt: <<C as AuthoringApi>::Block as BlockT>::Extrinsic = Decode::decode(&mut extrinsic.encode().as_slice()).expect("Encoded extrinsic is valid");
|
|
|
|
|
let hash = BlockId::<<C as AuthoringApi>::Block>::hash(self.parent_hash);
|
|
|
|
|
self.transaction_pool.submit_one(&hash, uxt)
|
|
|
|
|
.expect("locally signed extrinsic is valid; qed");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|