feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,341 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Tests for fork-aware transaction pool.
use pezsc_transaction_pool::{ChainApi, PoolLimit};
use pezsc_transaction_pool_api::ChainEvent;
use pezsp_runtime::transaction_validity::TransactionSource;
use std::sync::Arc;
use bizinikiwi_test_runtime_client::{
runtime::{Block, Hash, Header},
Sr25519Keyring::*,
};
use bizinikiwi_test_runtime_transaction_pool::{uxt, TestApi};
pub const LOG_TARGET: &str = "txpool";
use pezsc_transaction_pool::ForkAwareTxPool;
pub fn invalid_hash() -> Hash {
Default::default()
}
pub fn new_best_block_event(
pool: &ForkAwareTxPool<TestApi, Block>,
from: Option<Hash>,
to: Hash,
) -> ChainEvent<Block> {
ChainEvent::NewBestBlock {
hash: to,
tree_route: from.map(|from| {
// note: real tree route in NewBestBlock event does not contain 'to' block.
Arc::from(
pool.api()
.tree_route(from, pool.api().block_header(to).unwrap().unwrap().parent_hash)
.expect("Tree route exists"),
)
}),
}
}
pub fn finalized_block_event(
pool: &ForkAwareTxPool<TestApi, Block>,
from: Hash,
to: Hash,
) -> ChainEvent<Block> {
let t = pool.api().tree_route(from, to).expect("Tree route exists");
let e = t.enacted().iter().map(|h| h.hash).collect::<Vec<_>>();
ChainEvent::Finalized { hash: to, tree_route: Arc::from(&e[0..e.len() - 1]) }
}
pub struct TestPoolBuilder {
api: Option<Arc<TestApi>>,
use_default_limits: bool,
ready_limits: pezsc_transaction_pool::PoolLimit,
future_limits: pezsc_transaction_pool::PoolLimit,
mempool_max_transactions_count: usize,
finality_timeout_threshold: Option<usize>,
}
impl Default for TestPoolBuilder {
fn default() -> Self {
Self {
api: None,
use_default_limits: true,
ready_limits: PoolLimit { count: 8192, total_bytes: 20 * 1024 * 1024 },
future_limits: PoolLimit { count: 512, total_bytes: 1 * 1024 * 1024 },
mempool_max_transactions_count: usize::MAX,
finality_timeout_threshold: None,
}
}
}
impl TestPoolBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn with_api(mut self, api: Arc<TestApi>) -> Self {
self.api = Some(api);
self
}
pub fn with_mempool_count_limit(mut self, mempool_count_limit: usize) -> Self {
self.mempool_max_transactions_count = mempool_count_limit;
self.use_default_limits = false;
self
}
pub fn with_ready_count(mut self, ready_count: usize) -> Self {
self.ready_limits.count = ready_count;
self.use_default_limits = false;
self
}
pub fn with_ready_bytes_size(mut self, ready_bytes_size: usize) -> Self {
self.ready_limits.total_bytes = ready_bytes_size;
self.use_default_limits = false;
self
}
pub fn with_future_count(mut self, future_count: usize) -> Self {
self.future_limits.count = future_count;
self.use_default_limits = false;
self
}
pub fn with_future_bytes_size(mut self, future_bytes_size: usize) -> Self {
self.future_limits.total_bytes = future_bytes_size;
self.use_default_limits = false;
self
}
pub fn with_finality_timeout_threshold(mut self, threshold: usize) -> Self {
self.finality_timeout_threshold = Some(threshold);
self
}
pub fn build(
self,
) -> (ForkAwareTxPool<TestApi, Block>, Arc<TestApi>, futures::executor::ThreadPool) {
let api = self
.api
.unwrap_or(Arc::from(TestApi::with_alice_nonce(200).enable_stale_check()));
let genesis_hash = api
.chain()
.read()
.block_by_number
.get(&0)
.map(|blocks| blocks[0].0.header.hash())
.expect("there is block 0. qed");
let (pool, [txpool_task, blocking_task]) = if self.use_default_limits {
ForkAwareTxPool::new_test(
api.clone(),
genesis_hash,
genesis_hash,
self.finality_timeout_threshold,
)
} else {
ForkAwareTxPool::new_test_with_limits(
api.clone(),
genesis_hash,
genesis_hash,
self.ready_limits,
self.future_limits,
self.mempool_max_transactions_count,
self.finality_timeout_threshold,
)
};
let thread_pool = futures::executor::ThreadPool::new().unwrap();
thread_pool.spawn_ok(txpool_task);
thread_pool.spawn_ok(blocking_task);
(pool, api, thread_pool)
}
}
pub fn pool_with_api(
test_api: Arc<TestApi>,
) -> (ForkAwareTxPool<TestApi, Block>, futures::executor::ThreadPool) {
let builder = TestPoolBuilder::new();
let (pool, _, threadpool) = builder.with_api(test_api).build();
(pool, threadpool)
}
pub fn pool() -> (ForkAwareTxPool<TestApi, Block>, Arc<TestApi>, futures::executor::ThreadPool) {
let builder = TestPoolBuilder::new();
builder.build()
}
#[macro_export]
macro_rules! assert_pool_status {
($hash:expr, $pool:expr, $ready:expr, $future:expr) => {
{
tracing::debug!(target: LOG_TARGET, stats = ?$pool.status_all(), "Pool stats");
let status = &$pool.status_all()[&$hash];
assert_eq!(status.ready, $ready, "ready");
assert_eq!(status.future, $future, "future");
}
}
}
#[macro_export]
macro_rules! assert_ready_at_light_iterator {
($hash:expr, $pool:expr, [$( $xt:expr ),*]) => {{
let ready_iterator = $pool.ready_at_light($hash).now_or_never().unwrap();
let expected = vec![ $($pool.api().hash_and_length(&$xt).0),*];
let output: Vec<_> = ready_iterator.collect();
tracing::debug!(target: LOG_TARGET, ?expected, "expected");
tracing::debug!(target: LOG_TARGET, ?output, "output");
let output = output.into_iter().map(|t|t.hash).collect::<Vec<_>>();
assert_eq!(expected.len(), output.len());
assert_eq!(output,expected);
}};
}
#[macro_export]
macro_rules! assert_ready_iterator {
($hash:expr, $pool:expr, [$( $xt:expr ),*]) => {{
let ready_iterator = $pool.ready_at($hash).now_or_never().unwrap();
let expected = vec![ $($pool.api().hash_and_length(&$xt).0),*];
let output: Vec<_> = ready_iterator.collect();
tracing::debug!(target: LOG_TARGET, ?expected, "expected");
tracing::debug!(target: LOG_TARGET, ?output, "output");
let output = output.into_iter().map(|t|t.hash).collect::<Vec<_>>();
assert_eq!(expected.len(), output.len());
assert_eq!(output,expected);
}};
}
#[macro_export]
macro_rules! assert_future_iterator {
($hash:expr, $pool:expr, [$( $xt:expr ),*]) => {{
let futures = $pool.futures_at($hash).unwrap();
let expected = vec![ $($pool.api().hash_and_length(&$xt).0),*];
tracing::debug!(target: LOG_TARGET, ?expected, "expected");
tracing::debug!(target: LOG_TARGET, ?futures, "output");
assert_eq!(expected.len(), futures.len());
let hsf = futures.iter().map(|a| a.hash).collect::<std::collections::HashSet<_>>();
let hse = expected.into_iter().collect::<std::collections::HashSet<_>>();
assert_eq!(hse,hsf);
}};
}
#[macro_export]
macro_rules! assert_watcher_stream {
($stream:ident, [$( $event:expr ),*]) => {{
let expected = vec![ $($event),*];
tracing::debug!(
target: LOG_TARGET,
?expected,
expected_len = expected.len(),
"block now"
);
let output = futures::executor::block_on_stream($stream).take(expected.len()).collect::<Vec<_>>();
tracing::debug!(target: LOG_TARGET, ?output, "Output");
assert_eq!(expected.len(), output.len());
assert_eq!(output, expected);
}};
}
pub const SOURCE: TransactionSource = TransactionSource::External;
#[cfg(test)]
pub mod test_chain_with_forks {
use super::*;
use tracing::debug;
pub fn chain(
include_xts: Option<&dyn Fn(usize, usize) -> bool>,
) -> (Arc<TestApi>, Vec<Vec<Header>>) {
// Fork layout:
//
// (fork 0)
// F01 - F02 - F03 - F04 - F05 | Alice nonce increasing, alice's txs
// /
// F00
// \ (fork 1)
// F11 - F12 - F13 - F14 - F15 | Bob nonce increasing, Bob's txs
//
//
// e.g. F03 contains uxt(Alice, 202), nonces: Alice = 203, Bob = 200
// F12 contains uxt(Bob, 201), nonces: Alice = 200, Bob = 202
let api = Arc::from(TestApi::empty().enable_stale_check());
let genesis = api.genesis_hash();
let mut forks = vec![Vec::with_capacity(6), Vec::with_capacity(6)];
let accounts = vec![Alice, Bob];
accounts.iter().for_each(|a| api.set_nonce(genesis, (*a).into(), 200));
for fork in 0..2 {
let account = accounts[fork];
forks[fork].push(api.block_header(genesis).unwrap().unwrap());
let mut parent = genesis;
for block in 1..6 {
let xts = if include_xts.map_or(true, |v| v(fork, block)) {
debug!(fork = %fork, block = %block, "-> add");
vec![uxt(account, (200 + block - 1) as u64)]
} else {
debug!(fork = %fork, block = %block, "-> skip");
vec![]
};
let header = api.push_block_with_parent(parent, xts, true);
parent = header.hash();
api.set_nonce(header.hash(), account.into(), (200 + block) as u64);
forks[fork].push(header);
}
}
(api, forks)
}
pub fn print_block(api: Arc<TestApi>, hash: Hash) {
let accounts = vec![Alice.into(), Bob.into()];
let header = api.block_header(hash).unwrap().unwrap();
let nonces = accounts
.iter()
.map(|a| api.chain().read().nonces.get(&hash).unwrap().get(a).map(Clone::clone))
.collect::<Vec<_>>();
debug!(
number = ?header.number,
hash = ?header.hash(),
parent = ?header.parent_hash,
?nonces
);
}
#[test]
fn test_chain_works() {
pezsp_tracing::try_init_simple();
let (api, f) = chain(None);
debug!(forks = ?f, "forks");
f[0].iter().for_each(|h| print_block(api.clone(), h.hash()));
f[1].iter().for_each(|h| print_block(api.clone(), h.hash()));
let tr = api.tree_route(f[0][5].hash(), f[1][5].hash()).unwrap();
debug!(?tr);
debug!(enacted = ?tr.enacted());
debug!(retracted = ?tr.retracted());
}
}
@@ -0,0 +1,273 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Tests for finality timeout handling for fork-aware transaction pool.
pub mod fatp_common;
use std::cmp::min;
use fatp_common::{
finalized_block_event, invalid_hash, new_best_block_event, TestPoolBuilder, LOG_TARGET, SOURCE,
};
use futures::{executor::block_on, FutureExt};
use pezsc_transaction_pool::ChainApi;
use pezsc_transaction_pool_api::{MaintainedTransactionPool, TransactionPool, TransactionStatus};
use bizinikiwi_test_runtime_client::Sr25519Keyring::*;
use bizinikiwi_test_runtime_transaction_pool::uxt;
#[test]
fn fatp_finality_timeout_works() {
pezsp_tracing::try_init_simple();
const FINALITY_TIMEOUT_THRESHOLD: usize = 10;
let (pool, api, _) = TestPoolBuilder::new()
.with_finality_timeout_threshold(FINALITY_TIMEOUT_THRESHOLD)
.build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 4, 0);
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]);
let header02a = api.push_block_with_parent(
header01.hash(),
vec![xt0.clone(), xt1.clone(), xt2.clone(), xt3.clone()],
true,
);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash())));
assert_pool_status!(header02a.hash(), &pool, 0, 0);
let header02b = api.push_block_with_parent(header01.hash(), vec![xt0, xt1, xt2, xt3], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header02a.hash()), header02b.hash())));
assert_pool_status!(header02b.hash(), &pool, 0, 0);
let mut prev_header = header02b.clone();
for n in 3..66 {
let header = api.push_block_with_parent(prev_header.hash(), vec![], true);
let event = new_best_block_event(&pool, Some(prev_header.hash()), header.hash());
block_on(pool.maintain(event));
prev_header = header;
if n < 3 + FINALITY_TIMEOUT_THRESHOLD {
assert_eq!(pool.active_views_count(), 2);
} else {
assert_eq!(pool.active_views_count(), 1);
assert_eq!(pool.inactive_views_count(), FINALITY_TIMEOUT_THRESHOLD);
}
}
for (i, watcher) in
vec![xt0_watcher, xt1_watcher, xt2_watcher, xt3_watcher].into_iter().enumerate()
{
assert_watcher_stream!(
watcher,
[
TransactionStatus::Ready,
TransactionStatus::InBlock((header02a.hash(), i)),
TransactionStatus::InBlock((header02b.hash(), i)),
TransactionStatus::FinalityTimeout(min(header02a.hash(), header02b.hash()))
]
);
}
}
#[test]
fn fatp_finalized_still_works_after_finality_stall() {
pezsp_tracing::try_init_simple();
const FINALITY_TIMEOUT_THRESHOLD: usize = 10;
let (pool, api, _) = TestPoolBuilder::new()
.with_finality_timeout_threshold(FINALITY_TIMEOUT_THRESHOLD)
.build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header01.hash())));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 4, 0);
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]);
let header02a = api.push_block_with_parent(
header01.hash(),
vec![xt0.clone(), xt1.clone(), xt2.clone()],
true,
);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash())));
assert_pool_status!(header02a.hash(), &pool, 1, 0);
let header02b = api.push_block_with_parent(header01.hash(), vec![xt0, xt1, xt2], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header02a.hash()), header02b.hash())));
assert_pool_status!(header02b.hash(), &pool, 1, 0);
let header03b = api.push_block_with_parent(header02b.hash(), vec![xt3], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header02b.hash()), header03b.hash())));
assert_pool_status!(header03b.hash(), &pool, 0, 0);
let mut prev_header = header03b.clone();
for block_n in 4..=3 + FINALITY_TIMEOUT_THRESHOLD {
let header = api.push_block_with_parent(prev_header.hash(), vec![], true);
let event = new_best_block_event(&pool, Some(prev_header.hash()), header.hash());
block_on(pool.maintain(event));
prev_header = header;
if block_n == 3 + FINALITY_TIMEOUT_THRESHOLD {
//finality timeout triggered
assert_eq!(pool.active_views_count(), 1);
assert_eq!(pool.inactive_views_count(), FINALITY_TIMEOUT_THRESHOLD);
} else {
assert_eq!(pool.active_views_count(), 2);
}
}
block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header03b.hash())));
for (i, watcher) in vec![xt0_watcher, xt1_watcher, xt2_watcher].into_iter().enumerate() {
assert_watcher_stream!(
watcher,
[
TransactionStatus::Ready,
TransactionStatus::InBlock((header02a.hash(), i)),
TransactionStatus::InBlock((header02b.hash(), i)),
TransactionStatus::FinalityTimeout(min(header02a.hash(), header02b.hash()))
]
);
}
assert_watcher_stream!(
xt3_watcher,
[
TransactionStatus::Ready,
TransactionStatus::InBlock((header03b.hash(), 0)),
TransactionStatus::Finalized((header03b.hash(), 0))
]
);
}
#[test]
fn fatp_finality_timeout_works_for_txs_included_before_finalized() {
pezsp_tracing::try_init_simple();
const FINALITY_TIMEOUT_THRESHOLD: usize = 10;
let (pool, api, _) = TestPoolBuilder::new()
.with_finality_timeout_threshold(FINALITY_TIMEOUT_THRESHOLD)
.build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header01.hash())));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 4, 0);
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]);
let header02a = api.push_block_with_parent(
header01.hash(),
vec![xt0.clone(), xt1.clone(), xt2.clone()],
true,
);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash())));
assert_pool_status!(header02a.hash(), &pool, 1, 0);
let header02b = api.push_block_with_parent(header01.hash(), vec![xt0, xt1, xt2], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header02a.hash()), header02b.hash())));
assert_pool_status!(header02b.hash(), &pool, 1, 0);
let header03b = api.push_block_with_parent(header02b.hash(), vec![xt3], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header02b.hash()), header03b.hash())));
assert_pool_status!(header03b.hash(), &pool, 0, 0);
block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02b.hash())));
let mut prev_header = header03b.clone();
for block_n in 4..=4 + FINALITY_TIMEOUT_THRESHOLD {
let header = api.push_block_with_parent(prev_header.hash(), vec![], true);
let event = new_best_block_event(&pool, Some(prev_header.hash()), header.hash());
block_on(pool.maintain(event));
prev_header = header;
assert_eq!(pool.active_views_count(), 1);
if block_n == 4 + FINALITY_TIMEOUT_THRESHOLD {
//finality timeout triggered
assert_eq!(pool.inactive_views_count(), FINALITY_TIMEOUT_THRESHOLD);
}
}
for (i, watcher) in vec![xt0_watcher, xt1_watcher, xt2_watcher].into_iter().enumerate() {
assert_watcher_stream!(
watcher,
[
TransactionStatus::Ready,
TransactionStatus::InBlock((header02a.hash(), i)),
TransactionStatus::InBlock((header02b.hash(), i)),
TransactionStatus::Finalized((header02b.hash(), i))
]
);
}
assert_watcher_stream!(
xt3_watcher,
[
TransactionStatus::Ready,
TransactionStatus::InBlock((header03b.hash(), 0)),
TransactionStatus::FinalityTimeout(header03b.hash())
]
);
}
@@ -0,0 +1,673 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Tests of invalid transactions handling for fork-aware transaction pool.
pub mod fatp_common;
use fatp_common::{
finalized_block_event, invalid_hash, new_best_block_event, pool, TestPoolBuilder, LOG_TARGET,
SOURCE,
};
use futures::{executor::block_on, FutureExt};
use pezsc_transaction_pool::ChainApi;
use pezsc_transaction_pool_api::{
error::{Error as TxPoolError, IntoPoolError},
MaintainedTransactionPool, TransactionPool, TransactionStatus,
};
use pezsp_runtime::transaction_validity::{InvalidTransaction, TransactionValidityError};
use bizinikiwi_test_runtime_client::Sr25519Keyring::*;
use bizinikiwi_test_runtime_transaction_pool::uxt;
use tracing::debug;
#[test]
fn fatp_invalid_three_views_stale_gets_rejected() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = pool();
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Alice, 200);
let header02a = api.push_block_with_parent(header01.hash(), vec![], true);
let header02b = api.push_block_with_parent(header01.hash(), vec![], true);
let header02c = api.push_block_with_parent(header01.hash(), vec![], true);
api.set_nonce(header02a.hash(), Alice.into(), 201);
api.set_nonce(header02b.hash(), Alice.into(), 201);
api.set_nonce(header02c.hash(), Alice.into(), 201);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash())));
block_on(pool.maintain(new_best_block_event(&pool, Some(header02a.hash()), header02b.hash())));
block_on(pool.maintain(new_best_block_event(&pool, Some(header02b.hash()), header02c.hash())));
let result0 = block_on(pool.submit_one(invalid_hash(), SOURCE, xt0.clone()));
let result1 = block_on(pool.submit_one(invalid_hash(), SOURCE, xt1.clone()));
assert!(matches!(
result0.as_ref().unwrap_err().0,
TxPoolError::InvalidTransaction(InvalidTransaction::Stale)
));
assert!(matches!(
result1.as_ref().unwrap_err().0,
TxPoolError::InvalidTransaction(InvalidTransaction::Stale)
));
}
#[test]
fn fatp_invalid_three_views_invalid_gets_rejected() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = pool();
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Alice, 200);
let header02a = api.push_block_with_parent(header01.hash(), vec![], true);
let header02b = api.push_block_with_parent(header01.hash(), vec![], true);
let header02c = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash())));
block_on(pool.maintain(new_best_block_event(&pool, Some(header02a.hash()), header02b.hash())));
block_on(pool.maintain(new_best_block_event(&pool, Some(header02b.hash()), header02c.hash())));
api.add_invalid(&xt0);
api.add_invalid(&xt1);
let result0 = block_on(pool.submit_one(invalid_hash(), SOURCE, xt0.clone()));
let result1 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).map(|_| ());
assert!(matches!(
result0.as_ref().unwrap_err().0,
TxPoolError::InvalidTransaction(InvalidTransaction::Custom(_))
));
assert!(matches!(
result1.as_ref().unwrap_err().0,
TxPoolError::InvalidTransaction(InvalidTransaction::Custom(_))
));
}
#[test]
fn fatp_transactions_purging_invalid_on_finalization_works() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = pool();
let xt1 = uxt(Alice, 200);
let xt2 = uxt(Alice, 201);
let xt3 = uxt(Alice, 202);
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let watcher1 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let watcher2 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let watcher3 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_eq!(api.validation_requests().len(), 3);
assert_eq!(pool.status_all()[&header01.hash()].ready, 3);
assert_eq!(block_on(pool.mempool_len()), (0, 3));
let header02 = api.push_block(2, vec![], true);
api.add_invalid(&xt1);
api.add_invalid(&xt2);
api.add_invalid(&xt3);
block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash())));
// wait 10 blocks for revalidation
let mut prev_header = header02.clone();
for n in 3..=11 {
let header = api.push_block(n, vec![], true);
let event = finalized_block_event(&pool, prev_header.hash(), header.hash());
block_on(pool.maintain(event));
prev_header = header;
}
assert_eq!(block_on(pool.mempool_len()), (0, 0));
assert_watcher_stream!(watcher1, [TransactionStatus::Ready, TransactionStatus::Invalid]);
assert_watcher_stream!(watcher2, [TransactionStatus::Ready, TransactionStatus::Invalid]);
assert_watcher_stream!(watcher3, [TransactionStatus::Ready, TransactionStatus::Invalid]);
}
#[test]
fn fatp_transactions_purging_invalid_on_finalization_works2() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = pool();
let xt1 = uxt(Alice, 200);
let xt2 = uxt(Alice, 201);
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
block_on(pool.submit_one(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
assert_eq!(pool.status_all()[&header01.hash()].ready, 2);
assert_eq!(api.validation_requests().len(), 2);
let header02 = api.push_block(2, vec![xt1.clone()], true);
api.add_invalid(&xt2);
block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash())));
assert_eq!(pool.status_all()[&header02.hash()].ready, 1);
// wait 10 blocks for revalidation
let mut prev_header = header02.clone();
for _ in 3..=11 {
let header = api.push_block_with_parent(prev_header.hash(), vec![], true);
let event = finalized_block_event(&pool, prev_header.hash(), header.hash());
block_on(pool.maintain(event));
prev_header = header;
}
assert_watcher_stream!(watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]);
assert_eq!(pool.status_all()[&prev_header.hash()].ready, 0);
}
#[test]
fn should_not_retain_invalid_hashes_from_retracted() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = pool();
let xt = uxt(Alice, 200);
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt.clone())).unwrap();
let header02a = api.push_block_with_parent(header01.hash(), vec![xt.clone()], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash())));
assert_eq!(pool.status_all()[&header02a.hash()].ready, 0);
api.add_invalid(&xt);
let header02b = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02b.hash())));
// wait 10 blocks for revalidation
let mut prev_header = header02b.clone();
for _ in 3..=11 {
let header = api.push_block_with_parent(prev_header.hash(), vec![], true);
let event = finalized_block_event(&pool, prev_header.hash(), header.hash());
block_on(pool.maintain(event));
prev_header = header;
}
assert_watcher_stream!(
watcher,
[
TransactionStatus::Ready,
TransactionStatus::InBlock((header02a.hash(), 0)),
TransactionStatus::Invalid
]
);
//todo: shall revalidation check finalized (fork's tip) view?
assert_eq!(pool.status_all()[&prev_header.hash()].ready, 0);
}
#[test]
fn fatp_watcher_invalid_many_revalidation() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = pool();
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Alice, 201);
let xt2 = uxt(Alice, 202);
let xt3 = uxt(Alice, 203);
let xt4 = uxt(Alice, 204);
let submissions = vec![
pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()),
pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone()),
pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone()),
pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone()),
pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone()),
];
let submissions = block_on(futures::future::join_all(submissions));
assert_eq!(pool.status_all()[&header01.hash()].ready, 5);
let mut watchers = submissions.into_iter().map(Result::unwrap).collect::<Vec<_>>();
let xt4_watcher = watchers.remove(4);
let xt3_watcher = watchers.remove(3);
let xt2_watcher = watchers.remove(2);
let xt1_watcher = watchers.remove(1);
let xt0_watcher = watchers.remove(0);
api.add_invalid(&xt3);
api.add_invalid(&xt4);
let header02 = api.push_block(2, vec![], true);
block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash())));
//todo: shall revalidation check finalized (fork's tip) view?
assert_eq!(pool.status_all()[&header02.hash()].ready, 5);
let header03 = api.push_block(3, vec![xt0.clone(), xt1.clone(), xt2.clone()], true);
block_on(pool.maintain(finalized_block_event(&pool, header02.hash(), header03.hash())));
// wait 10 blocks for revalidation
let mut prev_header = header03.clone();
for n in 4..=11 {
let header = api.push_block(n, vec![], true);
let event = finalized_block_event(&pool, prev_header.hash(), header.hash());
block_on(pool.maintain(event));
prev_header = header;
}
assert_watcher_stream!(
xt0_watcher,
[
TransactionStatus::Ready,
TransactionStatus::InBlock((header03.hash(), 0)),
TransactionStatus::Finalized((header03.hash(), 0))
]
);
assert_watcher_stream!(
xt1_watcher,
[
TransactionStatus::Ready,
TransactionStatus::InBlock((header03.hash(), 1)),
TransactionStatus::Finalized((header03.hash(), 1))
]
);
assert_watcher_stream!(
xt2_watcher,
[
TransactionStatus::Ready,
TransactionStatus::InBlock((header03.hash(), 2)),
TransactionStatus::Finalized((header03.hash(), 2))
]
);
assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]);
assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]);
}
#[test]
fn fatp_watcher_invalid_fails_on_submission() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = pool();
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 150);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()));
let xt0_watcher = xt0_watcher.map(|_| ());
assert_pool_status!(header01.hash(), &pool, 0, 0);
// Alice's nonce in state is 200, tx is 150.
assert!(matches!(
xt0_watcher.unwrap_err().into_pool_error(),
Ok(TxPoolError::InvalidTransaction(InvalidTransaction::Stale))
));
}
#[test]
fn fatp_watcher_invalid_single_revalidation() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = pool();
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, Some(api.genesis_hash()), header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
api.add_invalid(&xt0);
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
let event = finalized_block_event(&pool, header01.hash(), header02.hash());
block_on(pool.maintain(event));
// wait 10 blocks for revalidation
let mut prev_header = header02;
for n in 3..=11 {
let header = api.push_block(n, vec![], true);
let event = finalized_block_event(&pool, prev_header.hash(), header.hash());
block_on(pool.maintain(event));
prev_header = header;
}
let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::<Vec<_>>();
debug!(target: LOG_TARGET, ?xt0_events, "xt0_events");
assert_eq!(xt0_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]);
}
#[test]
fn fatp_watcher_invalid_single_revalidation2() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = pool();
let xt0 = uxt(Alice, 200);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
assert_eq!(block_on(pool.mempool_len()), (0, 1));
let header01 = api.push_block(1, vec![], true);
let event = finalized_block_event(&pool, api.genesis_hash(), header01.hash());
block_on(pool.maintain(event));
api.add_invalid(&xt0);
// note: the tx will be revalidated in view::revalidation, not in mempool revalidation (which
// would require waiting 10 blocks).
// waiting 10 blocks is excessive, but we may want to keep it.
let mut prev_header = header01;
for n in 2..=11 {
let header = api.push_block(n, vec![], true);
let event = finalized_block_event(&pool, prev_header.hash(), header.hash());
block_on(pool.maintain(event));
prev_header = header;
}
let xt0_events = futures::executor::block_on_stream(xt0_watcher).collect::<Vec<_>>();
debug!(target: LOG_TARGET, ?xt0_events, "xt0_events");
assert_eq!(xt0_events, vec![TransactionStatus::Ready, TransactionStatus::Invalid]);
assert_eq!(block_on(pool.mempool_len()), (0, 0));
}
#[test]
fn fatp_invalid_report_stale_or_future_works_as_expected() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = TestPoolBuilder::new().build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 4, 0);
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]);
// future/stale are ignored when at is None
let xt0_report = (
pool.api().hash_and_length(&xt0).0,
Some(TransactionValidityError::Invalid(InvalidTransaction::Future)),
);
let invalid_txs = [xt0_report].into();
let result = block_on(pool.report_invalid(None, invalid_txs));
assert!(result.is_empty());
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]);
// future/stale are applied when at is provided
let xt0_report = (
pool.api().hash_and_length(&xt0).0,
Some(TransactionValidityError::Invalid(InvalidTransaction::Future)),
);
let xt1_report = (
pool.api().hash_and_length(&xt1).0,
Some(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
let invalid_txs = [xt0_report, xt1_report].into();
let result = block_on(pool.report_invalid(Some(header01.hash()), invalid_txs));
// stale/future does not cause tx to be removed from the pool
assert!(result.is_empty());
// assert_eq!(result[0].hash, pool.api().hash_and_length(&xt0).0);
assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]);
// None error means force removal
// todo
assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]);
}
#[test]
fn fatp_invalid_report_future_dont_remove_from_pool() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = TestPoolBuilder::new().build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
api.set_nonce(api.genesis_hash(), Eve.into(), 600);
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
let xt4 = uxt(Eve, 600);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 5, 0);
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3, xt4]);
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash())));
assert_pool_status!(header02.hash(), &pool, 5, 0);
assert_ready_iterator!(header02.hash(), pool, [xt0, xt1, xt2, xt3, xt4]);
let xt0_report = (
pool.api().hash_and_length(&xt0).0,
Some(TransactionValidityError::Invalid(InvalidTransaction::Stale)),
);
let xt1_report = (
pool.api().hash_and_length(&xt1).0,
Some(TransactionValidityError::Invalid(InvalidTransaction::Future)),
);
let xt4_report = (
pool.api().hash_and_length(&xt4).0,
Some(TransactionValidityError::Invalid(InvalidTransaction::BadProof)),
);
let invalid_txs = [xt0_report, xt1_report, xt4_report].into();
let result = block_on(pool.report_invalid(Some(header01.hash()), invalid_txs));
assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]);
// future does not cause tx to be removed from the pool
assert!(result.len() == 1);
assert!(result[0].hash == pool.api().hash_and_length(&xt4).0);
assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]);
assert_pool_status!(header02.hash(), &pool, 4, 0);
assert_ready_iterator!(header02.hash(), pool, [xt0, xt1, xt2, xt3]);
assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]);
}
#[test]
fn fatp_invalid_tx_is_removed_from_the_pool() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = TestPoolBuilder::new().build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 4, 0);
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]);
let xt0_report = (
pool.api().hash_and_length(&xt0).0,
Some(TransactionValidityError::Invalid(InvalidTransaction::BadProof)),
);
let xt1_report = (pool.api().hash_and_length(&xt1).0, None);
let invalid_txs = [xt0_report, xt1_report].into();
let result = block_on(pool.report_invalid(Some(header01.hash()), invalid_txs));
assert!(result.iter().any(|tx| tx.hash == pool.api().hash_and_length(&xt0).0));
assert_pool_status!(header01.hash(), &pool, 2, 0);
assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]);
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash())));
assert_pool_status!(header02.hash(), &pool, 2, 0);
assert_ready_iterator!(header02.hash(), pool, [xt2, xt3]);
assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]);
assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]);
assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]);
}
#[test]
fn fatp_invalid_tx_is_removed_from_the_pool_future_subtree_stays() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = TestPoolBuilder::new().build();
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Alice, 201);
let xt2 = uxt(Alice, 202);
let xt3 = uxt(Alice, 203);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 4, 0);
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]);
let xt0_report = (
pool.api().hash_and_length(&xt0).0,
Some(TransactionValidityError::Invalid(InvalidTransaction::BadProof)),
);
let invalid_txs = [xt0_report].into();
let result = block_on(pool.report_invalid(Some(header01.hash()), invalid_txs));
assert_eq!(result[0].hash, pool.api().hash_and_length(&xt0).0);
assert_pool_status!(header01.hash(), &pool, 0, 0);
assert_ready_iterator!(header01.hash(), pool, []);
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash())));
assert_pool_status!(header02.hash(), &pool, 0, 3);
assert_future_iterator!(header02.hash(), pool, [xt1, xt2, xt3]);
assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]);
assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]);
}
#[test]
fn fatp_invalid_tx_is_removed_from_the_pool2() {
pezsp_tracing::try_init_simple();
let (pool, api, _) = TestPoolBuilder::new().build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 4, 0);
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2, xt3]);
let header02a = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02a.hash())));
assert_pool_status!(header02a.hash(), &pool, 4, 0);
assert_ready_iterator!(header02a.hash(), pool, [xt0, xt1, xt2, xt3]);
let header02b = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header02a.hash()), header02b.hash())));
assert_pool_status!(header02b.hash(), &pool, 4, 0);
assert_ready_iterator!(header02b.hash(), pool, [xt0, xt1, xt2, xt3]);
let xt0_report = (
pool.api().hash_and_length(&xt0).0,
Some(TransactionValidityError::Invalid(InvalidTransaction::BadProof)),
);
let xt1_report = (pool.api().hash_and_length(&xt1).0, None);
let invalid_txs = [xt0_report, xt1_report].into();
let result = block_on(pool.report_invalid(Some(header01.hash()), invalid_txs));
assert!(result.iter().any(|tx| tx.hash == pool.api().hash_and_length(&xt0).0));
assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]);
assert_pool_status!(header02a.hash(), &pool, 2, 0);
assert_ready_iterator!(header02a.hash(), pool, [xt2, xt3]);
assert_pool_status!(header02b.hash(), &pool, 2, 0);
assert_ready_iterator!(header02b.hash(), pool, [xt2, xt3]);
let header03 = api.push_block_with_parent(header02b.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header02b.hash()), header03.hash())));
assert_pool_status!(header03.hash(), &pool, 2, 0);
assert_ready_iterator!(header03.hash(), pool, [xt2, xt3]);
assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]);
assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Invalid]);
assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]);
}
@@ -0,0 +1,832 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Tests of limits for fork-aware transaction pool.
pub mod fatp_common;
use fatp_common::{
finalized_block_event, invalid_hash, new_best_block_event, TestPoolBuilder, LOG_TARGET, SOURCE,
};
use futures::{executor::block_on, FutureExt};
use pezsc_transaction_pool::ChainApi;
use pezsc_transaction_pool_api::{
error::Error as TxPoolError, MaintainedTransactionPool, TransactionPool, TransactionStatus,
};
use std::thread::sleep;
use bizinikiwi_test_runtime_client::Sr25519Keyring::*;
use bizinikiwi_test_runtime_transaction_pool::uxt;
use tracing::debug;
#[test]
fn fatp_limits_no_views_mempool_count() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(2).build();
let header = api.push_block(1, vec![], true);
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Alice, 201);
let xt2 = uxt(Alice, 202);
let submissions = vec![
pool.submit_one(header.hash(), SOURCE, xt0.clone()),
pool.submit_one(header.hash(), SOURCE, xt1.clone()),
pool.submit_one(header.hash(), SOURCE, xt2.clone()),
];
let results = block_on(futures::future::join_all(submissions));
let mut results = results.iter();
assert!(results.next().unwrap().is_ok());
assert!(results.next().unwrap().is_ok());
assert!(matches!(
results.next().unwrap().as_ref().unwrap_err().0,
TxPoolError::ImmediatelyDropped
));
}
#[test]
fn fatp_limits_ready_count_works() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 200);
api.set_nonce(api.genesis_hash(), Charlie.into(), 500);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
//note: we need Charlie to be first as the oldest is removed.
//For 3x alice, all tree would be removed.
//(alice,bob,charlie would work too)
let xt0 = uxt(Charlie, 500);
let xt1 = uxt(Alice, 200);
let xt2 = uxt(Alice, 201);
let submissions = vec![
pool.submit_one(header01.hash(), SOURCE, xt0.clone()),
pool.submit_one(header01.hash(), SOURCE, xt1.clone()),
pool.submit_one(header01.hash(), SOURCE, xt2.clone()),
];
let results = block_on(futures::future::join_all(submissions));
assert!(results.iter().all(Result::is_ok));
//charlie was not included into view:
assert_pool_status!(header01.hash(), &pool, 2, 0);
assert_ready_iterator!(header01.hash(), pool, [xt1, xt2]);
//todo: can we do better? We don't have API to check if event was processed internally.
let mut counter = 0;
while block_on(pool.mempool_len()).0 == 3 {
sleep(std::time::Duration::from_millis(1));
counter = counter + 1;
if counter > 20 {
assert!(false, "timeout");
}
}
assert_eq!(block_on(pool.mempool_len()).0, 2);
//branch with alice transactions:
let header02b = api.push_block(2, vec![xt1.clone(), xt2.clone()], true);
let event = new_best_block_event(&pool, Some(header01.hash()), header02b.hash());
block_on(pool.maintain(event));
assert_eq!(block_on(pool.mempool_len()).0, 2);
assert_pool_status!(header02b.hash(), &pool, 0, 0);
assert_ready_iterator!(header02b.hash(), pool, []);
//branch with alice/charlie transactions shall also work:
let header02a = api.push_block(2, vec![xt0.clone(), xt1.clone()], true);
api.set_nonce(header02a.hash(), Alice.into(), 201);
let event = new_best_block_event(&pool, Some(header02b.hash()), header02a.hash());
block_on(pool.maintain(event));
assert_eq!(block_on(pool.mempool_len()).0, 2);
// assert_pool_status!(header02a.hash(), &pool, 1, 0);
assert_ready_iterator!(header02a.hash(), pool, [xt2]);
}
#[test]
fn fatp_limits_ready_count_works_for_submit_at() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 200);
api.set_nonce(api.genesis_hash(), Charlie.into(), 500);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Charlie, 500);
let xt1 = uxt(Alice, 200);
let xt2 = uxt(Alice, 201);
let results = block_on(pool.submit_at(
header01.hash(),
SOURCE,
vec![xt0.clone(), xt1.clone(), xt2.clone()],
))
.unwrap();
assert!(matches!(results[0].as_ref().unwrap_err().0, TxPoolError::ImmediatelyDropped));
assert!(results[1].as_ref().is_ok());
assert!(results[2].as_ref().is_ok());
assert_eq!(block_on(pool.mempool_len()).0, 2);
//charlie was not included into view:
assert_pool_status!(header01.hash(), &pool, 2, 0);
assert_ready_iterator!(header01.hash(), pool, [xt1, xt2]);
}
#[test]
fn fatp_limits_ready_count_works_for_submit_and_watch() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 500);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Charlie, 500);
let xt1 = uxt(Alice, 200);
let xt2 = uxt(Bob, 300);
api.set_priority(&xt0, 2);
api.set_priority(&xt1, 2);
api.set_priority(&xt2, 1);
let result0 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone()));
let result1 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone()));
let result2 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).map(|_| ());
assert!(matches!(result2.unwrap_err().0, TxPoolError::ImmediatelyDropped));
assert!(result0.is_ok());
assert!(result1.is_ok());
assert_eq!(block_on(pool.mempool_len()).1, 2);
//charlie was not included into view:
assert_pool_status!(header01.hash(), &pool, 2, 0);
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1]);
}
#[test]
fn fatp_limits_future_count_works() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(3).with_future_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 200);
api.set_nonce(api.genesis_hash(), Charlie.into(), 500);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Charlie, 501);
let xt2 = uxt(Alice, 201);
let xt3 = uxt(Alice, 202);
block_on(pool.submit_one(header01.hash(), SOURCE, xt1.clone())).unwrap();
block_on(pool.submit_one(header01.hash(), SOURCE, xt2.clone())).unwrap();
block_on(pool.submit_one(header01.hash(), SOURCE, xt3.clone())).unwrap();
//charlie was not included into view due to limits:
assert_pool_status!(header01.hash(), &pool, 0, 2);
//todo: can we do better? We don't have API to check if event was processed internally.
let mut counter = 0;
while block_on(pool.mempool_len()).0 != 2 {
sleep(std::time::Duration::from_millis(1));
counter = counter + 1;
if counter > 20 {
assert!(false, "timeout");
}
}
let header02 = api.push_block(2, vec![xt0], true);
api.set_nonce(header02.hash(), Alice.into(), 201); //redundant
let event = new_best_block_event(&pool, Some(header01.hash()), header02.hash());
block_on(pool.maintain(event));
assert_pool_status!(header02.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).0, 2);
}
#[test]
fn fatp_limits_watcher_mempool_doesnt_prevent_dropping() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Charlie, 400);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Alice, 200);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 2, 0);
let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>();
debug!(target: LOG_TARGET, ?xt0_status, "xt0_status");
assert_eq!(xt0_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]);
let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt1_status, vec![TransactionStatus::Ready]);
let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::<Vec<_>>();
debug!(target: LOG_TARGET, ?xt2_status, "xt2_status");
assert_eq!(xt2_status, vec![TransactionStatus::Ready]);
}
#[test]
fn fatp_limits_watcher_non_intial_view_drops_transaction() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Dave, 500);
let xt1 = uxt(Charlie, 400);
let xt2 = uxt(Bob, 300);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
// make sure tx0 is actually dropped before checking iterator
let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>();
assert_eq!(xt0_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_ready_iterator!(header01.hash(), pool, [xt1, xt2]);
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash())));
assert_pool_status!(header02.hash(), &pool, 2, 0);
assert_ready_iterator!(header02.hash(), pool, [xt1, xt2]);
let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt1_status, vec![TransactionStatus::Ready]);
let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt2_status, vec![TransactionStatus::Ready]);
}
#[test]
fn fatp_limits_watcher_finalized_transaction_frees_ready_space() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Dave, 500);
let xt1 = uxt(Charlie, 400);
let xt2 = uxt(Bob, 300);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
assert_ready_iterator!(header01.hash(), pool, [xt1, xt2]);
let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>();
assert_eq!(xt0_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]);
let header02 = api.push_block_with_parent(header01.hash(), vec![xt0.clone()], true);
block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash())));
assert_pool_status!(header02.hash(), &pool, 2, 0);
assert_ready_iterator!(header02.hash(), pool, [xt1, xt2]);
let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt1_status, vec![TransactionStatus::Ready]);
let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt2_status, vec![TransactionStatus::Ready]);
}
#[test]
fn fatp_limits_watcher_view_can_drop_transcation() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Dave, 500);
let xt1 = uxt(Charlie, 400);
let xt2 = uxt(Bob, 300);
let xt3 = uxt(Alice, 200);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>();
assert_eq!(xt0_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped,]);
assert_ready_iterator!(header01.hash(), pool, [xt1, xt2]);
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header02.hash())));
let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(2).collect::<Vec<_>>();
assert_eq!(xt1_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_pool_status!(header02.hash(), pool, 2, 0);
assert_ready_iterator!(header02.hash(), pool, [xt2, xt3]);
let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt2_status, vec![TransactionStatus::Ready]);
let xt3_status = futures::executor::block_on_stream(xt3_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt3_status, vec![TransactionStatus::Ready]);
}
#[test]
fn fatp_limits_watcher_empty_and_full_view_immediately_drops() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
api.set_nonce(api.genesis_hash(), Eve.into(), 600);
api.set_nonce(api.genesis_hash(), Ferdie.into(), 700);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
let xt4 = uxt(Eve, 600);
let xt5 = uxt(Ferdie, 700);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>();
assert_eq!(xt0_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_pool_status!(header01.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).1, 2);
let header02e = api.push_block_with_parent(
header01.hash(),
vec![xt0.clone(), xt1.clone(), xt2.clone()],
true,
);
api.set_nonce(header02e.hash(), Alice.into(), 201);
api.set_nonce(header02e.hash(), Bob.into(), 301);
api.set_nonce(header02e.hash(), Charlie.into(), 401);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02e.hash())));
assert_pool_status!(header02e.hash(), &pool, 0, 0);
let header02f = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02f.hash())));
assert_pool_status!(header02f.hash(), &pool, 2, 0);
assert_ready_iterator!(header02f.hash(), pool, [xt1, xt2]);
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap();
let result5 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt5.clone())).map(|_| ());
//xt5 hits internal mempool limit
assert!(matches!(result5.unwrap_err().0, TxPoolError::ImmediatelyDropped));
assert_pool_status!(header02e.hash(), &pool, 2, 0);
assert_ready_iterator!(header02e.hash(), pool, [xt3, xt4]);
assert_eq!(block_on(pool.mempool_len()).1, 4);
let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(2).collect::<Vec<_>>();
assert_eq!(
xt1_status,
vec![TransactionStatus::Ready, TransactionStatus::InBlock((header02e.hash(), 1))]
);
let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(2).collect::<Vec<_>>();
assert_eq!(
xt2_status,
vec![TransactionStatus::Ready, TransactionStatus::InBlock((header02e.hash(), 2))]
);
let xt3_status = futures::executor::block_on_stream(xt3_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt3_status, vec![TransactionStatus::Ready]);
let xt4_status = futures::executor::block_on_stream(xt4_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt4_status, vec![TransactionStatus::Ready]);
}
#[test]
fn fatp_limits_watcher_empty_and_full_view_drops_with_event() {
// it is almost copy of fatp_limits_watcher_empty_and_full_view_immediately_drops, but the
// mempool_count limit is set to 5 (vs 4).
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(5).with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
api.set_nonce(api.genesis_hash(), Eve.into(), 600);
api.set_nonce(api.genesis_hash(), Ferdie.into(), 700);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
let xt4 = uxt(Eve, 600);
let xt5 = uxt(Ferdie, 700);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>();
assert_eq!(xt0_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_pool_status!(header01.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).1, 2);
let header02e = api.push_block_with_parent(
header01.hash(),
vec![xt0.clone(), xt1.clone(), xt2.clone()],
true,
);
api.set_nonce(header02e.hash(), Alice.into(), 201);
api.set_nonce(header02e.hash(), Bob.into(), 301);
api.set_nonce(header02e.hash(), Charlie.into(), 401);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02e.hash())));
assert_pool_status!(header02e.hash(), &pool, 0, 0);
let header02f = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02f.hash())));
assert_pool_status!(header02f.hash(), &pool, 2, 0);
assert_ready_iterator!(header02f.hash(), pool, [xt1, xt2]);
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap();
let xt5_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt5.clone())).unwrap();
assert_pool_status!(header02e.hash(), &pool, 2, 0);
assert_ready_iterator!(header02e.hash(), pool, [xt4, xt5]);
let xt3_status = futures::executor::block_on_stream(xt3_watcher).take(2).collect::<Vec<_>>();
assert_eq!(xt3_status, vec![TransactionStatus::Ready, TransactionStatus::Dropped]);
//xt5 got dropped
assert_eq!(block_on(pool.mempool_len()).1, 4);
let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(2).collect::<Vec<_>>();
assert_eq!(
xt1_status,
vec![TransactionStatus::Ready, TransactionStatus::InBlock((header02e.hash(), 1))]
);
let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(2).collect::<Vec<_>>();
assert_eq!(
xt2_status,
vec![TransactionStatus::Ready, TransactionStatus::InBlock((header02e.hash(), 2))]
);
let xt4_status = futures::executor::block_on_stream(xt4_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt4_status, vec![TransactionStatus::Ready]);
let xt5_status = futures::executor::block_on_stream(xt5_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt5_status, vec![TransactionStatus::Ready]);
}
fn large_uxt(x: usize) -> bizinikiwi_test_runtime::Extrinsic {
bizinikiwi_test_runtime::ExtrinsicBuilder::new_include_data(vec![x as u8; 1024]).build()
}
#[test]
fn fatp_limits_ready_size_works() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_ready_bytes_size(3390).with_future_bytes_size(0).build();
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = large_uxt(0);
let xt1 = large_uxt(1);
let xt2 = large_uxt(2);
let submissions = vec![
pool.submit_one(header01.hash(), SOURCE, xt0.clone()),
pool.submit_one(header01.hash(), SOURCE, xt1.clone()),
pool.submit_one(header01.hash(), SOURCE, xt2.clone()),
];
let results = block_on(futures::future::join_all(submissions));
assert!(results.iter().all(Result::is_ok));
//charlie was not included into view:
assert_pool_status!(header01.hash(), &pool, 3, 0);
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1, xt2]);
let xt3 = large_uxt(3);
let result3 = block_on(pool.submit_one(header01.hash(), SOURCE, xt3.clone()));
assert!(matches!(result3.as_ref().unwrap_err().0, TxPoolError::ImmediatelyDropped));
}
#[test]
fn fatp_limits_future_size_works() {
pezsp_tracing::try_init_simple();
const UXT_SIZE: usize = 137;
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder
.with_ready_bytes_size(UXT_SIZE)
.with_future_bytes_size(3 * UXT_SIZE)
.build();
api.set_nonce(api.genesis_hash(), Bob.into(), 200);
api.set_nonce(api.genesis_hash(), Charlie.into(), 500);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Bob, 201);
let xt1 = uxt(Charlie, 501);
let xt2 = uxt(Alice, 201);
let xt3 = uxt(Alice, 202);
assert_eq!(api.hash_and_length(&xt0).1, UXT_SIZE);
assert_eq!(api.hash_and_length(&xt1).1, UXT_SIZE);
assert_eq!(api.hash_and_length(&xt2).1, UXT_SIZE);
assert_eq!(api.hash_and_length(&xt3).1, UXT_SIZE);
let _ = block_on(pool.submit_one(header01.hash(), SOURCE, xt0.clone())).unwrap();
let _ = block_on(pool.submit_one(header01.hash(), SOURCE, xt1.clone())).unwrap();
let _ = block_on(pool.submit_one(header01.hash(), SOURCE, xt2.clone())).unwrap();
let _ = block_on(pool.submit_one(header01.hash(), SOURCE, xt3.clone())).unwrap();
//todo: can we do better? We don't have API to check if event was processed internally.
let mut counter = 0;
while block_on(pool.mempool_len()).0 == 4 {
sleep(std::time::Duration::from_millis(1));
counter = counter + 1;
if counter > 20 {
assert!(false, "timeout");
}
}
assert_pool_status!(header01.hash(), &pool, 0, 3);
assert_eq!(block_on(pool.mempool_len()).0, 3);
}
#[test]
fn fatp_limits_watcher_ready_transactions_are_not_droped_when_view_is_dropped() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(6).with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
api.set_nonce(api.genesis_hash(), Eve.into(), 600);
api.set_nonce(api.genesis_hash(), Ferdie.into(), 700);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
let xt4 = uxt(Eve, 600);
let xt5 = uxt(Ferdie, 700);
let _xt0_watcher =
block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let _xt1_watcher =
block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).1, 2);
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash())));
let _xt2_watcher =
block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let _xt3_watcher =
block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_pool_status!(header02.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).1, 4);
let header03 = api.push_block_with_parent(header02.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash())));
let _xt4_watcher =
block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap();
let _xt5_watcher =
block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt5.clone())).unwrap();
assert_pool_status!(header03.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).1, 6);
let header04 =
api.push_block_with_parent(header03.hash(), vec![xt4.clone(), xt5.clone()], true);
api.set_nonce(header04.hash(), Alice.into(), 201);
api.set_nonce(header04.hash(), Bob.into(), 301);
api.set_nonce(header04.hash(), Charlie.into(), 401);
api.set_nonce(header04.hash(), Dave.into(), 501);
api.set_nonce(header04.hash(), Eve.into(), 601);
api.set_nonce(header04.hash(), Ferdie.into(), 701);
block_on(pool.maintain(new_best_block_event(&pool, Some(header03.hash()), header04.hash())));
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1]);
assert_ready_iterator!(header02.hash(), pool, [xt2, xt3]);
assert_ready_iterator!(header03.hash(), pool, [xt4, xt5]);
assert_ready_iterator!(header04.hash(), pool, []);
block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header01.hash())));
assert!(!pool.status_all().contains_key(&header01.hash()));
block_on(pool.maintain(finalized_block_event(&pool, header01.hash(), header02.hash())));
assert!(!pool.status_all().contains_key(&header02.hash()));
//view 01 was dropped
assert!(pool.ready_at(header01.hash()).now_or_never().is_none());
assert_eq!(block_on(pool.mempool_len()).1, 6);
block_on(pool.maintain(finalized_block_event(&pool, header02.hash(), header03.hash())));
//no revalidation has happened yet, all txs are kept
assert_eq!(block_on(pool.mempool_len()).1, 6);
//view 03 is still there
assert!(!pool.status_all().contains_key(&header03.hash()));
//view 02 was dropped
assert!(pool.ready_at(header02.hash()).now_or_never().is_none());
let mut prev_header = header03;
for n in 5..=11 {
let header = api.push_block(n, vec![], true);
let event = finalized_block_event(&pool, prev_header.hash(), header.hash());
block_on(pool.maintain(event));
prev_header = header;
}
//now revalidation has happened, all txs are dropped
assert_eq!(block_on(pool.mempool_len()).1, 0);
}
#[test]
fn fatp_limits_watcher_future_transactions_are_droped_when_view_is_dropped() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(6).with_future_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
api.set_nonce(api.genesis_hash(), Eve.into(), 600);
api.set_nonce(api.genesis_hash(), Ferdie.into(), 700);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 201);
let xt1 = uxt(Bob, 301);
let xt2 = uxt(Charlie, 401);
let xt3 = uxt(Dave, 501);
let xt4 = uxt(Eve, 601);
let xt5 = uxt(Ferdie, 701);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 0, 2);
assert_eq!(block_on(pool.mempool_len()).1, 2);
assert_future_iterator!(header01.hash(), pool, [xt0, xt1]);
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash())));
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_pool_status!(header02.hash(), &pool, 0, 2);
assert_eq!(block_on(pool.mempool_len()).1, 4);
assert_future_iterator!(header02.hash(), pool, [xt2, xt3]);
let header03 = api.push_block_with_parent(header02.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash())));
let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap();
let xt5_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt5.clone())).unwrap();
assert_pool_status!(header03.hash(), &pool, 0, 2);
assert_eq!(block_on(pool.mempool_len()).1, 6);
assert_future_iterator!(header03.hash(), pool, [xt4, xt5]);
let header04 = api.push_block_with_parent(header03.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header03.hash()), header04.hash())));
assert_pool_status!(header04.hash(), &pool, 0, 2);
assert_eq!(pool.futures().len(), 2);
assert_future_iterator!(header04.hash(), pool, [xt4, xt5]);
block_on(pool.maintain(finalized_block_event(&pool, api.genesis_hash(), header04.hash())));
assert_eq!(pool.active_views_count(), 1);
assert_eq!(pool.inactive_views_count(), 0);
//todo: can we do better? We don't have API to check if event was processed internally.
let mut counter = 0;
while block_on(pool.mempool_len()).1 != 2 {
sleep(std::time::Duration::from_millis(1));
counter = counter + 1;
if counter > 20 {
assert!(false, "timeout {}", block_on(pool.mempool_len()).1);
}
}
assert_eq!(block_on(pool.mempool_len()).1, 2);
assert_pool_status!(header04.hash(), &pool, 0, 2);
assert_eq!(pool.futures().len(), 2);
let to_be_checked = vec![xt0_watcher, xt1_watcher, xt2_watcher, xt3_watcher];
for x in to_be_checked {
let x_status = futures::executor::block_on_stream(x).take(2).collect::<Vec<_>>();
assert_eq!(x_status, vec![TransactionStatus::Future, TransactionStatus::Dropped]);
}
let to_be_checked = vec![xt4_watcher, xt5_watcher];
for x in to_be_checked {
let x_status = futures::executor::block_on_stream(x).take(1).collect::<Vec<_>>();
assert_eq!(x_status, vec![TransactionStatus::Future]);
}
}
@@ -0,0 +1,561 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Tests of priorities for fork-aware transaction pool.
pub mod fatp_common;
use fatp_common::{invalid_hash, new_best_block_event, TestPoolBuilder, LOG_TARGET, SOURCE};
use futures::{executor::block_on, FutureExt};
use pezsc_transaction_pool::ChainApi;
use pezsc_transaction_pool_api::{
error::Error as TxPoolError, LocalTransactionPool, MaintainedTransactionPool, TransactionPool,
TransactionStatus,
};
use bizinikiwi_test_runtime_client::Sr25519Keyring::*;
use bizinikiwi_test_runtime_transaction_pool::uxt;
use tracing::info;
#[test]
fn fatp_prio_ready_higher_evicts_lower() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build();
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Alice, 200);
api.set_priority(&xt0, 2);
api.set_priority(&xt1, 3);
let result0 = block_on(pool.submit_one(header01.hash(), SOURCE, xt0.clone()));
let result1 = block_on(pool.submit_one(header01.hash(), SOURCE, xt1.clone()));
info!(target: LOG_TARGET, ?result0, "r0");
info!(target: LOG_TARGET, ?result1, "r1");
info!(target: LOG_TARGET, len = ?block_on(pool.mempool_len()), "len");
info!(target: LOG_TARGET, status = ?pool.status_all()[&header01.hash()], "len");
assert_ready_iterator!(header01.hash(), pool, [xt1]);
assert_pool_status!(header01.hash(), &pool, 1, 0);
}
#[test]
fn fatp_prio_watcher_ready_higher_evicts_lower() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build();
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Alice, 200);
api.set_priority(&xt0, 2);
api.set_priority(&xt1, 3);
let xt0_watcher =
block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher =
block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt1.clone())).unwrap();
let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>();
assert_eq!(
xt0_status,
vec![TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt1).0)]
);
let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt1_status, vec![TransactionStatus::Ready]);
info!(target: LOG_TARGET, len = ?block_on(pool.mempool_len()), "len");
info!(target: LOG_TARGET, pool_status = ?pool.status_all()[&header01.hash()], "len");
assert_ready_iterator!(header01.hash(), pool, [xt1]);
assert_pool_status!(header01.hash(), &pool, 1, 0);
}
#[test]
fn fatp_prio_watcher_future_higher_evicts_lower() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(3).build();
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 201);
let xt1 = uxt(Alice, 201);
let xt2 = uxt(Alice, 200);
api.set_priority(&xt0, 2);
api.set_priority(&xt1, 3);
let xt0_watcher =
block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher =
block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher =
block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt2.clone())).unwrap();
let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>();
assert_eq!(
xt0_status,
vec![TransactionStatus::Future, TransactionStatus::Usurped(api.hash_and_length(&xt2).0)]
);
let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(2).collect::<Vec<_>>();
assert_eq!(xt1_status, vec![TransactionStatus::Future, TransactionStatus::Ready]);
let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt2_status, vec![TransactionStatus::Ready]);
assert_eq!(block_on(pool.mempool_len()).1, 2);
assert_ready_iterator!(header01.hash(), pool, [xt2, xt1]);
assert_pool_status!(header01.hash(), &pool, 2, 0);
}
#[test]
fn fatp_prio_watcher_ready_lower_prio_gets_dropped_from_all_views() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build();
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Alice, 200);
api.set_priority(&xt0, 2);
api.set_priority(&xt1, 3);
let xt0_watcher =
block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt0.clone())).unwrap();
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash())));
let header03a = api.push_block_with_parent(header02.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header03a.hash())));
let header03b = api.push_block_with_parent(header02.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header03a.hash()), header03b.hash())));
assert_pool_status!(header03a.hash(), &pool, 1, 0);
assert_ready_iterator!(header03a.hash(), pool, [xt0]);
assert_pool_status!(header03b.hash(), &pool, 1, 0);
assert_ready_iterator!(header03b.hash(), pool, [xt0]);
assert_ready_iterator!(header01.hash(), pool, [xt0]);
assert_ready_iterator!(header02.hash(), pool, [xt0]);
let xt1_watcher =
block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt1.clone())).unwrap();
let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt1_status, vec![TransactionStatus::Ready]);
let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>();
assert_eq!(
xt0_status,
vec![TransactionStatus::Ready, TransactionStatus::Usurped(api.hash_and_length(&xt1).0)]
);
assert_ready_iterator!(header03a.hash(), pool, [xt1]);
assert_ready_iterator!(header03b.hash(), pool, [xt1]);
assert_ready_iterator!(header01.hash(), pool, [xt1]);
assert_ready_iterator!(header02.hash(), pool, [xt1]);
}
#[test]
fn fatp_prio_watcher_future_lower_prio_gets_dropped_from_all_views() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(3).with_ready_count(2).build();
let header01 = api.push_block(1, vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, None, header01.hash())));
let xt0 = uxt(Alice, 201);
let xt1 = uxt(Alice, 201);
let xt2 = uxt(Alice, 200);
api.set_priority(&xt0, 2);
api.set_priority(&xt1, 3);
let xt0_watcher =
block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher =
block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt1.clone())).unwrap();
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash())));
let header03a = api.push_block_with_parent(header02.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header03a.hash())));
let header03b = api.push_block_with_parent(header02.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header03a.hash()), header03b.hash())));
assert_pool_status!(header03a.hash(), &pool, 0, 2);
assert_future_iterator!(header03a.hash(), pool, [xt0, xt1]);
assert_pool_status!(header03b.hash(), &pool, 0, 2);
assert_future_iterator!(header03b.hash(), pool, [xt0, xt1]);
assert_future_iterator!(header01.hash(), pool, [xt0, xt1]);
assert_future_iterator!(header02.hash(), pool, [xt0, xt1]);
let xt2_watcher =
block_on(pool.submit_and_watch(header01.hash(), SOURCE, xt2.clone())).unwrap();
let xt2_status = futures::executor::block_on_stream(xt2_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt2_status, vec![TransactionStatus::Ready]);
let xt1_status = futures::executor::block_on_stream(xt1_watcher).take(1).collect::<Vec<_>>();
assert_eq!(xt1_status, vec![TransactionStatus::Future]);
let xt0_status = futures::executor::block_on_stream(xt0_watcher).take(2).collect::<Vec<_>>();
assert_eq!(
xt0_status,
vec![TransactionStatus::Future, TransactionStatus::Usurped(api.hash_and_length(&xt2).0)]
);
assert_future_iterator!(header03a.hash(), pool, []);
assert_future_iterator!(header03b.hash(), pool, []);
assert_future_iterator!(header01.hash(), pool, []);
assert_future_iterator!(header02.hash(), pool, []);
assert_ready_iterator!(header03a.hash(), pool, [xt2, xt1]);
assert_ready_iterator!(header03b.hash(), pool, [xt2, xt1]);
assert_ready_iterator!(header01.hash(), pool, [xt2, xt1]);
assert_ready_iterator!(header02.hash(), pool, [xt2, xt1]);
}
#[test]
fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
api.set_nonce(api.genesis_hash(), Eve.into(), 600);
api.set_nonce(api.genesis_hash(), Ferdie.into(), 700);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
let xt4 = uxt(Eve, 600);
let xt5 = uxt(Ferdie, 700);
api.set_priority(&xt0, 1);
api.set_priority(&xt1, 2);
api.set_priority(&xt2, 3);
api.set_priority(&xt3, 4);
api.set_priority(&xt4, 5);
api.set_priority(&xt5, 6);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).1, 2);
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash())));
let _xt2_watcher =
block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let _xt3_watcher =
block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_pool_status!(header02.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).1, 4);
let header03 = api.push_block_with_parent(header02.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash())));
let _xt4_watcher =
block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap();
let _xt5_watcher =
block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt5.clone())).unwrap();
assert_pool_status!(header03.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).1, 4);
assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_ready_iterator!(header01.hash(), pool, []);
assert_ready_iterator!(header02.hash(), pool, [xt3, xt2]);
assert_ready_iterator!(header03.hash(), pool, [xt5, xt4]);
}
#[test]
fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted_with_subtree() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(4).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Alice, 201);
let xt2 = uxt(Alice, 202);
let xt3 = uxt(Bob, 300);
let xt4 = uxt(Charlie, 400);
api.set_priority(&xt0, 1);
api.set_priority(&xt1, 3);
api.set_priority(&xt2, 3);
api.set_priority(&xt3, 2);
api.set_priority(&xt4, 2);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_ready_iterator!(header01.hash(), pool, [xt3, xt0, xt1, xt2]);
assert_pool_status!(header01.hash(), &pool, 4, 0);
assert_eq!(block_on(pool.mempool_len()).1, 4);
let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 2, 0);
assert_ready_iterator!(header01.hash(), pool, [xt3, xt4]);
assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready]);
}
#[test]
fn fatp_prios_watcher_full_mempool_higher_prio_is_accepted_with_subtree2() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(4).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Alice, 201);
let xt2 = uxt(Alice, 202);
let xt3 = uxt(Bob, 300);
let xt4 = uxt(Charlie, 400);
api.set_priority(&xt0, 1);
api.set_priority(&xt1, 3);
api.set_priority(&xt2, 3);
api.set_priority(&xt3, 2);
api.set_priority(&xt4, 2);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_ready_iterator!(header01.hash(), pool, [xt3, xt0, xt1, xt2]);
assert_pool_status!(header01.hash(), &pool, 4, 0);
assert_eq!(block_on(pool.mempool_len()).1, 4);
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash())));
let xt4_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt4.clone())).unwrap();
assert_ready_iterator!(header01.hash(), pool, [xt3]);
assert_pool_status!(header02.hash(), &pool, 2, 0);
assert_ready_iterator!(header02.hash(), pool, [xt3, xt4]);
assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt4_watcher, [TransactionStatus::Ready]);
}
#[test]
fn fatp_prios_watcher_full_mempool_lower_prio_gets_rejected() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(2).with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
api.set_priority(&xt0, 2);
api.set_priority(&xt1, 2);
api.set_priority(&xt2, 2);
api.set_priority(&xt3, 1);
let _xt0_watcher =
block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let _xt1_watcher =
block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).1, 2);
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash())));
assert_pool_status!(header02.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).1, 2);
assert_ready_iterator!(header01.hash(), pool, [xt0, xt1]);
assert_ready_iterator!(header02.hash(), pool, [xt0, xt1]);
let result2 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).map(|_| ());
assert!(matches!(result2.as_ref().unwrap_err().0, TxPoolError::ImmediatelyDropped));
let result3 = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).map(|_| ());
assert!(matches!(result3.as_ref().unwrap_err().0, TxPoolError::ImmediatelyDropped));
}
#[test]
fn fatp_prios_watcher_full_mempool_does_not_keep_dropped_transaction() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
api.set_priority(&xt0, 2);
api.set_priority(&xt1, 2);
api.set_priority(&xt2, 2);
api.set_priority(&xt3, 2);
let xt0_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt0.clone())).unwrap();
let xt1_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt1.clone())).unwrap();
let xt2_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt2.clone())).unwrap();
let xt3_watcher = block_on(pool.submit_and_watch(invalid_hash(), SOURCE, xt3.clone())).unwrap();
assert_pool_status!(header01.hash(), &pool, 2, 0);
assert_ready_iterator!(header01.hash(), pool, [xt2, xt3]);
assert_watcher_stream!(xt0_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_watcher_stream!(xt1_watcher, [TransactionStatus::Ready, TransactionStatus::Dropped]);
assert_watcher_stream!(xt2_watcher, [TransactionStatus::Ready]);
assert_watcher_stream!(xt3_watcher, [TransactionStatus::Ready]);
}
#[test]
fn fatp_prios_submit_local_full_mempool_higher_prio_is_accepted() {
pezsp_tracing::try_init_simple();
let builder = TestPoolBuilder::new();
let (pool, api, _) = builder.with_mempool_count_limit(4).with_ready_count(2).build();
api.set_nonce(api.genesis_hash(), Bob.into(), 300);
api.set_nonce(api.genesis_hash(), Charlie.into(), 400);
api.set_nonce(api.genesis_hash(), Dave.into(), 500);
api.set_nonce(api.genesis_hash(), Eve.into(), 600);
api.set_nonce(api.genesis_hash(), Ferdie.into(), 700);
let header01 = api.push_block(1, vec![], true);
let event = new_best_block_event(&pool, None, header01.hash());
block_on(pool.maintain(event));
let xt0 = uxt(Alice, 200);
let xt1 = uxt(Bob, 300);
let xt2 = uxt(Charlie, 400);
let xt3 = uxt(Dave, 500);
let xt4 = uxt(Eve, 600);
let xt5 = uxt(Ferdie, 700);
api.set_priority(&xt0, 1);
api.set_priority(&xt1, 2);
api.set_priority(&xt2, 3);
api.set_priority(&xt3, 4);
api.set_priority(&xt4, 5);
api.set_priority(&xt5, 6);
pool.submit_local(invalid_hash(), xt0.clone()).unwrap();
pool.submit_local(invalid_hash(), xt1.clone()).unwrap();
assert_pool_status!(header01.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).0, 2);
let header02 = api.push_block_with_parent(header01.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header01.hash()), header02.hash())));
pool.submit_local(invalid_hash(), xt2.clone()).unwrap();
pool.submit_local(invalid_hash(), xt3.clone()).unwrap();
assert_pool_status!(header02.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).0, 4);
let header03 = api.push_block_with_parent(header02.hash(), vec![], true);
block_on(pool.maintain(new_best_block_event(&pool, Some(header02.hash()), header03.hash())));
pool.submit_local(invalid_hash(), xt4.clone()).unwrap();
pool.submit_local(invalid_hash(), xt5.clone()).unwrap();
assert_pool_status!(header03.hash(), &pool, 2, 0);
assert_eq!(block_on(pool.mempool_len()).0, 4);
assert_ready_iterator!(header01.hash(), pool, []);
assert_ready_iterator!(header02.hash(), pool, [xt3, xt2]);
assert_ready_iterator!(header03.hash(), pool, [xt5, xt4]);
}
@@ -0,0 +1,540 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Testsuite of transaction pool integration tests.
pub mod zombienet;
use std::time::Duration;
use crate::zombienet::{
default_zn_scenario_builder, relaychain_pezkuwichain_local_network_spec as relay,
relaychain_pezkuwichain_local_network_spec::teyrchain_asset_hub_network_spec as para,
BlockSubscriptionType, NetworkSpawner,
};
use futures::future::join_all;
use tracing::info;
use txtesttool::{execution_log::ExecutionLog, scenario::ScenarioExecutor};
use zombienet::DEFAULT_SEND_FUTURE_AND_READY_TXS_TESTS_TIMEOUT_IN_SECS;
// Test which sends future and ready txs from many accounts
// to an unlimited pool of a teyrchain collator based on the asset-hub-pezkuwichain runtime.
#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn send_future_and_ready_from_many_accounts_to_teyrchain() {
let net = NetworkSpawner::from_toml_with_env_logger(para::HIGH_POOL_LIMIT_FATP)
.await
.unwrap();
// Wait for the teyrchain collator to start block production.
net.wait_for_block("charlie", BlockSubscriptionType::Best).await.unwrap();
// Create future & ready txs executors.
let ws = net.node_rpc_uri("charlie").unwrap();
let future_scenario_executor = default_zn_scenario_builder(&net)
.with_rpc_uri(ws.clone())
.with_start_id(0)
.with_last_id(99)
.with_nonce_from(Some(100))
.with_txs_count(100)
.with_executor_id("future-txs-executor".to_string())
.with_timeout_in_secs(DEFAULT_SEND_FUTURE_AND_READY_TXS_TESTS_TIMEOUT_IN_SECS)
.build()
.await;
let ready_scenario_executor = default_zn_scenario_builder(&net)
.with_rpc_uri(ws)
.with_start_id(0)
.with_last_id(99)
.with_nonce_from(Some(0))
.with_txs_count(100)
.with_executor_id("ready-txs-executor".to_string())
.with_timeout_in_secs(DEFAULT_SEND_FUTURE_AND_READY_TXS_TESTS_TIMEOUT_IN_SECS)
.build()
.await;
// Execute transactions and fetch the execution logs.
let (future_logs, ready_logs) = futures::future::join(
future_scenario_executor.execute(),
ready_scenario_executor.execute(),
)
.await;
let finalized_future =
future_logs.values().filter_map(|default_log| default_log.finalized()).count();
let finalized_ready =
ready_logs.values().filter_map(|default_log| default_log.finalized()).count();
assert_eq!(finalized_future, 10_000);
assert_eq!(finalized_ready, 10_000);
}
// Test which sends future and ready txs from many accounts
// to an unlimited pool of a relaychain node based on `pezkuwichain-local` runtime.
#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn send_future_and_ready_from_many_accounts_to_relaychain() {
let net = NetworkSpawner::from_toml_with_env_logger(relay::HIGH_POOL_LIMIT_FATP)
.await
.unwrap();
// Wait for the paracha validator to start block production & have its genesis block
// finalized.
net.wait_for_block("alice", BlockSubscriptionType::Best).await.unwrap();
// Create future & ready txs executors.
let ws = net.node_rpc_uri("alice").unwrap();
let future_scenario_executor = default_zn_scenario_builder(&net)
.with_rpc_uri(ws.clone())
.with_start_id(0)
.with_last_id(99)
.with_nonce_from(Some(100))
.with_txs_count(100)
.with_executor_id("future-txs-executor".to_string())
.with_timeout_in_secs(DEFAULT_SEND_FUTURE_AND_READY_TXS_TESTS_TIMEOUT_IN_SECS)
.build()
.await;
let ready_scenario_executor = default_zn_scenario_builder(&net)
.with_rpc_uri(ws)
.with_start_id(0)
.with_last_id(99)
.with_nonce_from(Some(0))
.with_txs_count(100)
.with_executor_id("ready-txs-executor".to_string())
.with_timeout_in_secs(DEFAULT_SEND_FUTURE_AND_READY_TXS_TESTS_TIMEOUT_IN_SECS)
.build()
.await;
// Execute transactions and fetch the execution logs.
// Execute transactions and fetch the execution logs.
let (future_logs, ready_logs) = futures::future::join(
future_scenario_executor.execute(),
ready_scenario_executor.execute(),
)
.await;
let finalized_future =
future_logs.values().filter_map(|default_log| default_log.finalized()).count();
let finalized_ready =
ready_logs.values().filter_map(|default_log| default_log.finalized()).count();
assert_eq!(finalized_future, 10_000);
assert_eq!(finalized_ready, 10_000);
}
// Send immortal and mortal txs. Some of the mortal txs are configured to get dropped
// while others to succeed. Mortal txs are future so not being able to become ready in time and
// included in blocks result in their dropping.
//
// Block length for pezkuwichain for user txs is 75% of maximum 5MB (per pezframe-system setup),
// so we get 3750KB. In the test scenario we aim for 5 txs per block roughly (not precesily)
// so to fill a block each user tx must have around 750kb.
#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn send_future_mortal_txs() {
let net = NetworkSpawner::from_toml_with_env_logger(relay::HIGH_POOL_LIMIT_FATP)
.await
.unwrap();
// Wait for the teyrchain collator to start block production.
net.wait_for_block("alice", BlockSubscriptionType::Finalized).await.unwrap();
// Create txs executors.
let ws = net.node_rpc_uri("alice").unwrap();
let ready_scenario_executor = default_zn_scenario_builder(&net)
.with_rpc_uri(ws.clone())
.with_start_id(0)
.with_nonce_from(Some(0))
.with_txs_count(50)
// Block length for pezkuwichain for user txs is 75% of maximum 5MB (per pezframe-system
// setup), so we get 3750KB. In the test scenario we aim for 5 txs per block roughly (not
// precesily) so to fill a block each user tx must have around 750kb. We aim for 5 txs per
// block because we send 50 ready txs which we want to distribute over 10 blocks, so
// mortal txs with lifetime lower than 10 should be declared invalid after the ready txs
// finalize, while mortal txs with bigger lifetime should be finalized.
.with_remark_recipe(750)
.with_executor_id("ready-txs-executor".to_string())
.build()
.await;
let mortal_scenario_invalid = default_zn_scenario_builder(&net)
.with_rpc_uri(ws.clone())
.with_start_id(0)
.with_nonce_from(Some(60))
.with_txs_count(10)
.with_executor_id("mortal-tx-executor-invalid".to_string())
.with_mortality(5)
.build()
.await;
let mortal_scenario_success = default_zn_scenario_builder(&net)
.with_rpc_uri(ws)
.with_start_id(0)
.with_nonce_from(Some(50))
.with_txs_count(10)
.with_executor_id("mortal-tx-executor-success".to_string())
.with_mortality(25)
.build()
.await;
// Execute transactions and fetch the execution logs.
let (mortal_invalid_logs, ready_logs, mortal_succes_logs) = tokio::join!(
mortal_scenario_invalid.execute(),
ready_scenario_executor.execute(),
mortal_scenario_success.execute(),
);
let mortal_invalid = mortal_invalid_logs
.values()
.filter(|default_log| default_log.get_invalid_reason().len() > 0)
.count();
let mortal_succesfull = mortal_succes_logs
.values()
.filter_map(|default_log| default_log.finalized())
.count();
let finalized_ready =
ready_logs.values().filter_map(|default_log| default_log.finalized()).count();
assert_eq!(mortal_invalid, 10);
assert_eq!(mortal_succesfull, 10);
assert_eq!(finalized_ready, 50);
}
// Send immortal and mortal txs. Some mortal txs have lower priority so they shouldn't get into
// blocks during their lifetime, and will be considered invalid, while other mortal txs have
// sufficient lifetime to be included in blocks, and are finalized successfully.
#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn send_lower_priority_mortal_txs() {
let net = NetworkSpawner::from_toml_with_env_logger(relay::HIGH_POOL_LIMIT_FATP)
.await
.unwrap();
// Wait for the teyrchain collator to start block production.
net.wait_for_block("alice", BlockSubscriptionType::Finalized).await.unwrap();
// Create txs executors.
let ws = net.node_rpc_uri("alice").unwrap();
let ready_scenario_executor = default_zn_scenario_builder(&net)
.with_rpc_uri(ws.clone())
.with_start_id(0)
.with_nonce_from(Some(0))
.with_txs_count(50)
.with_executor_id("ready-txs-executor".to_string())
// Block length for pezkuwichain for user txs is 75% of maximum 5MB (per pezframe-system
// setup), so we get 3750KB. In the test scenario we aim for 5 txs per block roughly (not
// precesily) so to fill a block each user tx must have around 750kb. We aim for 5 txs per
// block because we send 50 ready txs which we want to distribute over 10 blocks, so
// mortal txs with lifetime lower than 10 should be declared invalid after the ready txs
// finalize, while mortal txs with bigger lifetime should be finalized.
.with_remark_recipe(750)
.with_tip(150)
.build()
.await;
let mortal_scenario_invalid = default_zn_scenario_builder(&net)
.with_rpc_uri(ws.clone())
.with_start_id(1)
.with_nonce_from(Some(0))
.with_txs_count(10)
.with_executor_id("mortal-tx-executor-invalid".to_string())
.with_mortality(5)
// Make it very hard for these mortal txs to be included in blocks, by making them big
// enough to not let other txs be part of the same block as them, but also make sure they
// have the lowest priority so that they are not included in a single tx block over other
// txs. At some point they'll be starved and their lifetime will pass.
.with_remark_recipe(3500)
.with_tip(50)
.build()
.await;
let mortal_scenario_success = default_zn_scenario_builder(&net)
.with_rpc_uri(ws)
.with_start_id(2)
.with_nonce_from(Some(0))
.with_txs_count(10)
.with_executor_id("mortal-tx-executor-success".to_string())
.with_mortality(20)
// Same reasoning as for the ready immortal txs, we want these txs to be similarly big as
// the immortal txs, so at all times if it comes to pick a ready txs to include it in a
// block, an immortal tx should be picked instead (leaving these mortal txs to be picked
// only after, which is fine for these mortal txs).
.with_remark_recipe(750)
.with_tip(100)
.build()
.await;
// Execute transactions and fetch the execution logs.
let (mortal_invalid_logs, ready_logs, mortal_success_logs) = tokio::join!(
mortal_scenario_invalid.execute(),
ready_scenario_executor.execute(),
mortal_scenario_success.execute(),
);
let mortal_invalid = mortal_invalid_logs
.values()
.filter(|default_log| default_log.get_invalid_reason().len() > 0)
.count();
let mortal_succesfull = mortal_success_logs
.values()
.filter_map(|default_log| default_log.finalized())
.count();
let finalized_ready =
ready_logs.values().filter_map(|default_log| default_log.finalized()).count();
assert_eq!(mortal_invalid, 10);
assert_eq!(mortal_succesfull, 10);
assert_eq!(finalized_ready, 50);
}
// Test which sends 5m transactions to teyrchain. Long execution time expected.
#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn send_5m_from_many_accounts_to_teyrchain() {
let net = NetworkSpawner::from_toml_with_env_logger(para::HIGH_POOL_LIMIT_FATP)
.await
.unwrap();
// Wait for the teyrchain collator to start block production.
net.wait_for_block("charlie", BlockSubscriptionType::Best).await.unwrap();
// Create txs executor.
let ws = net.node_rpc_uri("charlie").unwrap();
let executor = default_zn_scenario_builder(&net)
.with_rpc_uri(ws)
.with_start_id(0)
.with_last_id(999)
.with_txs_count(5_000)
.with_executor_id("txs-executor".to_string())
.with_send_threshold(7500)
.build()
.await;
// Execute transactions and fetch the execution logs.
let execution_logs = executor.execute().await;
let finalized_txs = execution_logs.values().filter_map(|tx_log| tx_log.finalized()).count();
assert_eq!(finalized_txs, 5_000_000);
}
// Test which sends 5m transactions to relaychain. Long execution time expected.
#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn send_5m_from_many_accounts_to_relaychain() {
let net = NetworkSpawner::from_toml_with_env_logger(relay::HIGH_POOL_LIMIT_FATP)
.await
.unwrap();
// Wait for the teyrchain collator to start block production.
net.wait_for_block("alice", BlockSubscriptionType::Best).await.unwrap();
// Create txs executor.
let ws = net.node_rpc_uri("alice").unwrap();
let executor = default_zn_scenario_builder(&net)
.with_rpc_uri(ws.clone())
.with_start_id(0)
.with_last_id(999)
.with_txs_count(5000)
.with_executor_id("txs-executor".to_string())
.with_send_threshold(7500)
.build()
.await;
// Execute transactions and fetch the execution logs.
let execution_logs = executor.execute().await;
let finalized_txs = execution_logs.values().filter_map(|tx_log| tx_log.finalized()).count();
assert_eq!(finalized_txs, 5_000_000);
}
/// Internal test that allows to observe how transcactions are gossiped in the network. Requires
/// external tool to track transactions presence at nodes. Was used to evaluate some metrics of
/// existing transaction protocol.
#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn gossiping() {
let net = NetworkSpawner::from_toml_with_env_logger(relay::HIGH_POOL_LIMIT_FATP_TRACE)
.await
.unwrap();
// Wait for the teyrchain collator to start block production.
net.wait_for_block("a00", BlockSubscriptionType::Best).await.unwrap();
// Create the txs executor.
let ws = net.node_rpc_uri("a00").unwrap();
let executor = default_zn_scenario_builder(&net)
.with_rpc_uri(ws)
.with_start_id(0)
.with_last_id(999)
.with_executor_id("txs-executor".to_string())
.build()
.await;
// Execute transactions and fetch the execution logs.
let execution_logs = executor.execute().await;
let finalized_txs = execution_logs.values().filter_map(|tx_log| tx_log.finalized()).count();
assert_eq!(finalized_txs, 1000);
tracing::info!("BASEDIR: {:?}", net.base_dir_path());
}
/// Creates new transaction scenario executor and sends given batch of ready transactions to the
/// specified node. Single transaction is sent from single account.
async fn send_batch(
net: &NetworkSpawner,
node_name: &str,
from: u32,
to: u32,
prio: u32,
) -> ScenarioExecutor {
let ws = net.node_rpc_uri(node_name).unwrap();
info!(from, to, prio, "send_batch");
default_zn_scenario_builder(net)
.with_rpc_uri(ws)
.with_start_id(from)
.with_last_id(to)
.with_txs_count(1)
.with_tip(prio.into())
.with_executor_id(format!("txs-executor_{}_{}_{}", from, to, prio))
.with_send_threshold(usize::MAX)
.with_legacy_backend(true)
.build()
.await
}
/// Repeatedly sends batches of transactions to the specified node with priority provided by
/// closure.
///
/// This function loops indefinitely, adjusting the priority of the transaction batch each time
/// based on the provided function. Each batch is executed by an executor that times out after
/// period duration if not completed.
///
/// The progress of transactions is intentionally not monitored; the utility is intended for
/// transaction pool limits testing, where the accuracy of execution is challenging to monitor.
async fn batch_loop<F>(
net: &NetworkSpawner,
node_name: &str,
from: u32,
to: u32,
priority: F,
period: std::time::Duration,
) where
F: Fn(u32) -> u32,
{
let mut prio = 0;
loop {
prio = priority(prio);
let executor = send_batch(&net, node_name, from, to, prio).await;
let start = std::time::Instant::now();
let _results = tokio::time::timeout(period, executor.execute()).await;
let elapsed = start.elapsed();
if elapsed < period {
tokio::time::sleep(period - elapsed).await;
}
}
}
/// Tests the transaction pool limits by continuously sending transaction batches to a teyrchain
/// network node. This test checks the pool's behavior under high load by simulating multiple
/// senders with increasing priorities.
#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn test_limits_increasing_prio_teyrchain() {
let net = NetworkSpawner::from_toml_with_env_logger(para::LOW_POOL_LIMIT_FATP)
.await
.unwrap();
net.wait_for_block("charlie", BlockSubscriptionType::Best).await.unwrap();
let mut executors = vec![];
let senders_count = 25;
let sender_batch = 2000;
for i in 0..senders_count {
let from = 0 + i * sender_batch;
let to = from + sender_batch - 1;
executors.push(batch_loop(
&net,
"charlie",
from,
to,
|prio| prio + 1,
Duration::from_secs(60),
));
}
let _results = join_all(executors).await;
}
/// Tests the transaction pool limits by continuously sending transaction batches to a relaychain
/// network node. This test checks the pool's behavior under high load by simulating multiple
/// senders with increasing priorities.
#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn test_limits_increasing_prio_relaychain() {
let net = NetworkSpawner::from_toml_with_env_logger(relay::LOW_POOL_LIMIT_FATP)
.await
.unwrap();
net.wait_for_block("alice", BlockSubscriptionType::Best).await.unwrap();
let mut executors = vec![];
//this looks like current limit of what we can handle. A bit choky but almost no empty blocks.
let senders_count = 50;
let sender_batch = 2000;
for i in 0..senders_count {
let from = 0 + i * sender_batch;
let to = from + sender_batch - 1;
executors.push(batch_loop(
&net,
"alice",
from,
to,
|prio| prio + 1,
Duration::from_secs(15),
));
}
let _results = join_all(executors).await;
}
/// Tests the transaction pool limits by continuously sending transaction batches to a relaychain
/// network node. This test checks the pool's behavior under high load by simulating multiple
/// senders with increasing priorities.
#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn test_limits_same_prio_relaychain() {
let net = NetworkSpawner::from_toml_with_env_logger(relay::LOW_POOL_LIMIT_FATP)
.await
.unwrap();
net.wait_for_block("alice", BlockSubscriptionType::Best).await.unwrap();
let mut executors = vec![];
let senders_count = 50;
let sender_batch = 2000;
for i in 0..senders_count {
let from = 0 + i * sender_batch;
let to = from + sender_batch - 1;
executors.push(batch_loop(&net, "alice", from, to, |prio| prio, Duration::from_secs(15)));
}
let _results = join_all(executors).await;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,250 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! The zombienet spawner for integration tests for a transaction pool. Holds shared logic used
//! across integration tests for transaction pool.
use anyhow::anyhow;
use std::time::SystemTime;
use tracing_subscriber::EnvFilter;
use txtesttool::scenario::{ChainType, ScenarioBuilder};
use zombienet_sdk::{
subxt::BizinikiwiConfig, GlobalSettingsBuilder, LocalFileSystem, Network, NetworkConfig,
NetworkConfigBuilder, NetworkConfigExt, WithRelaychain,
};
/// Gathers TOML files paths for relaychains and for teyrchains' (that use pezkuwichain-local based
/// relaychains) zombienet network specs for testing in relation to fork aware transaction pool.
pub mod relaychain_pezkuwichain_local_network_spec {
pub const HIGH_POOL_LIMIT_FATP: &'static str =
"tests/zombienet/network-specs/pezkuwichain-local-high-pool-limit-fatp.toml";
pub const LOW_POOL_LIMIT_FATP: &'static str =
"tests/zombienet/network-specs/pezkuwichain-local-low-pool-limit-fatp.toml";
pub const HIGH_POOL_LIMIT_FATP_TRACE: &'static str =
"tests/zombienet/network-specs/pezkuwichain-local-gossiping.toml";
/// Network specs used for fork-aware tx pool testing of teyrchains.
pub mod teyrchain_asset_hub_network_spec {
pub const LOW_POOL_LIMIT_FATP: &'static str =
"tests/zombienet/network-specs/asset-hub-low-pool-limit-fatp.toml";
pub const HIGH_POOL_LIMIT_FATP: &'static str =
"tests/zombienet/network-specs/asset-hub-high-pool-limit-fatp.toml";
}
}
mod yap_test;
/// Default time that we expect to need for a full run of current tests that send future and ready
/// txs to teyrchain or relaychain networks.
pub const DEFAULT_SEND_FUTURE_AND_READY_TXS_TESTS_TIMEOUT_IN_SECS: u64 = 1500;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("Network initialization failure: {0}")]
NetworkInit(anyhow::Error),
#[error("Node couldn't be found as part of the network: {0}")]
NodeNotFound(anyhow::Error),
#[error("Failed to get node online client")]
FailedToGetOnlineClinet,
#[error("Failed to get node blocks stream")]
FailedToGetBlocksStream,
}
/// Result of work related to network spawning.
pub type Result<T> = std::result::Result<T, Error>;
/// Environment variable defining the location of zombienet network base dir.
const TXPOOL_TEST_DIR_ENV: &str = "TXPOOL_TEST_DIR";
/// Type for block subscription modes.
pub enum BlockSubscriptionType {
Finalized,
Best,
}
/// Provides logic to spawn a network based on a Zombienet toml file.
pub struct NetworkSpawner {
network: Network<LocalFileSystem>,
}
impl NetworkSpawner {
/// Initialize the network spawner using given `builder` closure.
pub async fn with_closure<F>(builder: F) -> Result<NetworkSpawner>
where
F: FnOnce() -> NetworkConfigBuilder<WithRelaychain>,
{
let _ = env_logger::try_init_from_env(
env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"),
);
let config_builder = builder();
let net_config = config_builder
.with_global_settings(|global_settings| match NetworkSpawner::base_dir_from_env() {
Some(val) => global_settings.with_base_dir(val),
_ => global_settings,
})
.build()
.map_err(|errs| {
let msg = errs.into_iter().map(|e| e.to_string()).collect::<Vec<_>>().join(", ");
Error::NetworkInit(anyhow!(msg))
})?;
Ok(NetworkSpawner {
network: net_config
.spawn_native()
.await
.map_err(|err| Error::NetworkInit(anyhow!(err.to_string())))?,
})
}
/// Generates a directory path from an environment variable and the current timestamp.
/// The format is "<TENV>/test_YMD_HMS"
pub fn base_dir_from_env() -> Option<String> {
std::env::var(TXPOOL_TEST_DIR_ENV)
.map(|pool_test_dir| {
let datetime: chrono::DateTime<chrono::Local> = SystemTime::now().into();
let formatted_date = datetime.format("%Y%m%d_%H%M%S");
format!("{}/test_{}", pool_test_dir, formatted_date)
})
.ok()
}
/// Initialize the network spawner based on a Zombienet toml file
pub async fn from_toml_with_env_logger(toml_path: &'static str) -> Result<NetworkSpawner> {
// Initialize the subscriber with a default log level of INFO if RUST_LOG is not set
let env_filter =
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
// Set up the subscriber with the formatter and the environment filter
tracing_subscriber::fmt()
.with_env_filter(env_filter) // Use the env filter
.init();
let net_config = if let Some(base_dir) = Self::base_dir_from_env() {
let settings = GlobalSettingsBuilder::new().with_base_dir(base_dir).build().unwrap();
NetworkConfig::load_from_toml_with_settings(toml_path, &settings)
.map_err(Error::NetworkInit)?
} else {
tracing::info!("'{TXPOOL_TEST_DIR_ENV}' env not set, proceeding with defaults.");
NetworkConfig::load_from_toml(toml_path).map_err(Error::NetworkInit)?
};
Ok(NetworkSpawner {
network: net_config
.spawn_native()
.await
.map_err(|err| Error::NetworkInit(anyhow!(err.to_string())))?,
})
}
/// Returns the spawned network.
pub fn network(&self) -> &Network<LocalFileSystem> {
&self.network
}
/// Waits for blocks production/import to kick-off on given node.
///
/// It subscribes to best/finalized blocks on the given node to determine whether
/// the blocks were considered as best/finalized.
pub async fn wait_for_block(
&self,
node_name: &str,
subscription_type: BlockSubscriptionType,
) -> Result<()> {
let node = self
.network
.get_node(node_name)
.map_err(|_| Error::NodeNotFound(anyhow!("{node_name}")))?;
let client = node
.wait_client::<BizinikiwiConfig>()
.await
.map_err(|_| Error::FailedToGetOnlineClinet)?;
let mut stream = match subscription_type {
BlockSubscriptionType::Best => client
.blocks()
.subscribe_finalized()
.await
.map_err(|_| Error::FailedToGetBlocksStream)?,
BlockSubscriptionType::Finalized => client
.blocks()
.subscribe_best()
.await
.map_err(|_| Error::FailedToGetBlocksStream)?,
};
// It should take at most two iterations to return with the best block, if any.
for _ in 0..=1 {
let Some(block) = stream.next().await else {
continue;
};
if let Some(block) = block.ok().filter(|block| block.number() == 1) {
tracing::info!("[{node_name}] found first block: {:#?}", block.hash());
break;
}
tracing::info!("[{node_name}] waiting for first block");
}
Ok(())
}
/// Get the network filesystem base dir path.
pub fn base_dir_path(&self) -> Option<&str> {
self.network.base_dir()
}
/// Get a certain node rpc uri.
pub fn node_rpc_uri(&self, node_name: &str) -> Result<String> {
self.network
.get_node(node_name)
.and_then(|node| Ok(node.ws_uri().to_string()))
.map_err(|_| Error::NodeNotFound(anyhow!("{node_name}")))
}
}
/// Shared params usually set in same way for most of the scenarios.
pub struct ScenarioBuilderSharedParams {
watched_txs: bool,
does_block_monitoring: bool,
send_threshold: usize,
chain_type: ChainType,
}
impl Default for ScenarioBuilderSharedParams {
fn default() -> Self {
Self {
watched_txs: true,
does_block_monitoring: false,
send_threshold: 20000,
chain_type: ChainType::Sub,
}
}
}
/// Creates a [`txtesttool::scenario::ScenarioBuilder`] with a set of default parameters defined
/// with [`ScenarioBuilderSharedParams::default`].
pub fn default_zn_scenario_builder(net_spawner: &NetworkSpawner) -> ScenarioBuilder {
let shared_params = ScenarioBuilderSharedParams::default();
ScenarioBuilder::new()
.with_watched_txs(shared_params.watched_txs)
.with_send_threshold(shared_params.send_threshold)
.with_block_monitoring(shared_params.does_block_monitoring)
.with_chain_type(shared_params.chain_type)
.with_base_dir_path(net_spawner.base_dir_path().unwrap().to_string())
.with_timeout_in_secs(21600) //6 hours
}
@@ -0,0 +1,62 @@
[settings]
timeout = 1500
[relaychain]
default_image = "pezkuwichain/pezkuwi:latest"
default_command = "pezkuwi"
chain = "pezkuwichain-local"
[[relaychain.nodes]]
name = "alice"
rpc_port = 9944
validator = true
[[relaychain.nodes]]
name = "bob"
rpc_port = 9945
validator = true
[[teyrchains]]
id = 2000
chain = "asset-hub-pezkuwichain-local"
default_command = "pezkuwi-teyrchain"
default_image = "pezkuwichain/pezkuwi-teyrchain:latest"
cumulus_based = true
default_args = [
"--force-authoring",
"--pool-kbytes 2048000",
"--pool-limit 500000",
"--pool-type=fork-aware",
"--rpc-max-connections 15000",
"--rpc-max-response-size 150",
"--rpc-max-subscriptions-per-connection=128000",
"--state-pruning=1024",
"-laura::pezcumulus=info",
"-lbasic-authorship=info",
"-lpeerset=info",
"-lsub-libp2p=info",
"-lsync=info",
"-ltxpool=debug",
]
[teyrchains.genesis.runtimeGenesis.patch.balances]
devAccounts = [1000, 1000000000000000000, "//Sender//{}"]
[[teyrchains.collators]]
name = "charlie"
validator = false
rpc_port = 9933
[[teyrchains.collators]]
name = "dave"
validator = true
rpc_port = 9934
[[teyrchains.collators]]
name = "eve"
validator = true
rpc_port = 9935
[[teyrchains.collators]]
name = "ferdie"
validator = true
rpc_port = 9936
@@ -0,0 +1,63 @@
[settings]
timeout = 1500
[relaychain]
default_image = "pezkuwichain/pezkuwi:latest"
default_command = "pezkuwi"
chain = "pezkuwichain-local"
[[relaychain.nodes]]
name = "alice"
rpc_port = 9944
validator = true
[[relaychain.nodes]]
name = "bob"
validator = true
[[teyrchains]]
id = 2000
cumulus_based = true
chain = "asset-hub-pezkuwichain-local"
default_image = "pezkuwichain/pezkuwi-teyrchain:latest"
default_command = "pezkuwi-teyrchain"
default_args = [
"--force-authoring",
"--pool-kbytes 2048000",
"--pool-limit 10000",
"--pool-type=fork-aware",
"--rpc-max-connections 100000",
"--rpc-max-response-size 150",
"--rpc-max-subscriptions-per-connection=128000",
"--state-pruning=1024",
"-laura::pezcumulus=info",
"-lbasic-authorship=info",
"-lpeerset=info",
"-lsub-libp2p=info",
"-lsync=info",
"-ltxpool=debug",
"-ltxpoolstat=debug",
]
[teyrchains.genesis.runtimeGenesis.patch.balances]
devAccounts = [80000, 1000000000000000000, "//Sender//{}"]
# run charlie as teyrchain collator
[[teyrchains.collators]]
name = "charlie"
validator = false
rpc_port = 9933
[[teyrchains.collators]]
name = "dave"
validator = true
rpc_port = 9934
[[teyrchains.collators]]
name = "eve"
validator = true
rpc_port = 9935
[[teyrchains.collators]]
name = "ferdie"
validator = true
rpc_port = 9936
@@ -0,0 +1,108 @@
[settings]
timeout = 1500
[relaychain]
default_image = "pezkuwichain/pezkuwi:latest"
default_command = "pezkuwi"
chain = "pezkuwichain-local"
default_args = [
# "--network-backend litep2p",
"--pool-kbytes 2048000",
"--pool-limit 500000",
"--pool-type=fork-aware",
"--rpc-max-connections 15000",
"--rpc-max-response-size 150",
"--rpc-max-subscriptions-per-connection=128000",
"--state-pruning=1024",
"-ltxpool=trace",
"-lsync=trace",
"--out-peers=3",
"--in-peers=3",
]
[relaychain.genesis.runtimeGenesis.patch.balances]
devAccounts = [1000, 1000000000000000000, "//Sender//{}"]
[[relaychain.nodes]]
name = "a00"
rpc_port = 9944
validator = true
[[relaychain.nodes]]
name = "b00"
validator = true
[[relaychain.nodes]]
name = "b01"
validator = true
[[relaychain.nodes]]
name = "b02"
validator = true
[[relaychain.nodes]]
name = "b03"
validator = true
[[relaychain.nodes]]
name = "b04"
validator = true
[[relaychain.nodes]]
name = "b05"
validator = true
[[relaychain.nodes]]
name = "b06"
validator = true
[[relaychain.nodes]]
name = "b07"
validator = true
[[relaychain.nodes]]
name = "b08"
validator = true
[[relaychain.nodes]]
name = "b09"
validator = true
[[relaychain.nodes]]
name = "b10"
validator = true
[[relaychain.nodes]]
name = "b11"
validator = true
[[relaychain.nodes]]
name = "b12"
validator = true
[[relaychain.nodes]]
name = "b13"
validator = true
[[relaychain.nodes]]
name = "b14"
validator = true
[[relaychain.nodes]]
name = "b15"
validator = true
[[relaychain.nodes]]
name = "b16"
validator = true
[[relaychain.nodes]]
name = "b17"
validator = true
[[relaychain.nodes]]
name = "b18"
validator = true
[[relaychain.nodes]]
name = "b19"
validator = true
@@ -0,0 +1,30 @@
[settings]
timeout = 1500
[relaychain]
default_image = "pezkuwichain/pezkuwi:latest"
default_command = "pezkuwi"
chain = "pezkuwichain-local"
default_args = [
"--pool-kbytes 2048000",
"--pool-limit 500000",
"--pool-type=fork-aware",
"--rpc-max-connections 15000",
"--rpc-max-response-size 1500",
"--rpc-max-subscriptions-per-connection=128000",
"--state-pruning=1024",
"-lsync=info",
"-ltxpool=debug",
]
[relaychain.genesis.runtimeGenesis.patch.balances]
devAccounts = [1000, 1000000000000000000, "//Sender//{}"]
[[relaychain.nodes]]
name = "alice"
rpc_port = 9944
validator = true
[[relaychain.nodes]]
name = "bob"
rpc_port = 9945
validator = true
@@ -0,0 +1,38 @@
[settings]
timeout = 1500
[relaychain]
default_image = "pezkuwichain/pezkuwi:latest"
default_command = "pezkuwi"
chain = "pezkuwichain-local"
default_args = [
"--pool-kbytes 2048000",
"--pool-limit 100000",
"--pool-type=fork-aware",
"--rpc-max-connections 15000",
"--rpc-max-response-size 150",
"--rpc-max-subscriptions-per-connection=128000",
"--state-pruning=1024",
"-lsync=info",
"-ltxpool=debug",
"-ltxpoolstat=debug",
]
[relaychain.genesis.runtimeGenesis.patch.balances]
devAccounts = [100000, 1000000000000000000, "//Sender//{}"]
[[relaychain.nodes]]
# command = "/home/miszka/parity/14-txpool-forks/pezkuwi-sdk-master-02/target/release-tokio-console/pezkuwi"
name = "alice"
rpc_port = 9944
validator = false
[[relaychain.nodes]]
name = "bob"
rpc_port = 9945
validator = true
[[relaychain.nodes]]
name = "charlie"
rpc_port = 9946
validator = true
@@ -0,0 +1,145 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
// Test inspired (copied) from:
// https://github.com/pezkuwichain/pezkuwi-sdk/blob/85b71daf7aac59da4d2186b45d589c7c619f0981/polkadot/zombienet-sdk-tests/tests/elastic_scaling/slot_based_3cores.rs#L21
// and patched as in:
// https://github.com/pezkuwichain/kurdistan-sdk/issues/124#issuecomment-2808830472
use crate::zombienet::{BlockSubscriptionType, NetworkSpawner, ScenarioBuilderSharedParams};
use cumulus_zombienet_sdk_helpers::create_assign_core_call;
use serde_json::json;
use txtesttool::{execution_log::ExecutionLog, scenario::ScenarioBuilder};
use zombienet_sdk::{
subxt::{OnlineClient, PolkadotConfig},
subxt_signer::sr25519::dev,
NetworkConfigBuilder,
};
#[tokio::test(flavor = "multi_thread")]
#[ignore]
async fn slot_based_3cores_test() -> Result<(), anyhow::Error> {
let spawner = NetworkSpawner::with_closure(|| {
let images = zombienet_sdk::environment::get_images_from_env();
let names = ["alice", "bob", "charlie"];
NetworkConfigBuilder::new()
.with_relaychain(|r| {
let r = r
.with_chain("pezkuwichain-local")
.with_default_command("pezkuwi")
.with_default_image(images.pezkuwi.as_str())
.with_default_args(vec![("-lteyrchain=debug").into()])
.with_genesis_overrides(json!({
"configuration": {
"config": {
"scheduler_params": {
// Num cores is 2, because 1 extra will be added automatically when registering the para.
"num_cores": 2,
"max_validators_per_core": 1
}
}
}
}))
.with_default_resources(|resources| {
resources.with_request_cpu(4).with_request_memory("4G")
})
// Have to set a `with_node` outside of the loop below, so that `r` has the
// right type.
.with_node(|node| node.with_name(names[0]));
(1..3).fold(r, |acc, i| acc.with_node(|node| node.with_name(names[i])))
})
.with_teyrchain(|p| {
// Para 2200 uses the new RFC103-enabled collator which sends the UMP signal
// commitment for selecting the core index
p.with_id(2200)
.with_default_command("pezkuwi-teyrchain")
.with_default_image(images.pezcumulus.as_str())
.with_chain("yap-pezkuwichain-local-2200")
.with_genesis_overrides(json!({
"balances": {
"devAccounts": [
100000, 1000000000000000000u64, "//Sender//{}"
]
}
}))
.with_default_args(vec![
"--authoring=slot-based".into(),
"--rpc-max-subscriptions-per-connection=256000".into(),
"--rpc-max-connections=128000".into(),
"--rpc-max-response-size=150".into(),
"--pool-limit=2500000".into(),
"--pool-kbytes=4048000".into(),
"--pool-type=fork-aware".into(),
("-lteyrchain=debug,aura=debug,txpool=debug,txpoolstat=debug").into(),
])
.with_collator(|n| n.with_name("dave").with_rpc_port(9944))
})
})
.await
.unwrap();
let relay_node = spawner.network().get_node("alice")?;
let relay_client: OnlineClient<PolkadotConfig> = relay_node.wait_client().await?;
let alice = dev::alice();
let assign_cores_call = create_assign_core_call(&[(0, 2200), (1, 2200)]);
// Assign two extra cores to each teyrchain.
relay_client
.tx()
.sign_and_submit_then_watch_default(&assign_cores_call, &alice)
.await?
.wait_for_finalized_success()
.await?;
tracing::info!("2 more cores assigned to the teyrchain");
// Wait for the teyrchain collator to start block production.
spawner.wait_for_block("dave", BlockSubscriptionType::Best).await.unwrap();
// Create txs executor.
let ws = spawner.node_rpc_uri("dave").unwrap();
let executor = {
let shared_params = ScenarioBuilderSharedParams::default();
ScenarioBuilder::new()
.with_watched_txs(shared_params.watched_txs)
.with_send_threshold(shared_params.send_threshold)
.with_block_monitoring(shared_params.does_block_monitoring)
.with_chain_type(shared_params.chain_type)
.with_base_dir_path(spawner.base_dir_path().unwrap().to_string())
.with_timeout_in_secs(21600) //6 hours
.with_legacy_backend(true)
}
.with_rpc_uri(ws)
.with_start_id(0)
.with_last_id(99999)
.with_txs_count(150)
.with_executor_id("txs-executor".to_string())
.with_send_threshold(25000)
.build()
.await;
// Execute transactions and fetch the execution logs.
let execution_logs = executor.execute().await;
let finalized_txs = execution_logs.values().filter_map(|tx_log| tx_log.finalized()).count();
assert_eq!(finalized_txs, 15_000_000);
Ok(())
}