Skip to content

Commit 38c598e

Browse files
iulianbarbugithub-actions[bot]skunert
authored
fatxpool: added mortal transactions integration test (#8887)
# Description Based on michalkucharczyk/tx-test-tool#43. ## Integration N/A ## Review Notes - added tests with future mortal txs that are dropped due to not being included in blocks within their lifetime, but also future mortal txs that have a sufficient lifetime to be included in blocks - added test which uses priorities to delay mortal txs inclusion and make them invalid --------- Signed-off-by: Iulian Barbu <iulian.barbu@parity.io> Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Sebastian Kunert <mail@skunert.dev>
1 parent 80ab459 commit 38c598e

File tree

6 files changed

+210
-23
lines changed

6 files changed

+210
-23
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1463,7 +1463,7 @@ trybuild = { version = "1.0.103" }
14631463
tt-call = { version = "1.0.8" }
14641464
tuplex = { version = "0.1", default-features = false }
14651465
twox-hash = { version = "1.6.3", default-features = false }
1466-
txtesttool = { version = "0.6.0", package = "substrate-txtesttool" }
1466+
txtesttool = { version = "0.7.0", package = "substrate-txtesttool" }
14671467
unsigned-varint = { version = "0.7.2" }
14681468
url = { version = "2.5.4" }
14691469
verifiable = { version = "0.1", default-features = false }

substrate/client/transaction-pool/tests/integration.rs

Lines changed: 175 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ use std::time::Duration;
2424

2525
use crate::zombienet::{
2626
default_zn_scenario_builder, relaychain_rococo_local_network_spec as relay,
27-
relaychain_rococo_local_network_spec::parachain_asset_hub_network_spec as para, NetworkSpawner,
27+
relaychain_rococo_local_network_spec::parachain_asset_hub_network_spec as para,
28+
BlockSubscriptionType, NetworkSpawner,
2829
};
2930
use futures::future::join_all;
3031
use tracing::info;
@@ -41,7 +42,7 @@ async fn send_future_and_ready_from_many_accounts_to_parachain() {
4142
.unwrap();
4243

4344
// Wait for the parachain collator to start block production.
44-
net.wait_for_block_production("charlie").await.unwrap();
45+
net.wait_for_block("charlie", BlockSubscriptionType::Best).await.unwrap();
4546

4647
// Create future & ready txs executors.
4748
let ws = net.node_rpc_uri("charlie").unwrap();
@@ -93,7 +94,7 @@ async fn send_future_and_ready_from_many_accounts_to_relaychain() {
9394

9495
// Wait for the paracha validator to start block production & have its genesis block
9596
// finalized.
96-
net.wait_for_block_production("alice").await.unwrap();
97+
net.wait_for_block("alice", BlockSubscriptionType::Best).await.unwrap();
9798

9899
// Create future & ready txs executors.
99100
let ws = net.node_rpc_uri("alice").unwrap();
@@ -135,6 +136,171 @@ async fn send_future_and_ready_from_many_accounts_to_relaychain() {
135136
assert_eq!(finalized_ready, 10_000);
136137
}
137138

139+
// Send immortal and mortal txs. Some of the mortal txs are configured to get dropped
140+
// while others to succeed. Mortal txs are future so not being able to become ready in time and
141+
// included in blocks result in their dropping.
142+
//
143+
// Block length for rococo for user txs is 75% of maximum 5MB (per frame-system setup),
144+
// so we get 3750KB. In the test scenario we aim for 5 txs per block roughly (not precesily)
145+
// so to fill a block each user tx must have around 750kb.
146+
#[tokio::test(flavor = "multi_thread")]
147+
#[ignore]
148+
async fn send_future_mortal_txs() {
149+
let net = NetworkSpawner::from_toml_with_env_logger(relay::HIGH_POOL_LIMIT_FATP)
150+
.await
151+
.unwrap();
152+
153+
// Wait for the parachain collator to start block production.
154+
net.wait_for_block("alice", BlockSubscriptionType::Finalized).await.unwrap();
155+
156+
// Create txs executors.
157+
let ws = net.node_rpc_uri("alice").unwrap();
158+
let ready_scenario_executor = default_zn_scenario_builder(&net)
159+
.with_rpc_uri(ws.clone())
160+
.with_start_id(0)
161+
.with_nonce_from(Some(0))
162+
.with_txs_count(50)
163+
// Block length for rococo for user txs is 75% of maximum 5MB (per frame-system setup),
164+
// so we get 3750KB. In the test scenario we aim for 5 txs per block roughly (not precesily)
165+
// so to fill a block each user tx must have around 750kb. We aim for 5 txs per block
166+
// because we send 50 ready txs which we want to distribute over 10 blocks, so mortal txs
167+
// with lifetime lower than 10 should be declared invalid after the ready txs finalize,
168+
// while mortal txs with bigger lifetime should be finalized.
169+
.with_remark_recipe(750)
170+
.with_executor_id("ready-txs-executor".to_string())
171+
.build()
172+
.await;
173+
174+
let mortal_scenario_invalid = default_zn_scenario_builder(&net)
175+
.with_rpc_uri(ws.clone())
176+
.with_start_id(0)
177+
.with_nonce_from(Some(60))
178+
.with_txs_count(10)
179+
.with_executor_id("mortal-tx-executor-invalid".to_string())
180+
.with_mortality(5)
181+
.build()
182+
.await;
183+
184+
let mortal_scenario_success = default_zn_scenario_builder(&net)
185+
.with_rpc_uri(ws)
186+
.with_start_id(0)
187+
.with_nonce_from(Some(50))
188+
.with_txs_count(10)
189+
.with_executor_id("mortal-tx-executor-success".to_string())
190+
.with_mortality(25)
191+
.build()
192+
.await;
193+
194+
// Execute transactions and fetch the execution logs.
195+
let (mortal_invalid_logs, ready_logs, mortal_succes_logs) = tokio::join!(
196+
mortal_scenario_invalid.execute(),
197+
ready_scenario_executor.execute(),
198+
mortal_scenario_success.execute(),
199+
);
200+
201+
let mortal_invalid = mortal_invalid_logs
202+
.values()
203+
.filter(|default_log| default_log.get_invalid_reason().len() > 0)
204+
.count();
205+
let mortal_succesfull = mortal_succes_logs
206+
.values()
207+
.filter_map(|default_log| default_log.finalized())
208+
.count();
209+
let finalized_ready =
210+
ready_logs.values().filter_map(|default_log| default_log.finalized()).count();
211+
212+
assert_eq!(mortal_invalid, 10);
213+
assert_eq!(mortal_succesfull, 10);
214+
assert_eq!(finalized_ready, 50);
215+
}
216+
217+
// Send immortal and mortal txs. Some mortal txs have lower priority so they shouldn't get into
218+
// blocks during their lifetime, and will be considered invalid, while other mortal txs have
219+
// sufficient lifetime to be included in blocks, and are finalized successfully.
220+
#[tokio::test(flavor = "multi_thread")]
221+
#[ignore]
222+
async fn send_lower_priority_mortal_txs() {
223+
let net = NetworkSpawner::from_toml_with_env_logger(relay::HIGH_POOL_LIMIT_FATP)
224+
.await
225+
.unwrap();
226+
227+
// Wait for the parachain collator to start block production.
228+
net.wait_for_block("alice", BlockSubscriptionType::Finalized).await.unwrap();
229+
230+
// Create txs executors.
231+
let ws = net.node_rpc_uri("alice").unwrap();
232+
let ready_scenario_executor = default_zn_scenario_builder(&net)
233+
.with_rpc_uri(ws.clone())
234+
.with_start_id(0)
235+
.with_nonce_from(Some(0))
236+
.with_txs_count(50)
237+
.with_executor_id("ready-txs-executor".to_string())
238+
// Block length for rococo for user txs is 75% of maximum 5MB (per frame-system setup),
239+
// so we get 3750KB. In the test scenario we aim for 5 txs per block roughly (not precesily)
240+
// so to fill a block each user tx must have around 750kb. We aim for 5 txs per block
241+
// because we send 50 ready txs which we want to distribute over 10 blocks, so mortal txs
242+
// with lifetime lower than 10 should be declared invalid after the ready txs finalize,
243+
// while mortal txs with bigger lifetime should be finalized.
244+
.with_remark_recipe(750)
245+
.with_tip(150)
246+
.build()
247+
.await;
248+
249+
let mortal_scenario_invalid = default_zn_scenario_builder(&net)
250+
.with_rpc_uri(ws.clone())
251+
.with_start_id(1)
252+
.with_nonce_from(Some(0))
253+
.with_txs_count(10)
254+
.with_executor_id("mortal-tx-executor-invalid".to_string())
255+
.with_mortality(5)
256+
// Make it very hard for these mortal txs to be included in blocks, by making them big
257+
// enough to not let other txs be part of the same block as them, but also make sure they
258+
// have the lowest priority so that they are not included in a single tx block over other
259+
// txs. At some point they'll be starved and their lifetime will pass.
260+
.with_remark_recipe(3500)
261+
.with_tip(50)
262+
.build()
263+
.await;
264+
265+
let mortal_scenario_success = default_zn_scenario_builder(&net)
266+
.with_rpc_uri(ws)
267+
.with_start_id(2)
268+
.with_nonce_from(Some(0))
269+
.with_txs_count(10)
270+
.with_executor_id("mortal-tx-executor-success".to_string())
271+
.with_mortality(20)
272+
// Same reasoning as for the ready immortal txs, we want these txs to be similarly big as
273+
// the immortal txs, so at all times if it comes to pick a ready txs to include it in a
274+
// block, an immortal tx should be picked instead (leaving these mortal txs to be picked
275+
// only after, which is fine for these mortal txs).
276+
.with_remark_recipe(750)
277+
.with_tip(100)
278+
.build()
279+
.await;
280+
281+
// Execute transactions and fetch the execution logs.
282+
let (mortal_invalid_logs, ready_logs, mortal_success_logs) = tokio::join!(
283+
mortal_scenario_invalid.execute(),
284+
ready_scenario_executor.execute(),
285+
mortal_scenario_success.execute(),
286+
);
287+
288+
let mortal_invalid = mortal_invalid_logs
289+
.values()
290+
.filter(|default_log| default_log.get_invalid_reason().len() > 0)
291+
.count();
292+
let mortal_succesfull = mortal_success_logs
293+
.values()
294+
.filter_map(|default_log| default_log.finalized())
295+
.count();
296+
let finalized_ready =
297+
ready_logs.values().filter_map(|default_log| default_log.finalized()).count();
298+
299+
assert_eq!(mortal_invalid, 10);
300+
assert_eq!(mortal_succesfull, 10);
301+
assert_eq!(finalized_ready, 50);
302+
}
303+
138304
// Test which sends 5m transactions to parachain. Long execution time expected.
139305
#[tokio::test(flavor = "multi_thread")]
140306
#[ignore]
@@ -144,7 +310,7 @@ async fn send_5m_from_many_accounts_to_parachain() {
144310
.unwrap();
145311

146312
// Wait for the parachain collator to start block production.
147-
net.wait_for_block_production("charlie").await.unwrap();
313+
net.wait_for_block("charlie", BlockSubscriptionType::Best).await.unwrap();
148314

149315
// Create txs executor.
150316
let ws = net.node_rpc_uri("charlie").unwrap();
@@ -174,7 +340,7 @@ async fn send_5m_from_many_accounts_to_relaychain() {
174340
.unwrap();
175341

176342
// Wait for the parachain collator to start block production.
177-
net.wait_for_block_production("alice").await.unwrap();
343+
net.wait_for_block("alice", BlockSubscriptionType::Best).await.unwrap();
178344

179345
// Create txs executor.
180346
let ws = net.node_rpc_uri("alice").unwrap();
@@ -206,7 +372,7 @@ async fn gossiping() {
206372
.unwrap();
207373

208374
// Wait for the parachain collator to start block production.
209-
net.wait_for_block_production("a00").await.unwrap();
375+
net.wait_for_block("a00", BlockSubscriptionType::Best).await.unwrap();
210376

211377
// Create the txs executor.
212378
let ws = net.node_rpc_uri("a00").unwrap();
@@ -293,7 +459,7 @@ async fn test_limits_increasing_prio_parachain() {
293459
.await
294460
.unwrap();
295461

296-
net.wait_for_block_production("charlie").await.unwrap();
462+
net.wait_for_block("charlie", BlockSubscriptionType::Best).await.unwrap();
297463

298464
let mut executors = vec![];
299465
let senders_count = 25;
@@ -325,7 +491,7 @@ async fn test_limits_increasing_prio_relaychain() {
325491
.await
326492
.unwrap();
327493

328-
net.wait_for_block_production("alice").await.unwrap();
494+
net.wait_for_block("alice", BlockSubscriptionType::Best).await.unwrap();
329495

330496
let mut executors = vec![];
331497
//this looks like current limit of what we can handle. A bit choky but almost no empty blocks.
@@ -358,7 +524,7 @@ async fn test_limits_same_prio_relaychain() {
358524
.await
359525
.unwrap();
360526

361-
net.wait_for_block_production("alice").await.unwrap();
527+
net.wait_for_block("alice", BlockSubscriptionType::Best).await.unwrap();
362528

363529
let mut executors = vec![];
364530
let senders_count = 50;

substrate/client/transaction-pool/tests/zombienet/mod.rs

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ pub type Result<T> = std::result::Result<T, Error>;
7171
/// Environment variable defining the location of zombienet network base dir.
7272
const TXPOOL_TEST_DIR_ENV: &str = "TXPOOL_TEST_DIR";
7373

74+
/// Type for block subscription modes.
75+
pub enum BlockSubscriptionType {
76+
Finalized,
77+
Best,
78+
}
79+
7480
/// Provides logic to spawn a network based on a Zombienet toml file.
7581
pub struct NetworkSpawner {
7682
network: Network<LocalFileSystem>,
@@ -152,7 +158,14 @@ impl NetworkSpawner {
152158
}
153159

154160
/// Waits for blocks production/import to kick-off on given node.
155-
pub async fn wait_for_block_production(&self, node_name: &str) -> Result<()> {
161+
///
162+
/// It subscribes to best/finalized blocks on the given node to determine whether
163+
/// the blocks were considered as best/finalized.
164+
pub async fn wait_for_block(
165+
&self,
166+
node_name: &str,
167+
subscription_type: BlockSubscriptionType,
168+
) -> Result<()> {
156169
let node = self
157170
.network
158171
.get_node(node_name)
@@ -161,23 +174,31 @@ impl NetworkSpawner {
161174
.wait_client::<SubstrateConfig>()
162175
.await
163176
.map_err(|_| Error::FailedToGetOnlineClinet)?;
164-
let mut stream = client
165-
.blocks()
166-
.subscribe_best()
167-
.await
168-
.map_err(|_| Error::FailedToGetBlocksStream)?;
177+
let mut stream = match subscription_type {
178+
BlockSubscriptionType::Best => client
179+
.blocks()
180+
.subscribe_finalized()
181+
.await
182+
.map_err(|_| Error::FailedToGetBlocksStream)?,
183+
BlockSubscriptionType::Finalized => client
184+
.blocks()
185+
.subscribe_best()
186+
.await
187+
.map_err(|_| Error::FailedToGetBlocksStream)?,
188+
};
189+
169190
// It should take at most two iterations to return with the best block, if any.
170191
for _ in 0..=1 {
171192
let Some(block) = stream.next().await else {
172193
continue;
173194
};
174195

175196
if let Some(block) = block.ok().filter(|block| block.number() == 1) {
176-
tracing::info!("[{node_name}] found first best block: {:#?}", block.hash());
197+
tracing::info!("[{node_name}] found first block: {:#?}", block.hash());
177198
break;
178199
}
179200

180-
tracing::info!("[{node_name}] waiting for first best block");
201+
tracing::info!("[{node_name}] waiting for first block");
181202
}
182203
Ok(())
183204
}

substrate/client/transaction-pool/tests/zombienet/network-specs/rococo-local-high-pool-limit-fatp.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ default_args = [
1010
"--pool-limit 500000",
1111
"--pool-type=fork-aware",
1212
"--rpc-max-connections 15000",
13-
"--rpc-max-response-size 150",
13+
"--rpc-max-response-size 1500",
1414
"--rpc-max-subscriptions-per-connection=128000",
1515
"--state-pruning=1024",
1616
"-lsync=info",

substrate/client/transaction-pool/tests/zombienet/yap_test.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
// and patched as in:
2222
// https://github.com/paritytech/polkadot-sdk/pull/7220#issuecomment-2808830472
2323

24-
use crate::zombienet::{NetworkSpawner, ScenarioBuilderSharedParams};
24+
use crate::zombienet::{BlockSubscriptionType, NetworkSpawner, ScenarioBuilderSharedParams};
2525
use cumulus_zombienet_sdk_helpers::create_assign_core_call;
2626
use serde_json::json;
2727
use txtesttool::{execution_log::ExecutionLog, scenario::ScenarioBuilder};
@@ -111,7 +111,7 @@ async fn slot_based_3cores_test() -> Result<(), anyhow::Error> {
111111
tracing::info!("2 more cores assigned to the parachain");
112112

113113
// Wait for the parachain collator to start block production.
114-
spawner.wait_for_block_production("dave").await.unwrap();
114+
spawner.wait_for_block("dave", BlockSubscriptionType::Best).await.unwrap();
115115

116116
// Create txs executor.
117117
let ws = spawner.node_rpc_uri("dave").unwrap();

0 commit comments

Comments
 (0)