From dcf0aff8d3b9c400ae4b7133ea8f4e09f1858d25 Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Tue, 9 Dec 2025 09:14:21 +0000 Subject: [PATCH 01/18] Align `StoragePeriod` between Bulletin and SDK --- .../transaction-storage/src/benchmarking.rs | 2 +- pallets/transaction-storage/src/lib.rs | 55 ++++++++++++++----- pallets/transaction-storage/src/mock.rs | 13 ++++- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/pallets/transaction-storage/src/benchmarking.rs b/pallets/transaction-storage/src/benchmarking.rs index b276598ec..efd73f337 100644 --- a/pallets/transaction-storage/src/benchmarking.rs +++ b/pallets/transaction-storage/src/benchmarking.rs @@ -157,7 +157,7 @@ mod benchmarks { vec![0u8; T::MaxTransactionSize::get() as usize], )?; } - run_to_block::(T::StoragePeriod::get() + BlockNumberFor::::one()); + run_to_block::(StoragePeriod::::get() + BlockNumberFor::::one()); let encoded_proof = proof(); let proof = TransactionStorageProof::decode(&mut &*encoded_proof).unwrap(); diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index 420828c0b..414907617 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -122,7 +122,7 @@ pub struct TransactionInfo { /// is used to find transaction info by block chunk index using binary search. /// /// Cumulative value of all previous transactions in the block; the last transaction holds the - /// total chunk value. + /// total chunks. block_chunks: ChunkIndex, } @@ -194,11 +194,6 @@ pub mod pallet { /// Maximum data set in a single transaction in bytes. #[pallet::constant] type MaxTransactionSize: Get; - /// Storage period for data in blocks. Should match - /// [`DEFAULT_STORAGE_PERIOD`](sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD) for - /// block authoring. - #[pallet::constant] - type StoragePeriod: Get>; /// Authorizations expire after this many blocks. #[pallet::constant] type AuthorizationPeriod: Get>; @@ -258,13 +253,14 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(n: BlockNumberFor) -> Weight { + // TODO: https://github.com/paritytech/polkadot-sdk/issues/10203 - Replace this with benchmarked weights. let mut weight = Weight::zero(); let db_weight = T::DbWeight::get(); // Drop obsolete roots. The proof for `obsolete` will be checked later // in this block, so we drop `obsolete` - 1. weight.saturating_accrue(db_weight.reads(1)); - let period = T::StoragePeriod::get(); + let period = StoragePeriod::::get(); let obsolete = n.saturating_sub(period.saturating_add(One::one())); if obsolete > Zero::zero() { weight.saturating_accrue(db_weight.writes(2)); @@ -282,7 +278,7 @@ pub mod pallet { >::take() || { // Proof is not required for early or empty blocks. let number = >::block_number(); - let period = T::StoragePeriod::get(); + let period = StoragePeriod::::get(); let target_number = number.saturating_sub(period); target_number.is_zero() || { @@ -294,7 +290,7 @@ pub mod pallet { "Storage proof must be checked once in the block" ); - // Insert new transactions + // Insert new transactions, iff they have chunks. let transactions = >::take(); let total_chunks = transactions.last().map_or(0, |t| t.block_chunks); if total_chunks != 0 { @@ -308,7 +304,9 @@ pub mod pallet { "Not useful if data cannot be stored" ); assert!(!T::MaxTransactionSize::get().is_zero(), "Not useful if data cannot be stored"); - assert!(!T::StoragePeriod::get().is_zero(), "Not useful if data is not stored"); + let default_period: BlockNumberFor = + sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(); + assert!(!default_period.is_zero(), "Not useful if data is not stored"); assert!( !T::AuthorizationPeriod::get().is_zero(), "Not useful if authorizations are never valid" @@ -318,7 +316,7 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// Index and store data off chain. Minimum data size is 1 bytes, maximum is + /// Index and store data off chain. Minimum data size is 1 byte, maximum is /// `MaxTransactionSize`. Data will be removed after `StoragePeriod` blocks, unless `renew` /// is called. /// @@ -346,9 +344,9 @@ pub mod pallet { debug_assert_eq!(chunk_count, num_chunks(data.len() as u32) as usize); let root = sp_io::trie::blake2_256_ordered_root(chunks, sp_runtime::StateVersion::V1); + let content_hash = sp_io::hashing::blake2_256(&data); let extrinsic_index = >::extrinsic_index().ok_or(Error::::BadContext)?; - let content_hash = sp_io::hashing::blake2_256(&data); sp_io::transaction_index::index(extrinsic_index, data.len() as u32, content_hash); let mut index = 0; @@ -436,7 +434,7 @@ pub mod pallet { // Get the target block metadata. let number = >::block_number(); - let period = T::StoragePeriod::get(); + let period = StoragePeriod::::get(); let target_number = number.saturating_sub(period); ensure!(!target_number.is_zero(), Error::::UnexpectedProof); let transactions = @@ -639,6 +637,11 @@ pub mod pallet { /// Storage fee per transaction. pub type EntryFee = StorageValue<_, BalanceOf>; + /// Storage period for data in blocks. Should match `sp_storage_proof::DEFAULT_STORAGE_PERIOD` + /// for block authoring. + #[pallet::storage] + pub type StoragePeriod = StorageValue<_, BlockNumberFor, ValueQuery>; + // Intermediates #[pallet::storage] pub(super) type BlockTransactions = @@ -648,6 +651,32 @@ pub mod pallet { #[pallet::storage] pub(super) type ProofChecked = StorageValue<_, bool, ValueQuery>; + #[pallet::genesis_config] + pub struct GenesisConfig { + pub byte_fee: BalanceOf, + pub entry_fee: BalanceOf, + pub storage_period: BlockNumberFor, + } + + impl Default for GenesisConfig { + fn default() -> Self { + Self { + byte_fee: 10u32.into(), + entry_fee: 1000u32.into(), + storage_period: sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(), + } + } + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + ByteFee::::put(&self.byte_fee); + EntryFee::::put(&self.entry_fee); + StoragePeriod::::put(&self.storage_period); + } + } + #[pallet::inherent] impl ProvideInherent for Pallet { type Call = Call; diff --git a/pallets/transaction-storage/src/mock.rs b/pallets/transaction-storage/src/mock.rs index 2d673e801..c20e46f1b 100644 --- a/pallets/transaction-storage/src/mock.rs +++ b/pallets/transaction-storage/src/mock.rs @@ -43,7 +43,6 @@ impl frame_system::Config for Test { } parameter_types! { - pub const StoragePeriod: BlockNumberFor = 10; pub const AuthorizationPeriod: BlockNumberFor = 10; pub const StoreRenewPriority: TransactionPriority = TransactionPriority::MAX; pub const StoreRenewLongevity: TransactionLongevity = 10; @@ -60,7 +59,6 @@ impl pallet_transaction_storage::Config for Test { type WeightInfo = (); type MaxBlockTransactions = ConstU32<{ DEFAULT_MAX_BLOCK_TRANSACTIONS }>; type MaxTransactionSize = ConstU32<{ DEFAULT_MAX_TRANSACTION_SIZE }>; - type StoragePeriod = StoragePeriod; type AuthorizationPeriod = AuthorizationPeriod; type Authorizer = EnsureRoot; type StoreRenewPriority = StoreRenewPriority; @@ -70,7 +68,16 @@ impl pallet_transaction_storage::Config for Test { } pub fn new_test_ext() -> TestExternalities { - let t = RuntimeGenesisConfig { system: Default::default() }.build_storage().unwrap(); + let t = RuntimeGenesisConfig { + system: Default::default(), + transaction_storage: pallet_transaction_storage::GenesisConfig:: { + storage_period: 10, + byte_fee: 2, + entry_fee: 200, + }, + } + .build_storage() + .unwrap(); t.into() } From ae27ed5dd2aba1cf0421db2ee810dff87827391d Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:15:32 +0000 Subject: [PATCH 02/18] Sync mock --- pallets/transaction-storage/src/mock.rs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/pallets/transaction-storage/src/mock.rs b/pallets/transaction-storage/src/mock.rs index c20e46f1b..479f92ce1 100644 --- a/pallets/transaction-storage/src/mock.rs +++ b/pallets/transaction-storage/src/mock.rs @@ -81,15 +81,13 @@ pub fn new_test_ext() -> TestExternalities { t.into() } -pub fn run_to_block(n: u64, f: impl Fn() -> Option) { - while System::block_number() < n { - if let Some(proof) = f() { - TransactionStorage::check_proof(RuntimeOrigin::none(), proof).unwrap(); - } - TransactionStorage::on_finalize(System::block_number()); - System::on_finalize(System::block_number()); - System::set_block_number(System::block_number() + 1); - System::on_initialize(System::block_number()); - TransactionStorage::on_initialize(System::block_number()); - } +pub fn run_to_block(n: u64, f: impl Fn() -> Option + 'static) { + System::run_to_block_with::( + n, + RunToBlockHooks::default().before_finalize(|_| { + if let Some(proof) = f() { + TransactionStorage::check_proof(RuntimeOrigin::none(), proof).unwrap(); + } + }), + ); } From 32dadb1d7594263a99c8d7454a2555d4968309b0 Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:15:43 +0000 Subject: [PATCH 03/18] Remove StoragePeriod --- runtime/src/lib.rs | 1 - runtimes/bulletin-polkadot/src/lib.rs | 1 - runtimes/bulletin-westend/src/storage.rs | 1 - 3 files changed, 3 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 9bd1ff274..b7aff7e1b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -352,7 +352,6 @@ impl pallet_transaction_storage::Config for Runtime { type WeightInfo = pallet_transaction_storage::weights::SubstrateWeight; type MaxBlockTransactions = ConstU32<512>; type MaxTransactionSize = ConstU32<{ 8 * 1024 * 1024 }>; - type StoragePeriod = StoragePeriod; type AuthorizationPeriod = AuthorizationPeriod; type Authorizer = EnsureRoot; type StoreRenewPriority = StoreRenewPriority; diff --git a/runtimes/bulletin-polkadot/src/lib.rs b/runtimes/bulletin-polkadot/src/lib.rs index 8b0aa1abc..12f45b73c 100644 --- a/runtimes/bulletin-polkadot/src/lib.rs +++ b/runtimes/bulletin-polkadot/src/lib.rs @@ -351,7 +351,6 @@ impl pallet_transaction_storage::Config for Runtime { type MaxBlockTransactions = ConstU32<512>; /// Max transaction size per block needs to be aligned with [`BlockLength`]. type MaxTransactionSize = ConstU32<{ 8 * 1024 * 1024 }>; - type StoragePeriod = StoragePeriod; type AuthorizationPeriod = AuthorizationPeriod; type Authorizer = EnsureRoot; type StoreRenewPriority = StoreRenewPriority; diff --git a/runtimes/bulletin-westend/src/storage.rs b/runtimes/bulletin-westend/src/storage.rs index 648c0f193..c409b5f00 100644 --- a/runtimes/bulletin-westend/src/storage.rs +++ b/runtimes/bulletin-westend/src/storage.rs @@ -51,7 +51,6 @@ impl pallet_transaction_storage::Config for Runtime { type MaxBlockTransactions = crate::ConstU32<512>; /// Max transaction size per block needs to be aligned with `BlockLength`. type MaxTransactionSize = crate::ConstU32<{ 8 * 1024 * 1024 }>; - type StoragePeriod = StoragePeriod; type AuthorizationPeriod = AuthorizationPeriod; type Authorizer = EitherOfDiverse< // Root can do whatever. From 91536d4f8e89af316b6928209c82751f1212399e Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:21:10 +0000 Subject: [PATCH 04/18] Use default --- pallets/transaction-storage/src/lib.rs | 3 +-- pallets/transaction-storage/src/mock.rs | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index 414907617..8c324e97a 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -304,8 +304,7 @@ pub mod pallet { "Not useful if data cannot be stored" ); assert!(!T::MaxTransactionSize::get().is_zero(), "Not useful if data cannot be stored"); - let default_period: BlockNumberFor = - sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(); + let default_period = GenesisConfig::::default().storage_period; assert!(!default_period.is_zero(), "Not useful if data is not stored"); assert!( !T::AuthorizationPeriod::get().is_zero(), diff --git a/pallets/transaction-storage/src/mock.rs b/pallets/transaction-storage/src/mock.rs index 479f92ce1..9bbe64595 100644 --- a/pallets/transaction-storage/src/mock.rs +++ b/pallets/transaction-storage/src/mock.rs @@ -70,11 +70,7 @@ impl pallet_transaction_storage::Config for Test { pub fn new_test_ext() -> TestExternalities { let t = RuntimeGenesisConfig { system: Default::default(), - transaction_storage: pallet_transaction_storage::GenesisConfig:: { - storage_period: 10, - byte_fee: 2, - entry_fee: 200, - }, + transaction_storage: Default::default(), } .build_storage() .unwrap(); From 0a4946161c228cd00e6ae33f84f2b3ea4f8ead4c Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:24:29 +0000 Subject: [PATCH 05/18] Update integrity_test --- pallets/transaction-storage/src/lib.rs | 5 +++-- pallets/transaction-storage/src/mock.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index 8c324e97a..eb6ca0f50 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -304,8 +304,9 @@ pub mod pallet { "Not useful if data cannot be stored" ); assert!(!T::MaxTransactionSize::get().is_zero(), "Not useful if data cannot be stored"); - let default_period = GenesisConfig::::default().storage_period; - assert!(!default_period.is_zero(), "Not useful if data is not stored"); + let default_period = sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(); + let storage_period = GenesisConfig::::default().storage_period; + assert_eq!(storage_period, default_period, "Not useful if data is not stored"); assert!( !T::AuthorizationPeriod::get().is_zero(), "Not useful if authorizations are never valid" diff --git a/pallets/transaction-storage/src/mock.rs b/pallets/transaction-storage/src/mock.rs index 9bbe64595..955437025 100644 --- a/pallets/transaction-storage/src/mock.rs +++ b/pallets/transaction-storage/src/mock.rs @@ -70,7 +70,7 @@ impl pallet_transaction_storage::Config for Test { pub fn new_test_ext() -> TestExternalities { let t = RuntimeGenesisConfig { system: Default::default(), - transaction_storage: Default::default(), + transaction_storage: pallet_transaction_storage::GenesisConfig::::default(), } .build_storage() .unwrap(); From bde511933ad601583d695538e55f35f056ca487f Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:30:56 +0000 Subject: [PATCH 06/18] Revert "Update integrity_test" This reverts commit 0a4946161c228cd00e6ae33f84f2b3ea4f8ead4c. --- pallets/transaction-storage/src/lib.rs | 5 ++--- pallets/transaction-storage/src/mock.rs | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index eb6ca0f50..8c324e97a 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -304,9 +304,8 @@ pub mod pallet { "Not useful if data cannot be stored" ); assert!(!T::MaxTransactionSize::get().is_zero(), "Not useful if data cannot be stored"); - let default_period = sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(); - let storage_period = GenesisConfig::::default().storage_period; - assert_eq!(storage_period, default_period, "Not useful if data is not stored"); + let default_period = GenesisConfig::::default().storage_period; + assert!(!default_period.is_zero(), "Not useful if data is not stored"); assert!( !T::AuthorizationPeriod::get().is_zero(), "Not useful if authorizations are never valid" diff --git a/pallets/transaction-storage/src/mock.rs b/pallets/transaction-storage/src/mock.rs index 955437025..9bbe64595 100644 --- a/pallets/transaction-storage/src/mock.rs +++ b/pallets/transaction-storage/src/mock.rs @@ -70,7 +70,7 @@ impl pallet_transaction_storage::Config for Test { pub fn new_test_ext() -> TestExternalities { let t = RuntimeGenesisConfig { system: Default::default(), - transaction_storage: pallet_transaction_storage::GenesisConfig::::default(), + transaction_storage: Default::default(), } .build_storage() .unwrap(); From 5116be38eaef305ad489f0b9f255cdb292e3727f Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:31:05 +0000 Subject: [PATCH 07/18] Revert "Use default" This reverts commit 91536d4f8e89af316b6928209c82751f1212399e. --- pallets/transaction-storage/src/lib.rs | 3 ++- pallets/transaction-storage/src/mock.rs | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index 8c324e97a..414907617 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -304,7 +304,8 @@ pub mod pallet { "Not useful if data cannot be stored" ); assert!(!T::MaxTransactionSize::get().is_zero(), "Not useful if data cannot be stored"); - let default_period = GenesisConfig::::default().storage_period; + let default_period: BlockNumberFor = + sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(); assert!(!default_period.is_zero(), "Not useful if data is not stored"); assert!( !T::AuthorizationPeriod::get().is_zero(), diff --git a/pallets/transaction-storage/src/mock.rs b/pallets/transaction-storage/src/mock.rs index 9bbe64595..479f92ce1 100644 --- a/pallets/transaction-storage/src/mock.rs +++ b/pallets/transaction-storage/src/mock.rs @@ -70,7 +70,11 @@ impl pallet_transaction_storage::Config for Test { pub fn new_test_ext() -> TestExternalities { let t = RuntimeGenesisConfig { system: Default::default(), - transaction_storage: Default::default(), + transaction_storage: pallet_transaction_storage::GenesisConfig:: { + storage_period: 10, + byte_fee: 2, + entry_fee: 200, + }, } .build_storage() .unwrap(); From 9804c05189dfc7928d8c26073fb4b500c8fbc557 Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Tue, 9 Dec 2025 10:31:58 +0000 Subject: [PATCH 08/18] Update integrity_test --- pallets/transaction-storage/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index 414907617..eb6ca0f50 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -304,9 +304,9 @@ pub mod pallet { "Not useful if data cannot be stored" ); assert!(!T::MaxTransactionSize::get().is_zero(), "Not useful if data cannot be stored"); - let default_period: BlockNumberFor = - sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(); - assert!(!default_period.is_zero(), "Not useful if data is not stored"); + let default_period = sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(); + let storage_period = GenesisConfig::::default().storage_period; + assert_eq!(storage_period, default_period, "Not useful if data is not stored"); assert!( !T::AuthorizationPeriod::get().is_zero(), "Not useful if authorizations are never valid" From 8651ae89a3c2c4df27e23b37e71e1875459ea1a4 Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Tue, 9 Dec 2025 11:13:36 +0000 Subject: [PATCH 09/18] Fix clippy --- pallets/transaction-storage/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index eb6ca0f50..7844dbec8 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -671,9 +671,9 @@ pub mod pallet { #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { - ByteFee::::put(&self.byte_fee); - EntryFee::::put(&self.entry_fee); - StoragePeriod::::put(&self.storage_period); + ByteFee::::put(self.byte_fee); + EntryFee::::put(self.entry_fee); + StoragePeriod::::put(self.storage_period); } } From d75aa8e672cf29dfafd4096cc68b01756b098811 Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Wed, 10 Dec 2025 08:51:57 +0000 Subject: [PATCH 10/18] Sync with SDK --- pallets/transaction-storage/src/lib.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index 7844dbec8..6bb57c8cd 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -33,6 +33,7 @@ mod mock; mod tests; use codec::{Decode, Encode, MaxEncodedLen}; +use core::fmt::Debug; use polkadot_sdk_frame::{ deps::{sp_core::sp_std::prelude::*, *}, prelude::*, @@ -54,12 +55,10 @@ pub use weights::WeightInfo; const LOG_TARGET: &str = "runtime::transaction-storage"; +// TODO: https://github.com/paritytech/polkadot-sdk/issues/10591 - Clarify purpose of allocator limits and decide whether to remove or use these constants. /// Maximum bytes that can be stored in one transaction. -// TODO: find out what is "allocator" and "allocator limit" // Setting higher limit also requires raising the allocator limit. -// TODO: not used, can we remove or use? pub const DEFAULT_MAX_TRANSACTION_SIZE: u32 = 8 * 1024 * 1024; -// TODO: not used, can we remove or use? pub const DEFAULT_MAX_BLOCK_TRANSACTIONS: u32 = 512; /// Encountered an impossible situation, implies a bug. @@ -74,7 +73,7 @@ pub const AUTHORIZATION_NOT_FOUND: InvalidTransaction = InvalidTransaction::Cust pub const AUTHORIZATION_NOT_EXPIRED: InvalidTransaction = InvalidTransaction::Custom(4); /// Number of transactions and bytes covered by an authorization. -#[derive(PartialEq, Eq, RuntimeDebug, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +#[derive(PartialEq, Eq, Debug, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] pub struct AuthorizationExtent { /// Number of transactions. pub transactions: u32, @@ -108,9 +107,7 @@ struct Authorization { type AuthorizationFor = Authorization>; /// State data for a stored transaction. -#[derive( - Encode, Decode, Clone, RuntimeDebug, PartialEq, Eq, scale_info::TypeInfo, MaxEncodedLen, -)] +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, scale_info::TypeInfo, MaxEncodedLen)] pub struct TransactionInfo { /// Chunk trie root. chunk_root: ::Output, From f807e4edc8a67e20825eddb9f23549da0777f37a Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:11:46 +0000 Subject: [PATCH 11/18] Update TODO --- pallets/transaction-storage/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index 6bb57c8cd..4e3553203 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -55,7 +55,7 @@ pub use weights::WeightInfo; const LOG_TARGET: &str = "runtime::transaction-storage"; -// TODO: https://github.com/paritytech/polkadot-sdk/issues/10591 - Clarify purpose of allocator limits and decide whether to remove or use these constants. +// TODO: https://github.com/paritytech/polkadot-bulletin-chain/issues/139 - Clarify purpose of allocator limits and decide whether to remove or use these constants. /// Maximum bytes that can be stored in one transaction. // Setting higher limit also requires raising the allocator limit. pub const DEFAULT_MAX_TRANSACTION_SIZE: u32 = 8 * 1024 * 1024; From ffee2b772b0ce475156ccb2965c9f406919238c4 Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:28:53 +0000 Subject: [PATCH 12/18] Update errors --- pallets/transaction-storage/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index 4e3553203..379bf541e 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -298,15 +298,15 @@ pub mod pallet { fn integrity_test() { assert!( !T::MaxBlockTransactions::get().is_zero(), - "Not useful if data cannot be stored" + "MaxTransactionSize must be greater than zero" ); - assert!(!T::MaxTransactionSize::get().is_zero(), "Not useful if data cannot be stored"); + assert!(!T::MaxTransactionSize::get().is_zero(), "MaxTransactionSize must be greater than zero"); let default_period = sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(); let storage_period = GenesisConfig::::default().storage_period; - assert_eq!(storage_period, default_period, "Not useful if data is not stored"); + assert_eq!(storage_period, default_period, "GenesisConfig.storage_period must match DEFAULT_STORAGE_PERIOD"); assert!( !T::AuthorizationPeriod::get().is_zero(), - "Not useful if authorizations are never valid" + "AuthorizationPeriod must be greater than zero" ); } } From b1a9c401225a10f4c1f6b1a5c7fb5088c6fd5f32 Mon Sep 17 00:00:00 2001 From: Raymond Cheung <178801527+raymondkfcheung@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:33:07 +0000 Subject: [PATCH 13/18] Fix fmt --- pallets/transaction-storage/src/lib.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index 379bf541e..c71d20dd2 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -300,10 +300,16 @@ pub mod pallet { !T::MaxBlockTransactions::get().is_zero(), "MaxTransactionSize must be greater than zero" ); - assert!(!T::MaxTransactionSize::get().is_zero(), "MaxTransactionSize must be greater than zero"); + assert!( + !T::MaxTransactionSize::get().is_zero(), + "MaxTransactionSize must be greater than zero" + ); let default_period = sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(); let storage_period = GenesisConfig::::default().storage_period; - assert_eq!(storage_period, default_period, "GenesisConfig.storage_period must match DEFAULT_STORAGE_PERIOD"); + assert_eq!( + storage_period, default_period, + "GenesisConfig.storage_period must match DEFAULT_STORAGE_PERIOD" + ); assert!( !T::AuthorizationPeriod::get().is_zero(), "AuthorizationPeriod must be greater than zero" From 4dca624babe84f3d799a422086be4938cc84ed03 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 29 Dec 2025 22:07:11 +0100 Subject: [PATCH 14/18] Reflect pallet changes from https://github.com/paritytech/polkadot-sdk/pull/10662 --- pallets/transaction-storage/README.md | 10 ++--- .../transaction-storage/src/benchmarking.rs | 2 +- pallets/transaction-storage/src/lib.rs | 43 ++++++++++++------- pallets/transaction-storage/src/mock.rs | 2 +- 4 files changed, 34 insertions(+), 23 deletions(-) diff --git a/pallets/transaction-storage/README.md b/pallets/transaction-storage/README.md index 0ed3ba279..b2b22fe44 100644 --- a/pallets/transaction-storage/README.md +++ b/pallets/transaction-storage/README.md @@ -2,8 +2,8 @@ Indexes transactions and manages storage proofs. -Allows storing arbitrary data on the chain. Data is automatically removed after `StoragePeriod` blocks, unless the storage is renewed. -Validators must submit proof of storing a random chunk of data for block `N - StoragePeriod` when producing block `N`. +Allows storing arbitrary data on the chain. Data is automatically removed after `RetentionPeriod` blocks, unless the storage is renewed. +Validators must submit proof of storing a random chunk of data for block `N - RetentionPeriod` when producing block `N`. # Running a chain @@ -16,7 +16,7 @@ cargo run --release -- build-spec --chain=local > sc_init.json ``` Edit the json chain spec file to customise the chain. The storage chain genesis params are configured in the `transactionStorage` section. -Note that `storagePeriod` is specified in blocks and changing it also requires code changes at the moment. +Note that `retentionPeriod` is specified in blocks and changing it also requires code changes at the moment. Build a raw spec from the init spec. @@ -32,7 +32,7 @@ cargo run --release -- --chain=sc.json -d /tmp/bob --storage-chain --keep-blocks ``` `--storage-chain` enables transaction indexing. -`--keep-blocks=100800` enables block pruning. The value here should be greater or equal than the storage period. +`--keep-blocks=100800` enables block pruning. The value here should be greater or equal than the retention period. `--ipfs-server` enables serving stored content over IPFS. Once the network is started, any other joining nodes need to sync with `--sync=fast`. Regular sync will fail because block pruning removes old blocks. The chain does not keep full block history. @@ -75,7 +75,7 @@ ipfs swarm connect ipfs block get /ipfs/ > kitten.jpeg ``` -To renew data and prevent it from being disposed after the storage period, use `transactionStorage.renew(block, index)` +To renew data and prevent it from being disposed after the retention period, use `transactionStorage.renew(block, index)` where `block` is the block number of the previous store or renew transction, and index is the index of that transaction in the block. diff --git a/pallets/transaction-storage/src/benchmarking.rs b/pallets/transaction-storage/src/benchmarking.rs index efd73f337..bcad7d936 100644 --- a/pallets/transaction-storage/src/benchmarking.rs +++ b/pallets/transaction-storage/src/benchmarking.rs @@ -157,7 +157,7 @@ mod benchmarks { vec![0u8; T::MaxTransactionSize::get() as usize], )?; } - run_to_block::(StoragePeriod::::get() + BlockNumberFor::::one()); + run_to_block::(crate::Pallet::::retention_period() + BlockNumberFor::::one()); let encoded_proof = proof(); let proof = TransactionStorageProof::decode(&mut &*encoded_proof).unwrap(); diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index c71d20dd2..dc544e78f 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -55,6 +55,9 @@ pub use weights::WeightInfo; const LOG_TARGET: &str = "runtime::transaction-storage"; +/// Default retention period for data (in blocks). +pub const DEFAULT_RETENTION_PERIOD: u32 = 100800; + // TODO: https://github.com/paritytech/polkadot-bulletin-chain/issues/139 - Clarify purpose of allocator limits and decide whether to remove or use these constants. /// Maximum bytes that can be stored in one transaction. // Setting higher limit also requires raising the allocator limit. @@ -257,7 +260,7 @@ pub mod pallet { // Drop obsolete roots. The proof for `obsolete` will be checked later // in this block, so we drop `obsolete` - 1. weight.saturating_accrue(db_weight.reads(1)); - let period = StoragePeriod::::get(); + let period = Self::retention_period(); let obsolete = n.saturating_sub(period.saturating_add(One::one())); if obsolete > Zero::zero() { weight.saturating_accrue(db_weight.writes(2)); @@ -275,7 +278,7 @@ pub mod pallet { >::take() || { // Proof is not required for early or empty blocks. let number = >::block_number(); - let period = StoragePeriod::::get(); + let period = Self::retention_period(); let target_number = number.saturating_sub(period); target_number.is_zero() || { @@ -304,11 +307,11 @@ pub mod pallet { !T::MaxTransactionSize::get().is_zero(), "MaxTransactionSize must be greater than zero" ); - let default_period = sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(); - let storage_period = GenesisConfig::::default().storage_period; + let default_period = DEFAULT_RETENTION_PERIOD.into(); + let retention_period = GenesisConfig::::default().retention_period; assert_eq!( - storage_period, default_period, - "GenesisConfig.storage_period must match DEFAULT_STORAGE_PERIOD" + retention_period, default_period, + "GenesisConfig.retention_period must match DEFAULT_RETENTION_PERIOD" ); assert!( !T::AuthorizationPeriod::get().is_zero(), @@ -320,8 +323,8 @@ pub mod pallet { #[pallet::call] impl Pallet { /// Index and store data off chain. Minimum data size is 1 byte, maximum is - /// `MaxTransactionSize`. Data will be removed after `StoragePeriod` blocks, unless `renew` - /// is called. + /// `MaxTransactionSize`. Data will be removed after `RetentionPeriod` blocks, unless + /// `renew` is called. /// /// Authorization is required to store data using regular signed/unsigned transactions. /// Regular signed transactions require account authorization (see @@ -419,7 +422,7 @@ pub mod pallet { Ok(().into()) } - /// Check storage proof for block number `block_number() - StoragePeriod`. If such a block + /// Check storage proof for block number `block_number() - RetentionPeriod`. If such a block /// does not exist, the proof is expected to be `None`. /// /// ## Complexity @@ -437,7 +440,7 @@ pub mod pallet { // Get the target block metadata. let number = >::block_number(); - let period = StoragePeriod::::get(); + let period = Self::retention_period(); let target_number = number.saturating_sub(period); ensure!(!target_number.is_zero(), Error::::UnexpectedProof); let transactions = @@ -640,10 +643,13 @@ pub mod pallet { /// Storage fee per transaction. pub type EntryFee = StorageValue<_, BalanceOf>; - /// Storage period for data in blocks. Should match `sp_storage_proof::DEFAULT_STORAGE_PERIOD` - /// for block authoring. + /// Number of blocks for which stored data must be retained. + /// + /// Data older than `RetentionPeriod` blocks is eligible for removal unless it + /// has been explicitly renewed. Validators are required to prove possession of + /// data corresponding to block `N - RetentionPeriod` when producing block `N`. #[pallet::storage] - pub type StoragePeriod = StorageValue<_, BlockNumberFor, ValueQuery>; + pub type RetentionPeriod = StorageValue<_, BlockNumberFor, ValueQuery>; // Intermediates #[pallet::storage] @@ -658,7 +664,7 @@ pub mod pallet { pub struct GenesisConfig { pub byte_fee: BalanceOf, pub entry_fee: BalanceOf, - pub storage_period: BlockNumberFor, + pub retention_period: BlockNumberFor, } impl Default for GenesisConfig { @@ -666,7 +672,7 @@ pub mod pallet { Self { byte_fee: 10u32.into(), entry_fee: 1000u32.into(), - storage_period: sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD.into(), + retention_period: DEFAULT_RETENTION_PERIOD.into(), } } } @@ -676,7 +682,7 @@ pub mod pallet { fn build(&self) { ByteFee::::put(self.byte_fee); EntryFee::::put(self.entry_fee); - StoragePeriod::::put(self.storage_period); + RetentionPeriod::::put(self.retention_period); } } @@ -867,6 +873,11 @@ pub mod pallet { Self::check_signed(who, call, CheckContext::PreDispatch).map(|_| ()) } + /// Get RetentionPeriod storage information from the outside of this pallet. + pub fn retention_period() -> BlockNumberFor { + RetentionPeriod::::get() + } + /// Returns `true` if a blob of the given size can be stored. fn data_size_ok(size: usize) -> bool { (size > 0) && (size <= T::MaxTransactionSize::get() as usize) diff --git a/pallets/transaction-storage/src/mock.rs b/pallets/transaction-storage/src/mock.rs index 479f92ce1..96afcbadf 100644 --- a/pallets/transaction-storage/src/mock.rs +++ b/pallets/transaction-storage/src/mock.rs @@ -71,7 +71,7 @@ pub fn new_test_ext() -> TestExternalities { let t = RuntimeGenesisConfig { system: Default::default(), transaction_storage: pallet_transaction_storage::GenesisConfig:: { - storage_period: 10, + retention_period: 10, byte_fee: 2, entry_fee: 200, }, From 263de3c369844b31305db1e9617c839f49a03d3b Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 29 Dec 2025 22:12:05 +0100 Subject: [PATCH 15/18] Remove unused constants --- runtime/src/lib.rs | 3 --- runtimes/bulletin-polkadot/src/lib.rs | 2 -- runtimes/bulletin-westend/src/storage.rs | 2 -- 3 files changed, 7 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index b7aff7e1b..5692c0ad2 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -191,9 +191,6 @@ parameter_types! { pub const EquivocationReportPeriodInBlocks: u64 = EquivocationReportPeriodInEpochs::get() * (EPOCH_DURATION_IN_BLOCKS as u64); - - // This currently _must_ be set to DEFAULT_STORAGE_PERIOD - pub const StoragePeriod: BlockNumber = sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD; pub const AuthorizationPeriod: BlockNumber = 7 * DAYS; pub const StoreRenewPriority: TransactionPriority = RemoveExpiredAuthorizationPriority::get() - 1; pub const StoreRenewLongevity: TransactionLongevity = DAYS as TransactionLongevity; diff --git a/runtimes/bulletin-polkadot/src/lib.rs b/runtimes/bulletin-polkadot/src/lib.rs index 12f45b73c..0afd30182 100644 --- a/runtimes/bulletin-polkadot/src/lib.rs +++ b/runtimes/bulletin-polkadot/src/lib.rs @@ -193,8 +193,6 @@ parameter_types! { EquivocationReportPeriodInEpochs::get() * (EPOCH_DURATION_IN_BLOCKS as u64); - // This currently _must_ be set to DEFAULT_STORAGE_PERIOD - pub const StoragePeriod: BlockNumber = sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD; pub const AuthorizationPeriod: BlockNumber = 7 * DAYS; pub const StoreRenewPriority: TransactionPriority = RemoveExpiredAuthorizationPriority::get() - 1; pub const StoreRenewLongevity: TransactionLongevity = DAYS as TransactionLongevity; diff --git a/runtimes/bulletin-westend/src/storage.rs b/runtimes/bulletin-westend/src/storage.rs index c409b5f00..900fe541a 100644 --- a/runtimes/bulletin-westend/src/storage.rs +++ b/runtimes/bulletin-westend/src/storage.rs @@ -27,8 +27,6 @@ use sp_runtime::transaction_validity::{TransactionLongevity, TransactionPriority use testnet_parachains_constants::westend::locations::PeopleLocation; parameter_types! { - // This currently _must_ be set to DEFAULT_STORAGE_PERIOD - pub const StoragePeriod: crate::BlockNumber = sp_transaction_storage_proof::DEFAULT_STORAGE_PERIOD; pub const AuthorizationPeriod: crate::BlockNumber = 7 * crate::DAYS; // Priorities and longevities used by the transaction storage pallet extrinsics. pub const SudoPriority: TransactionPriority = TransactionPriority::MAX; From 7a1fd6eeb63603cba7b62606352558e861d8fda7 Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 29 Dec 2025 23:09:04 +0100 Subject: [PATCH 16/18] Setup single vs mbm migrations --- runtime/src/lib.rs | 19 ++++++++++++ runtimes/bulletin-polkadot/src/lib.rs | 19 ++++++++++++ runtimes/bulletin-westend/src/lib.rs | 42 ++++++++++++++++++--------- 3 files changed, 67 insertions(+), 13 deletions(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 5692c0ad2..4c92dc0f3 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -247,6 +247,9 @@ impl frame_system::Config for Runtime { /// This is used as an identifier of the chain. 42 is the generic substrate prefix. type SS58Prefix = SS58Prefix; type MaxConsumers = frame_support::traits::ConstU32<16>; + + type SingleBlockMigrations = migrations::SingleBlockMigrations; + type MultiBlockMigrator = migrations::MbmMigrations; } impl pallet_validator_set::Config for Runtime { @@ -637,6 +640,22 @@ pub type Executive = frame_executive::Executive< AllPalletsWithSystem, >; +/// The runtime migrations per release. +#[allow(deprecated, missing_docs)] +pub mod migrations { + /// Unreleased migrations. Add new ones here: + pub type Unreleased = (); + + /// Migrations/checks that do not need to be versioned and can run on every update. + pub type Permanent = (); + + /// All single block migrations that will run on the next runtime upgrade. + pub type SingleBlockMigrations = (Unreleased, Permanent); + + /// MBM migrations to apply on runtime upgrade. + pub type MbmMigrations = (); +} + #[cfg(feature = "runtime-benchmarks")] mod benches { frame_benchmarking::define_benchmarks!( diff --git a/runtimes/bulletin-polkadot/src/lib.rs b/runtimes/bulletin-polkadot/src/lib.rs index 0afd30182..02d2fbb8a 100644 --- a/runtimes/bulletin-polkadot/src/lib.rs +++ b/runtimes/bulletin-polkadot/src/lib.rs @@ -252,6 +252,9 @@ impl frame_system::Config for Runtime { /// Weight information for the extrinsics of this pallet. type SystemWeightInfo = weights::frame_system::WeightInfo; type ExtensionsWeightInfo = weights::frame_system_extensions::WeightInfo; + + type SingleBlockMigrations = migrations::SingleBlockMigrations; + type MultiBlockMigrator = migrations::MbmMigrations; } impl pallet_validator_set::Config for Runtime { @@ -714,6 +717,22 @@ pub type Executive = frame_executive::Executive< AllPalletsWithSystem, >; +/// The runtime migrations per release. +#[allow(deprecated, missing_docs)] +pub mod migrations { + /// Unreleased migrations. Add new ones here: + pub type Unreleased = (); + + /// Migrations/checks that do not need to be versioned and can run on every update. + pub type Permanent = (); + + /// All single block migrations that will run on the next runtime upgrade. + pub type SingleBlockMigrations = (Unreleased, Permanent); + + /// MBM migrations to apply on runtime upgrade. + pub type MbmMigrations = (); +} + #[cfg(feature = "runtime-benchmarks")] mod benches { use super::*; diff --git a/runtimes/bulletin-westend/src/lib.rs b/runtimes/bulletin-westend/src/lib.rs index 46972ec4f..2b53b57fb 100644 --- a/runtimes/bulletin-westend/src/lib.rs +++ b/runtimes/bulletin-westend/src/lib.rs @@ -122,19 +122,32 @@ pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; -/// Migrations to apply on runtime upgrade. -pub type Migrations = ( - pallet_collator_selection::migration::v2::MigrationToV2, - cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, - cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5, - pallet_session::migrations::v1::MigrateV0ToV1< - Runtime, - pallet_session::migrations::v1::InitOffenceSeverity, - >, - // permanent - pallet_xcm::migration::MigrateToLatestXcmVersion, - cumulus_pallet_aura_ext::migration::MigrateV0ToV1, -); +/// The runtime migrations per release. +#[allow(deprecated, missing_docs)] +pub mod migrations { + use super::*; + + /// Unreleased migrations. Add new ones here: + pub type Unreleased = ( + pallet_collator_selection::migration::v2::MigrationToV2, + cumulus_pallet_xcmp_queue::migration::v4::MigrationToV4, + cumulus_pallet_xcmp_queue::migration::v5::MigrateV4ToV5, + pallet_session::migrations::v1::MigrateV0ToV1< + Runtime, + pallet_session::migrations::v1::InitOffenceSeverity, + >, + cumulus_pallet_aura_ext::migration::MigrateV0ToV1, + ); + + /// Migrations/checks that do not need to be versioned and can run on every update. + pub type Permanent = (pallet_xcm::migration::MigrateToLatestXcmVersion,); + + /// All single block migrations that will run on the next runtime upgrade. + pub type SingleBlockMigrations = (Unreleased, Permanent); + + /// MBM migrations to apply on runtime upgrade. + pub type MbmMigrations = (); +} /// Executive: handles dispatch to the various modules. #[allow(deprecated)] @@ -227,6 +240,9 @@ impl frame_system::Config for Runtime { /// The action to take on a Runtime Upgrade type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; type MaxConsumers = ConstU32<16>; + + type SingleBlockMigrations = migrations::SingleBlockMigrations; + type MultiBlockMigrator = migrations::MbmMigrations; } impl cumulus_pallet_weight_reclaim::Config for Runtime { From c65c3f5816db310c40cdc57e584077bba8a609bf Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Mon, 29 Dec 2025 23:54:25 +0100 Subject: [PATCH 17/18] Add migration `SetRetentionPeriodIfZero` to ensure RetentionPeriod is non-zero for live solochain --- pallets/transaction-storage/src/lib.rs | 9 +++- pallets/transaction-storage/src/migrations.rs | 48 +++++++++++++++++++ runtime/src/lib.rs | 7 ++- runtimes/bulletin-polkadot/src/lib.rs | 7 ++- runtimes/bulletin-westend/src/lib.rs | 8 +++- 5 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 pallets/transaction-storage/src/migrations.rs diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index dc544e78f..dc85804c8 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -27,6 +27,7 @@ mod benchmarking; pub mod weights; +pub mod migrations; #[cfg(test)] mod mock; #[cfg(test)] @@ -37,7 +38,10 @@ use core::fmt::Debug; use polkadot_sdk_frame::{ deps::{sp_core::sp_std::prelude::*, *}, prelude::*, - traits::fungible::{Balanced, Credit, Inspect, Mutate, MutateHold}, + traits::{ + fungible::{Balanced, Credit, Inspect, Mutate, MutateHold}, + parameter_types, + }, }; use sp_transaction_storage_proof::{ encode_index, num_chunks, random_chunk, ChunkIndex, InherentError, TransactionStorageProof, @@ -57,6 +61,9 @@ const LOG_TARGET: &str = "runtime::transaction-storage"; /// Default retention period for data (in blocks). pub const DEFAULT_RETENTION_PERIOD: u32 = 100800; +parameter_types! { + pub const DefaultRetentionPeriod: u32 = DEFAULT_RETENTION_PERIOD; +} // TODO: https://github.com/paritytech/polkadot-bulletin-chain/issues/139 - Clarify purpose of allocator limits and decide whether to remove or use these constants. /// Maximum bytes that can be stored in one transaction. diff --git a/pallets/transaction-storage/src/migrations.rs b/pallets/transaction-storage/src/migrations.rs new file mode 100644 index 000000000..c6d968643 --- /dev/null +++ b/pallets/transaction-storage/src/migrations.rs @@ -0,0 +1,48 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{Config, RetentionPeriod, LOG_TARGET}; +use core::marker::PhantomData; +use polkadot_sdk_frame::{ + prelude::{BlockNumberFor, Weight}, + traits::{Get, OnRuntimeUpgrade, Zero}, +}; + +/// Runtime migration that sets the `RetentionPeriod` storage item to a +/// non-zero `NewValue` value **only if it is currently zero**. +/// +/// Idempotent migration: safe to run multiple times +pub struct SetRetentionPeriodIfZero(PhantomData<(T, NewValue)>); +impl>> OnRuntimeUpgrade + for SetRetentionPeriodIfZero +{ + fn on_runtime_upgrade() -> Weight { + let mut weight = T::DbWeight::get().reads(1); + + // If zero, let's reset. + if RetentionPeriod::::get().is_zero() { + RetentionPeriod::::set(NewValue::get()); + weight.saturating_accrue(T::DbWeight::get().writes(1)); + + tracing::warn!( + target: LOG_TARGET, + "[SetRetentionPeriodIfZero] RetentionPeriod was zero; set to {:?}", + NewValue::get() + ); + } + + weight + } +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 4c92dc0f3..43f61e9d0 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -647,7 +647,12 @@ pub mod migrations { pub type Unreleased = (); /// Migrations/checks that do not need to be versioned and can run on every update. - pub type Permanent = (); + pub type Permanent = ( + pallet_transaction_storage::migrations::SetRetentionPeriodIfZero< + crate::Runtime, + pallet_transaction_storage::DefaultRetentionPeriod, + >, + ); /// All single block migrations that will run on the next runtime upgrade. pub type SingleBlockMigrations = (Unreleased, Permanent); diff --git a/runtimes/bulletin-polkadot/src/lib.rs b/runtimes/bulletin-polkadot/src/lib.rs index 02d2fbb8a..03936301d 100644 --- a/runtimes/bulletin-polkadot/src/lib.rs +++ b/runtimes/bulletin-polkadot/src/lib.rs @@ -724,7 +724,12 @@ pub mod migrations { pub type Unreleased = (); /// Migrations/checks that do not need to be versioned and can run on every update. - pub type Permanent = (); + pub type Permanent = ( + pallet_transaction_storage::migrations::SetRetentionPeriodIfZero< + crate::Runtime, + pallet_transaction_storage::DefaultRetentionPeriod, + >, + ); /// All single block migrations that will run on the next runtime upgrade. pub type SingleBlockMigrations = (Unreleased, Permanent); diff --git a/runtimes/bulletin-westend/src/lib.rs b/runtimes/bulletin-westend/src/lib.rs index 2b53b57fb..0b1e427c9 100644 --- a/runtimes/bulletin-westend/src/lib.rs +++ b/runtimes/bulletin-westend/src/lib.rs @@ -140,7 +140,13 @@ pub mod migrations { ); /// Migrations/checks that do not need to be versioned and can run on every update. - pub type Permanent = (pallet_xcm::migration::MigrateToLatestXcmVersion,); + pub type Permanent = ( + pallet_xcm::migration::MigrateToLatestXcmVersion, + pallet_transaction_storage::migrations::SetRetentionPeriodIfZero< + Runtime, + pallet_transaction_storage::DefaultRetentionPeriod, + >, + ); /// All single block migrations that will run on the next runtime upgrade. pub type SingleBlockMigrations = (Unreleased, Permanent); From 74ec72da7017296e4d681b6fc3ad9a0ad8590f4f Mon Sep 17 00:00:00 2001 From: Branislav Kontur Date: Tue, 30 Dec 2025 00:46:57 +0100 Subject: [PATCH 18/18] Add post-upgrade check --- pallets/transaction-storage/src/lib.rs | 2 ++ pallets/transaction-storage/src/migrations.rs | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/pallets/transaction-storage/src/lib.rs b/pallets/transaction-storage/src/lib.rs index dc85804c8..a641aed72 100644 --- a/pallets/transaction-storage/src/lib.rs +++ b/pallets/transaction-storage/src/lib.rs @@ -24,6 +24,8 @@ // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] +extern crate alloc; + mod benchmarking; pub mod weights; diff --git a/pallets/transaction-storage/src/migrations.rs b/pallets/transaction-storage/src/migrations.rs index c6d968643..69289eb75 100644 --- a/pallets/transaction-storage/src/migrations.rs +++ b/pallets/transaction-storage/src/migrations.rs @@ -38,11 +38,24 @@ impl>> OnRuntimeUpgrade tracing::warn!( target: LOG_TARGET, - "[SetRetentionPeriodIfZero] RetentionPeriod was zero; set to {:?}", - NewValue::get() + new_value = ?NewValue::get(), + "[SetRetentionPeriodIfZero] RetentionPeriod was zero, resetting to:", ); } weight } + + #[cfg(feature = "try-runtime")] + fn post_upgrade( + _state: alloc::vec::Vec, + ) -> Result<(), polkadot_sdk_frame::deps::sp_runtime::DispatchError> { + polkadot_sdk_frame::prelude::ensure!( + !RetentionPeriod::::get().is_zero(), + "must be migrate to the `NewValue`." + ); + + tracing::info!(target: LOG_TARGET, "SetRetentionPeriodIfZero is OK!"); + Ok(()) + } }