Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions runtimes/bulletin-westend/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ use parachains_common::{
impls::DealWithFees,
message_queue::{NarrowOriginToSibling, ParaIdToSibling},
AccountId, AuraId, Balance, BlockNumber, Hash, Header, Nonce, Signature,
AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO,
AVERAGE_ON_INITIALIZE_RATIO,
};
use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate};
use sp_api::impl_runtime_apis;
Expand Down Expand Up @@ -171,10 +171,14 @@ pub fn native_version() -> NativeVersion {
NativeVersion { runtime_version: VERSION, can_author_with: Default::default() }
}

/// We allow for 90% of the block to be consumed by normal transactions.
const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(90);

parameter_types! {
pub const Version: RuntimeVersion = VERSION;
// 10 MiB (allows 9 MiB for normal transactions with 90% NORMAL_DISPATCH_RATIO)
pub RuntimeBlockLength: BlockLength =
BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO);
BlockLength::max_with_normal_ratio(10 * 1024 * 1024, NORMAL_DISPATCH_RATIO);
pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder()
.base_block(BlockExecutionWeight::get())
.for_class(DispatchClass::all(), |weights| {
Expand Down
92 changes: 87 additions & 5 deletions runtimes/bulletin-westend/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ use xcm_runtime_apis::conversions::LocationToAccountHelper;

const ALICE: [u8; 32] = [1u8; 32];

/// Advance to the next block for testing transaction storage.
fn advance_block() {
use bulletin_westend_runtime::TransactionStorage;
use frame_support::traits::{OnFinalize, OnInitialize};

let current = frame_system::Pallet::<Runtime>::block_number();

TransactionStorage::on_finalize(current);
System::on_finalize(current);

let next = current + 1;
System::set_block_number(next);

frame_system::BlockWeight::<Runtime>::kill();
frame_system::AllExtrinsicsLen::<Runtime>::kill();

System::on_initialize(next);
TransactionStorage::on_initialize(next);
}

fn construct_extrinsic(
sender: sp_core::sr25519::Pair,
call: RuntimeCall,
Expand Down Expand Up @@ -105,11 +125,11 @@ fn transaction_storage_runtime_sizes() {
let who: AccountId = account.to_account_id();
#[allow(clippy::identity_op)]
let sizes: [usize; 5] = [
2000, // 2 KB
256 * 1024, // 256 KB
512 * 1024, // 512 KB
1 * 1024 * 1024, // 1 MB
(3 * 1024 * 1024) / 2, // 1.5 MB
2000, // 2 KB
1 * 1024 * 1024, // 1 MB
4 * 1024 * 1024, // 4 MB
6 * 1024 * 1024, // 6 MB
8 * 1024 * 1024, // 8 MB
];
let total_bytes: u64 = sizes.iter().map(|s| *s as u64).sum();

Expand All @@ -131,6 +151,9 @@ fn transaction_storage_runtime_sizes() {

// store data via signed extrinsics (ValidateSigned consumes authorization)
for (index, size) in sizes.into_iter().enumerate() {
// Advance to a new block for each store
advance_block();

tracing::info!("Storing data with size: {size} and index: {index}");
let res = construct_and_apply_extrinsic(
account.pair(),
Expand Down Expand Up @@ -178,6 +201,65 @@ fn transaction_storage_runtime_sizes() {
});
}

/// Test maximum write throughput: 8 transactions of 1 MiB each in a single block (8 MiB total).
#[test]
fn transaction_storage_max_throughput() {
use bulletin_westend_runtime as runtime;
use bulletin_westend_runtime::BuildStorage;
use frame_support::assert_ok;
use pallet_transaction_storage::{AuthorizationExtent, Call as TxStorageCall};
use sp_keyring::Sr25519Keyring;

const NUM_TRANSACTIONS: u32 = 8;
const TRANSACTION_SIZE: usize = 1024 * 1024; // 1 MiB
const TOTAL_BYTES: u64 = (NUM_TRANSACTIONS as u64) * (TRANSACTION_SIZE as u64); // 8 MiB

sp_io::TestExternalities::new(
runtime::RuntimeGenesisConfig::default().build_storage().unwrap(),
)
.execute_with(|| {
let account = Sr25519Keyring::Alice;
let who: AccountId = account.to_account_id();

// fund Alice to cover length-based tx fees
let initial: Balance = 10_000_000_000_000_000_000u128;
<pallet_balances::Pallet<Runtime> as FungibleMutate<_>>::set_balance(&who, initial);

// authorize 8 transactions of 1 MiB each
assert_ok!(runtime::TransactionStorage::authorize_account(
RuntimeOrigin::root(),
who.clone(),
NUM_TRANSACTIONS,
TOTAL_BYTES,
));
assert_eq!(
runtime::TransactionStorage::account_authorization_extent(who.clone()),
AuthorizationExtent { transactions: NUM_TRANSACTIONS, bytes: TOTAL_BYTES },
);

// Advance to a fresh block
advance_block();

// Store all 8 transactions in the same block (no advance_block between them)
for index in 0..NUM_TRANSACTIONS {
let res = construct_and_apply_extrinsic(
account.pair(),
RuntimeCall::TransactionStorage(TxStorageCall::<Runtime>::store {
data: vec![0u8; TRANSACTION_SIZE],
}),
);
assert_ok!(res);
assert_ok!(res.unwrap());
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@antkve when 8 (or 9) passes, then I would add here one more store with construct_and_apply_extrinsic, which should fail because of // 10 MiB (allows 9 MiB for normal transactions with 90% NORMAL_DISPATCH_RATIO), right?

We should also cover check_proof in block, so the ultimate test case would be like:

- successfully submit `check_proof` tx (later (as follow-up, when we merge [configurable StoragePeriod](https://github.com/paritytech/polkadot-sdk/pull/10662)), we should advance blocks so the full block is hit by `check_proof` ) (maybe we will need to tune `NORMAL_DISPATCH_RATIO` because of this)
- 8-9x submit **successfully** `construct_and_apply_extrinsic(store)`
- the last submit `construct_and_apply_extrinsic(store)` should fail

Copy link
Contributor Author

@antkve antkve Dec 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bkontur Okay but at the moment we'd need to advance it for the whole set storage period to call check_proof. Should I do that or just leave a TODO for when configurable StoragePeriod is merged?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bkontur Okay but at the moment we'd need to advance it for the whole set storage period to call check_proof. Should I do that or just leave a TODO for when configurable StoragePeriod is merged?

Create follow-up issue and TODO please

// Verify all authorizations were consumed
assert_eq!(
runtime::TransactionStorage::account_authorization_extent(who.clone()),
AuthorizationExtent { transactions: 0, bytes: 0 },
);
});
}

#[test]
fn location_conversion_works() {
// the purpose of hardcoded values is to catch an unintended location conversion logic change.
Expand Down
Loading