mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 13:01:07 +00:00
a782021ee8
* recover tx pool on light client * revert local tests fix * removed import renamings * futures03::Future -> std::future::Future * Update core/transaction-pool/graph/src/error.rs Co-Authored-By: Tomasz Drwięga <tomusdrw@users.noreply.github.com> * replace remove_from_ready with remove_invalid * avoid excess hashing * debug -> warn * TransactionPool + BasicTransactionPool * pause future tx reject when resubmitting * bump impl_version to make CI happy * and revert back local test fixes * alter doc to restart CI * Transaction::clone() -> Transaction::duplicate() * transactions -> updated_tranasctions * remove explicit consensus-common ref * ::std:: -> std:: * manual set/unset flag -> calling clusore with given flag value * removed comments * removed force argument * BestIterator -> Box<Iterator> * separate crate for TxPool + Maintainer trait * long line fix * pos-merge fix * fix benches compilation * Rename txpoolapi to txpool_api * Clean up. * Finalize merge. * post-merge fix * Move transaction pool api to primitives directly. * Consistent naming for txpool-runtime-api * Warn about missing docs. * Move abstraction for offchain calls to tx-pool-api. * Merge RPC instantiation. * Update cargo.lock * Post merge fixes. * Avoid depending on client. * Fix build
588 lines
18 KiB
Rust
588 lines
18 KiB
Rust
// 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/>.
|
|
|
|
use std::{
|
|
marker::{PhantomData, Unpin},
|
|
sync::Arc,
|
|
time::Instant,
|
|
};
|
|
use futures::{
|
|
Future, FutureExt,
|
|
future::{Either, join, ready},
|
|
};
|
|
use log::warn;
|
|
use parking_lot::Mutex;
|
|
|
|
use client_api::{
|
|
client::BlockBody,
|
|
light::{Fetcher, RemoteBodyRequest},
|
|
};
|
|
use primitives::{Blake2Hasher, H256};
|
|
use sr_primitives::{
|
|
generic::BlockId,
|
|
traits::{Block as BlockT, Extrinsic, Header, NumberFor, ProvideRuntimeApi, SimpleArithmetic},
|
|
};
|
|
use sp_blockchain::HeaderBackend;
|
|
use txpool_api::TransactionPoolMaintainer;
|
|
use txpool_runtime_api::TaggedTransactionQueue;
|
|
|
|
use txpool::{self, ChainApi};
|
|
|
|
/// Basic transaction pool maintainer for full clients.
|
|
pub struct FullBasicPoolMaintainer<Client, PoolApi: ChainApi> {
|
|
pool: Arc<txpool::Pool<PoolApi>>,
|
|
client: Arc<Client>,
|
|
}
|
|
|
|
impl<Client, PoolApi: ChainApi> FullBasicPoolMaintainer<Client, PoolApi> {
|
|
/// Create new basic full pool maintainer.
|
|
pub fn new(
|
|
pool: Arc<txpool::Pool<PoolApi>>,
|
|
client: Arc<Client>,
|
|
) -> Self {
|
|
FullBasicPoolMaintainer { pool, client }
|
|
}
|
|
}
|
|
|
|
impl<Block, Client, PoolApi> TransactionPoolMaintainer
|
|
for
|
|
FullBasicPoolMaintainer<Client, PoolApi>
|
|
where
|
|
Block: BlockT<Hash = <Blake2Hasher as primitives::Hasher>::Out>,
|
|
Client: ProvideRuntimeApi + HeaderBackend<Block> + BlockBody<Block> + 'static,
|
|
Client::Api: TaggedTransactionQueue<Block>,
|
|
PoolApi: ChainApi<Block = Block, Hash = H256> + 'static,
|
|
{
|
|
type Block = Block;
|
|
type Hash = Block::Hash;
|
|
|
|
fn maintain(
|
|
&self,
|
|
id: &BlockId<Block>,
|
|
retracted: &[Block::Hash],
|
|
) -> Box<dyn Future<Output=()> + Send + Unpin> {
|
|
// Put transactions from retracted blocks back into the pool.
|
|
let client_copy = self.client.clone();
|
|
let retracted_transactions = retracted.to_vec().into_iter()
|
|
.filter_map(move |hash| client_copy.block_body(&BlockId::hash(hash)).ok().unwrap_or(None))
|
|
.flat_map(|block| block.into_iter())
|
|
.filter(|tx| tx.is_signed().unwrap_or(false));
|
|
let resubmit_future = self.pool
|
|
.submit_at(id, retracted_transactions, true)
|
|
.then(|resubmit_result| ready(match resubmit_result {
|
|
Ok(_) => (),
|
|
Err(e) => {
|
|
warn!("Error re-submitting transactions: {:?}", e);
|
|
()
|
|
}
|
|
}));
|
|
|
|
// Avoid calling into runtime if there is nothing to prune from the pool anyway.
|
|
if self.pool.status().is_empty() {
|
|
return Box::new(resubmit_future)
|
|
}
|
|
|
|
let block = (self.client.header(*id), self.client.block_body(id));
|
|
match block {
|
|
(Ok(Some(header)), Ok(Some(extrinsics))) => {
|
|
let parent_id = BlockId::hash(*header.parent_hash());
|
|
let prune_future = self.pool
|
|
.prune(id, &parent_id, &extrinsics)
|
|
.then(|prune_result| ready(match prune_result {
|
|
Ok(_) => (),
|
|
Err(e) => {
|
|
warn!("Error pruning transactions: {:?}", e);
|
|
()
|
|
}
|
|
}));
|
|
|
|
Box::new(resubmit_future.then(|_| prune_future))
|
|
},
|
|
(Ok(_), Ok(_)) => Box::new(resubmit_future),
|
|
err => {
|
|
warn!("Error reading block: {:?}", err);
|
|
Box::new(resubmit_future)
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Basic transaction pool maintainer for light clients.
|
|
pub struct LightBasicPoolMaintainer<Block: BlockT, Client, PoolApi: ChainApi, F> {
|
|
pool: Arc<txpool::Pool<PoolApi>>,
|
|
client: Arc<Client>,
|
|
fetcher: Arc<F>,
|
|
revalidate_time_period: Option<std::time::Duration>,
|
|
revalidate_block_period: Option<NumberFor<Block>>,
|
|
revalidation_status: Arc<Mutex<TxPoolRevalidationStatus<NumberFor<Block>>>>,
|
|
_phantom: PhantomData<Block>,
|
|
}
|
|
|
|
impl<Block, Client, PoolApi, F> LightBasicPoolMaintainer<Block, Client, PoolApi, F>
|
|
where
|
|
Block: BlockT<Hash = <Blake2Hasher as primitives::Hasher>::Out>,
|
|
Client: ProvideRuntimeApi + HeaderBackend<Block> + BlockBody<Block> + 'static,
|
|
Client::Api: TaggedTransactionQueue<Block>,
|
|
PoolApi: ChainApi<Block = Block, Hash = H256> + 'static,
|
|
F: Fetcher<Block> + 'static,
|
|
{
|
|
/// Create light pool maintainer with default constants.
|
|
///
|
|
/// Default constants are: revalidate every 60 seconds or every 20 blocks
|
|
/// (whatever happens first).
|
|
pub fn with_defaults(
|
|
pool: Arc<txpool::Pool<PoolApi>>,
|
|
client: Arc<Client>,
|
|
fetcher: Arc<F>,
|
|
) -> Self {
|
|
Self::new(
|
|
pool,
|
|
client,
|
|
fetcher,
|
|
Some(std::time::Duration::from_secs(60)),
|
|
Some(20.into()),
|
|
)
|
|
}
|
|
|
|
/// Create light pool maintainer with passed constants.
|
|
pub fn new(
|
|
pool: Arc<txpool::Pool<PoolApi>>,
|
|
client: Arc<Client>,
|
|
fetcher: Arc<F>,
|
|
revalidate_time_period: Option<std::time::Duration>,
|
|
revalidate_block_period: Option<NumberFor<Block>>,
|
|
) -> Self {
|
|
Self {
|
|
pool,
|
|
client,
|
|
fetcher,
|
|
revalidate_time_period,
|
|
revalidate_block_period,
|
|
revalidation_status: Arc::new(Mutex::new(TxPoolRevalidationStatus::NotScheduled)),
|
|
_phantom: Default::default(),
|
|
}
|
|
}
|
|
|
|
/// Returns future that prunes block transactions from the pool.
|
|
fn prune(
|
|
&self,
|
|
id: &BlockId<Block>,
|
|
header: &Block::Header,
|
|
) -> impl std::future::Future<Output = ()> {
|
|
// fetch transactions (possible future optimization: proofs of inclusion) that
|
|
// have been included into new block and prune these from the pool
|
|
let id = id.clone();
|
|
let pool = self.pool.clone();
|
|
self.fetcher.remote_body(RemoteBodyRequest {
|
|
header: header.clone(),
|
|
retry_count: None,
|
|
})
|
|
.then(move |transactions| ready(
|
|
transactions
|
|
.map_err(|e| format!("{}", e))
|
|
.and_then(|transactions| {
|
|
let hashes = transactions
|
|
.into_iter()
|
|
.map(|tx| pool.hash_of(&tx))
|
|
.collect::<Vec<_>>();
|
|
pool.prune_known(&id, &hashes)
|
|
.map_err(|e| format!("{}", e))
|
|
})
|
|
))
|
|
.then(|r| {
|
|
if let Err(e) = r {
|
|
warn!("Error pruning known transactions: {}", e)
|
|
}
|
|
ready(())
|
|
})
|
|
}
|
|
|
|
/// Returns future that performs in-pool transations revalidation, if required.
|
|
fn revalidate(
|
|
&self,
|
|
id: &BlockId<Block>,
|
|
header: &Block::Header,
|
|
) -> impl std::future::Future<Output = ()> {
|
|
// to determine whether ready transaction is still valid, we perform periodic revalidaton
|
|
// of ready transactions
|
|
let is_revalidation_required = self.revalidation_status.lock().is_required(
|
|
*header.number(),
|
|
self.revalidate_time_period,
|
|
self.revalidate_block_period,
|
|
);
|
|
match is_revalidation_required {
|
|
true => {
|
|
let revalidation_status = self.revalidation_status.clone();
|
|
Either::Left(self.pool
|
|
.revalidate_ready(id)
|
|
.map(|r| r.map_err(|e| warn!("Error revalidating known transactions: {}", e)))
|
|
.map(move |_| revalidation_status.lock().clear()))
|
|
},
|
|
false => Either::Right(ready(())),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<Block, Client, PoolApi, F> TransactionPoolMaintainer
|
|
for
|
|
LightBasicPoolMaintainer<Block, Client, PoolApi, F>
|
|
where
|
|
Block: BlockT<Hash = <Blake2Hasher as primitives::Hasher>::Out>,
|
|
Client: ProvideRuntimeApi + HeaderBackend<Block> + BlockBody<Block> + 'static,
|
|
Client::Api: TaggedTransactionQueue<Block>,
|
|
PoolApi: ChainApi<Block = Block, Hash = H256> + 'static,
|
|
F: Fetcher<Block> + 'static,
|
|
{
|
|
type Block = Block;
|
|
type Hash = Block::Hash;
|
|
|
|
fn maintain(
|
|
&self,
|
|
id: &BlockId<Block>,
|
|
_retracted: &[Block::Hash],
|
|
) -> Box<dyn Future<Output=()> + Send + Unpin> {
|
|
// Do nothing if transaction pool is empty.
|
|
if self.pool.status().is_empty() {
|
|
self.revalidation_status.lock().clear();
|
|
return Box::new(ready(()));
|
|
}
|
|
let header = self.client.header(*id)
|
|
.and_then(|h| h.ok_or(sp_blockchain::Error::UnknownBlock(format!("{}", id))));
|
|
let header = match header {
|
|
Ok(header) => header,
|
|
Err(err) => {
|
|
println!("Failed to maintain light tx pool: {:?}", err);
|
|
return Box::new(ready(()));
|
|
}
|
|
};
|
|
|
|
// else prune block transactions from the pool
|
|
let prune_future = self.prune(id, &header);
|
|
|
|
// and then (optionally) revalidate in-pool transactions
|
|
let revalidate_future = self.revalidate(id, &header);
|
|
|
|
let maintain_future = join(
|
|
prune_future,
|
|
revalidate_future,
|
|
).map(|_| ());
|
|
|
|
Box::new(maintain_future)
|
|
}
|
|
}
|
|
|
|
/// The status of transactions revalidation at light tx pool.
|
|
#[cfg_attr(test, derive(Debug))]
|
|
enum TxPoolRevalidationStatus<N> {
|
|
/// The revalidation has never been completed.
|
|
NotScheduled,
|
|
/// The revalidation is scheduled.
|
|
Scheduled(Option<std::time::Instant>, Option<N>),
|
|
/// The revalidation is in progress.
|
|
InProgress,
|
|
}
|
|
|
|
impl<N: Clone + Copy + SimpleArithmetic> TxPoolRevalidationStatus<N> {
|
|
/// Called when revalidation is completed.
|
|
pub fn clear(&mut self) {
|
|
*self = TxPoolRevalidationStatus::NotScheduled;
|
|
}
|
|
|
|
/// Returns true if revalidation is required.
|
|
pub fn is_required(
|
|
&mut self,
|
|
block: N,
|
|
revalidate_time_period: Option<std::time::Duration>,
|
|
revalidate_block_period: Option<N>,
|
|
) -> bool {
|
|
match *self {
|
|
TxPoolRevalidationStatus::NotScheduled => {
|
|
*self = TxPoolRevalidationStatus::Scheduled(
|
|
revalidate_time_period.map(|period| Instant::now() + period),
|
|
revalidate_block_period.map(|period| block + period),
|
|
);
|
|
false
|
|
},
|
|
TxPoolRevalidationStatus::Scheduled(revalidate_at_time, revalidate_at_block) => {
|
|
let is_required = revalidate_at_time.map(|at| Instant::now() >= at).unwrap_or(false)
|
|
|| revalidate_at_block.map(|at| block >= at).unwrap_or(false);
|
|
if is_required {
|
|
*self = TxPoolRevalidationStatus::InProgress;
|
|
}
|
|
is_required
|
|
},
|
|
TxPoolRevalidationStatus::InProgress => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use futures::executor::block_on;
|
|
use codec::Encode;
|
|
use test_client::{prelude::*, runtime::{Block, Transfer}, consensus::{BlockOrigin, SelectChain}};
|
|
use txpool_api::PoolStatus;
|
|
use crate::api::{FullChainApi, LightChainApi};
|
|
|
|
#[test]
|
|
fn should_remove_transactions_from_the_full_pool() {
|
|
let (client, longest_chain) = TestClientBuilder::new().build_with_longest_chain();
|
|
let client = Arc::new(client);
|
|
let pool = txpool::Pool::new(Default::default(), FullChainApi::new(client.clone()));
|
|
let pool = Arc::new(pool);
|
|
let transaction = Transfer {
|
|
amount: 5,
|
|
nonce: 0,
|
|
from: AccountKeyring::Alice.into(),
|
|
to: Default::default(),
|
|
}.into_signed_tx();
|
|
let best = longest_chain.best_chain().unwrap();
|
|
|
|
// store the transaction in the pool
|
|
block_on(pool.submit_one(&BlockId::hash(best.hash()), transaction.clone())).unwrap();
|
|
|
|
// import the block
|
|
let mut builder = client.new_block(Default::default()).unwrap();
|
|
builder.push(transaction.clone()).unwrap();
|
|
let block = builder.bake().unwrap();
|
|
let id = BlockId::hash(block.header().hash());
|
|
client.import(BlockOrigin::Own, block).unwrap();
|
|
|
|
// fire notification - this should clean up the queue
|
|
assert_eq!(pool.status().ready, 1);
|
|
block_on(FullBasicPoolMaintainer::new(pool.clone(), client).maintain(&id, &[]));
|
|
|
|
// then
|
|
assert_eq!(pool.status().ready, 0);
|
|
assert_eq!(pool.status().future, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn should_remove_transactions_from_the_light_pool() {
|
|
let transaction = Transfer {
|
|
amount: 5,
|
|
nonce: 0,
|
|
from: AccountKeyring::Alice.into(),
|
|
to: Default::default(),
|
|
}.into_signed_tx();
|
|
let fetcher_transaction = transaction.clone();
|
|
let fetcher = Arc::new(test_client::new_light_fetcher()
|
|
.with_remote_body(Some(Box::new(move |_| Ok(vec![fetcher_transaction.clone()]))))
|
|
.with_remote_call(Some(Box::new(move |_| {
|
|
let validity: sr_primitives::transaction_validity::TransactionValidity =
|
|
Ok(sr_primitives::transaction_validity::ValidTransaction {
|
|
priority: 0,
|
|
requires: Vec::new(),
|
|
provides: vec![vec![42]],
|
|
longevity: 0,
|
|
propagate: true,
|
|
});
|
|
Ok(validity.encode())
|
|
}))));
|
|
|
|
let (client, longest_chain) = TestClientBuilder::new().build_with_longest_chain();
|
|
let client = Arc::new(client);
|
|
let pool = txpool::Pool::new(Default::default(), LightChainApi::new(
|
|
client.clone(),
|
|
fetcher.clone(),
|
|
));
|
|
let pool = Arc::new(pool);
|
|
let best = longest_chain.best_chain().unwrap();
|
|
|
|
// store the transaction in the pool
|
|
block_on(pool.submit_one(&BlockId::hash(best.hash()), transaction.clone())).unwrap();
|
|
|
|
// fire notification - this should clean up the queue
|
|
assert_eq!(pool.status().ready, 1);
|
|
block_on(LightBasicPoolMaintainer::with_defaults(pool.clone(), client.clone(), fetcher).maintain(
|
|
&BlockId::Number(0),
|
|
&[],
|
|
));
|
|
|
|
// then
|
|
assert_eq!(pool.status().ready, 0);
|
|
assert_eq!(pool.status().future, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn should_schedule_transactions_revalidation_at_light_pool() {
|
|
// when revalidation is not scheduled, it became scheduled
|
|
let mut status = TxPoolRevalidationStatus::NotScheduled;
|
|
assert!(!status.is_required(10u32, None, None));
|
|
match status {
|
|
TxPoolRevalidationStatus::Scheduled(_, _) => (),
|
|
_ => panic!("Unexpected status: {:?}", status),
|
|
}
|
|
|
|
// revalidation required at time
|
|
let mut status = TxPoolRevalidationStatus::Scheduled(Some(Instant::now()), None);
|
|
assert!(status.is_required(10u32, None, None));
|
|
match status {
|
|
TxPoolRevalidationStatus::InProgress => (),
|
|
_ => panic!("Unexpected status: {:?}", status),
|
|
}
|
|
|
|
// revalidation required at block
|
|
let mut status = TxPoolRevalidationStatus::Scheduled(None, Some(10));
|
|
assert!(status.is_required(10u32, None, None));
|
|
match status {
|
|
TxPoolRevalidationStatus::InProgress => (),
|
|
_ => panic!("Unexpected status: {:?}", status),
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn should_revalidate_transactions_at_light_pool() {
|
|
use std::sync::atomic;
|
|
use sr_primitives::transaction_validity::*;
|
|
|
|
let build_fetcher = || {
|
|
let validated = Arc::new(atomic::AtomicBool::new(false));
|
|
Arc::new(test_client::new_light_fetcher()
|
|
.with_remote_body(Some(Box::new(move |_| Ok(vec![]))))
|
|
.with_remote_call(Some(Box::new(move |_| {
|
|
let is_inserted = validated.swap(true, atomic::Ordering::SeqCst);
|
|
let validity: TransactionValidity = if is_inserted {
|
|
Err(TransactionValidityError::Invalid(
|
|
InvalidTransaction::Custom(0)
|
|
))
|
|
} else {
|
|
Ok(ValidTransaction {
|
|
priority: 0,
|
|
requires: Vec::new(),
|
|
provides: vec![vec![42]],
|
|
longevity: 0,
|
|
propagate: true,
|
|
})
|
|
};
|
|
Ok(validity.encode())
|
|
}))))
|
|
};
|
|
|
|
fn with_fetcher_maintain<F: Fetcher<Block> + 'static>(
|
|
fetcher: Arc<F>,
|
|
revalidate_time_period: Option<std::time::Duration>,
|
|
revalidate_block_period: Option<u64>,
|
|
prepare_maintainer: impl Fn(&Mutex<TxPoolRevalidationStatus<u64>>),
|
|
) -> PoolStatus {
|
|
let (client, longest_chain) = TestClientBuilder::new().build_with_longest_chain();
|
|
let client = Arc::new(client);
|
|
|
|
// now let's prepare pool
|
|
let pool = txpool::Pool::new(Default::default(), LightChainApi::new(
|
|
client.clone(),
|
|
fetcher.clone(),
|
|
));
|
|
let pool = Arc::new(pool);
|
|
let best = longest_chain.best_chain().unwrap();
|
|
|
|
// let's prepare maintainer
|
|
let maintainer = LightBasicPoolMaintainer::new(
|
|
pool.clone(),
|
|
client,
|
|
fetcher,
|
|
revalidate_time_period,
|
|
revalidate_block_period,
|
|
);
|
|
prepare_maintainer(&*maintainer.revalidation_status);
|
|
|
|
// store the transaction in the pool
|
|
block_on(pool.submit_one(
|
|
&BlockId::hash(best.hash()),
|
|
Transfer {
|
|
amount: 5,
|
|
nonce: 0,
|
|
from: AccountKeyring::Alice.into(),
|
|
to: Default::default(),
|
|
}.into_signed_tx(),
|
|
)).unwrap();
|
|
|
|
// and run maintain procedures
|
|
block_on(maintainer.maintain(&BlockId::Number(0), &[]));
|
|
|
|
pool.status()
|
|
}
|
|
|
|
// when revalidation is never required - nothing happens
|
|
let fetcher = build_fetcher();
|
|
//let maintainer = DefaultLightTransactionPoolMaintainer::new(client.clone(), fetcher.clone(), None, None);
|
|
let status = with_fetcher_maintain(fetcher, None, None, |_revalidation_status| {});
|
|
assert_eq!(status.ready, 1);
|
|
|
|
// when revalidation is scheduled by time - it is performed
|
|
let fetcher = build_fetcher();
|
|
let status = with_fetcher_maintain(fetcher, None, None, |revalidation_status|
|
|
*revalidation_status.lock() = TxPoolRevalidationStatus::Scheduled(Some(Instant::now()), None)
|
|
);
|
|
assert_eq!(status.ready, 0);
|
|
|
|
// when revalidation is scheduled by block number - it is performed
|
|
let fetcher = build_fetcher();
|
|
let status = with_fetcher_maintain(fetcher, None, None, |revalidation_status|
|
|
*revalidation_status.lock() = TxPoolRevalidationStatus::Scheduled(None, Some(0))
|
|
);
|
|
assert_eq!(status.ready, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn should_add_reverted_transactions_to_the_pool() {
|
|
let (client, longest_chain) = TestClientBuilder::new().build_with_longest_chain();
|
|
let client = Arc::new(client);
|
|
let pool = txpool::Pool::new(Default::default(), FullChainApi::new(client.clone()));
|
|
let pool = Arc::new(pool);
|
|
let transaction = Transfer {
|
|
amount: 5,
|
|
nonce: 0,
|
|
from: AccountKeyring::Alice.into(),
|
|
to: Default::default(),
|
|
}.into_signed_tx();
|
|
let best = longest_chain.best_chain().unwrap();
|
|
|
|
// store the transaction in the pool
|
|
block_on(pool.submit_one(&BlockId::hash(best.hash()), transaction.clone())).unwrap();
|
|
|
|
// import the block
|
|
let mut builder = client.new_block(Default::default()).unwrap();
|
|
builder.push(transaction.clone()).unwrap();
|
|
let block = builder.bake().unwrap();
|
|
let block1_hash = block.header().hash();
|
|
let id = BlockId::hash(block1_hash.clone());
|
|
client.import(BlockOrigin::Own, block).unwrap();
|
|
|
|
// fire notification - this should clean up the queue
|
|
assert_eq!(pool.status().ready, 1);
|
|
block_on(FullBasicPoolMaintainer::new(pool.clone(), client.clone()).maintain(&id, &[]));
|
|
|
|
// then
|
|
assert_eq!(pool.status().ready, 0);
|
|
assert_eq!(pool.status().future, 0);
|
|
|
|
// import second block
|
|
let builder = client.new_block_at(&BlockId::hash(best.hash()), Default::default()).unwrap();
|
|
let block = builder.bake().unwrap();
|
|
let id = BlockId::hash(block.header().hash());
|
|
client.import(BlockOrigin::Own, block).unwrap();
|
|
|
|
// fire notification - this should add the transaction back to the pool.
|
|
block_on(FullBasicPoolMaintainer::new(pool.clone(), client).maintain(&id, &[block1_hash]));
|
|
|
|
// then
|
|
assert_eq!(pool.status().ready, 1);
|
|
assert_eq!(pool.status().future, 0);
|
|
}
|
|
}
|