Skip to content
Open
Show file tree
Hide file tree
Changes from 13 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
15 changes: 15 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ pallet-sudo = { version = "47.0.0", default-features = false }
pallet-timestamp = { version = "46.0.0", default-features = false }
pallet-transaction-payment = { version = "47.0.0", default-features = false }
pallet-transaction-payment-rpc-runtime-api = { version = "47.0.0", default-features = false }
pallet-tx-pause = { version = "28.0.0", default-features = false }

pallet-xcm = { version = "27.0.0", default-features = false }
xcm = { version = "23.0.0", package = "staging-xcm", default-features = false }
Expand Down
3 changes: 2 additions & 1 deletion justfile
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ test-zombienet-auto-renew runtime="westend" group="all":
auto_renew_storage::parachain_auto_renew_test \
auto_renew_storage::parachain_auto_renew_many_items_test \
auto_renew_storage::parachain_auto_renew_quota_exhaustion_test \
auto_renew_storage::parachain_auto_renew_authorization_expires_mid_cycle_test)
auto_renew_storage::parachain_auto_renew_authorization_expires_mid_cycle_test \
auto_renew_storage::parachain_pause_renew_filters_dispatch_test)
;;
pruning)
filter_args=(--exact \
Expand Down
4 changes: 4 additions & 0 deletions pallets/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,31 @@ polkadot-sdk-frame = { workspace = true, default-features = false, features = [
] }
pallet-proxy = { workspace = true }
pallet-sudo = { workspace = true }
pallet-tx-pause = { workspace = true }
pallet-utility = { workspace = true }

[features]
default = ["std"]
runtime-benchmarks = [
"pallet-proxy/runtime-benchmarks",
"pallet-sudo/runtime-benchmarks",
"pallet-tx-pause/runtime-benchmarks",
"pallet-utility/runtime-benchmarks",
"polkadot-sdk-frame/runtime-benchmarks",
]
std = [
"codec/std",
"pallet-proxy/std",
"pallet-sudo/std",
"pallet-tx-pause/std",
"pallet-utility/std",
"polkadot-sdk-frame/std",
"scale-info/std",
]
try-runtime = [
"pallet-proxy/try-runtime",
"pallet-sudo/try-runtime",
"pallet-tx-pause/try-runtime",
"pallet-utility/try-runtime",
"polkadot-sdk-frame/try-runtime",
]
36 changes: 36 additions & 0 deletions pallets/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,42 @@ pub fn proxy_inner_calls<T: pallet_proxy::Config>(
}
}

/// `pallet_tx_pause::Config::WhitelistedCalls` impl scoped to the bulletin runtimes:
/// the renew family of `StoragePallet` is the only set of dispatchables that can be
/// paused; everything else returns `true` (= unpausable).
///
/// `StoragePallet` is the runtime-side pallet type — typically the alias generated by
/// `construct_runtime!` for `pallet_bulletin_transaction_storage`. We source its name via
/// `PalletInfoAccess` so a rename in `construct_runtime!` propagates automatically instead
/// of silently leaving renews unpausable.
pub struct TxPauseWhitelist<T, StoragePallet>(core::marker::PhantomData<(T, StoragePallet)>);

impl<T, StoragePallet> frame_support::traits::Contains<pallet_tx_pause::RuntimeCallNameOf<T>>
for TxPauseWhitelist<T, StoragePallet>
where
T: pallet_tx_pause::Config,
StoragePallet: frame_support::traits::PalletInfoAccess,
{
fn contains(name: &pallet_tx_pause::RuntimeCallNameOf<T>) -> bool {
// Bench harness constructs synthetic names that wouldn't pass our inverted
// whitelist; let everything through so `pause`/`unpause` benches can run.
#[cfg(feature = "runtime-benchmarks")]
{
let _ = name;
false
}
#[cfg(not(feature = "runtime-benchmarks"))]
{
let is_renew_call = name.0.as_slice() == StoragePallet::name().as_bytes() &&
matches!(
name.1.as_slice(),
b"renew" | b"force_renew" | b"enable_auto_renew" | b"disable_auto_renew"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

this is not nice and it is error prone, doesnt pallet_tx_pause support better filtering?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

good point, is this better 3339e27 ?

);
!is_renew_call
}
}
}

/// Extract inner calls from a sudo call variant.
pub fn sudo_inner_calls<T: pallet_sudo::Config>(
call: &pallet_sudo::Call<T>,
Expand Down
4 changes: 4 additions & 0 deletions runtimes/bulletin-paseo/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pallet-session = { workspace = true }
pallet-skip-feeless-payment = { workspace = true }
pallet-sudo = { workspace = true }
pallet-timestamp = { workspace = true }
pallet-tx-pause = { workspace = true }
pallet-utility = { workspace = true }
pallet-transaction-payment = { workspace = true }
pallet-transaction-payment-rpc-runtime-api = { workspace = true }
Expand Down Expand Up @@ -130,6 +131,7 @@ std = [
"pallet-timestamp/std",
"pallet-transaction-payment-rpc-runtime-api/std",
"pallet-transaction-payment/std",
"pallet-tx-pause/std",
"pallet-utility/std",
"pallet-xcm-benchmarks?/std",
"pallet-xcm/std",
Expand Down Expand Up @@ -186,6 +188,7 @@ runtime-benchmarks = [
"pallet-sudo/runtime-benchmarks",
"pallet-timestamp/runtime-benchmarks",
"pallet-transaction-payment/runtime-benchmarks",
"pallet-tx-pause/runtime-benchmarks",
"pallet-utility/runtime-benchmarks",
"pallet-xcm-benchmarks/runtime-benchmarks",
"pallet-xcm/runtime-benchmarks",
Expand Down Expand Up @@ -222,6 +225,7 @@ try-runtime = [
"pallet-sudo/try-runtime",
"pallet-timestamp/try-runtime",
"pallet-transaction-payment/try-runtime",
"pallet-tx-pause/try-runtime",
"pallet-utility/try-runtime",
"pallet-xcm/try-runtime",
"parachain-info/try-runtime",
Expand Down
21 changes: 21 additions & 0 deletions runtimes/bulletin-paseo/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ parameter_types! {
// Configure FRAME pallets to include in runtime.
#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)]
impl frame_system::Config for Runtime {
type BaseCallFilter = TxPause;
/// The identifier used to distinguish between accounts.
type AccountId = AccountId;
/// The nonce type for storing how many extrinsics an account has signed.
Expand Down Expand Up @@ -541,6 +542,23 @@ impl pallet_utility::Config for Runtime {
type WeightInfo = weights::pallet_utility::WeightInfo<Runtime>;
}

parameter_types! {
// Bounds the byte length of pallet and call names tracked in `pallet_tx_pause::PausedCalls`.
// Names exceeding this length cannot be encoded into `RuntimeCallNameOf` and are therefore
// treated as paused by the pallet's `Contains<RuntimeCall>` filter.
pub const TxPauseMaxNameLen: u32 = 256;
}

impl pallet_tx_pause::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type PauseOrigin = EnsureRoot<AccountId>;
type UnpauseOrigin = EnsureRoot<AccountId>;
type WhitelistedCalls = bulletin_pallets_common::TxPauseWhitelist<Runtime, TransactionStorage>;
type MaxNameLen = TxPauseMaxNameLen;
type WeightInfo = pallet_tx_pause::weights::SubstrateWeight<Runtime>;
}

impl<C> frame_system::offchain::CreateTransactionBase<C> for Runtime
where
RuntimeCall: From<C>,
Expand Down Expand Up @@ -632,6 +650,8 @@ mod runtime {
pub type Utility = pallet_utility;
#[runtime::pallet_index(7)]
pub type MultiBlockMigrations = pallet_migrations;
#[runtime::pallet_index(8)]
pub type TxPause = pallet_tx_pause;

// Monetary stuff.
#[runtime::pallet_index(10)]
Expand Down Expand Up @@ -695,6 +715,7 @@ mod benches {
[pallet_xcm_benchmarks::generic, XcmGeneric]
[cumulus_pallet_weight_reclaim, WeightReclaim]
[pallet_utility, Utility]
[pallet_tx_pause, TxPause]
);
}

Expand Down
148 changes: 146 additions & 2 deletions runtimes/bulletin-paseo/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,22 @@ use bulletin_paseo_runtime::{
xcm_config::{GovernanceLocation, LocationToAccountId},
AllPalletsWithoutSystem, Balances, Block, HopPromotion, Runtime, RuntimeCall, RuntimeEvent,
RuntimeGenesisConfig, RuntimeOrigin, SessionKeys, System, TransactionStorage, TxExtension,
UncheckedExtrinsic,
TxPauseMaxNameLen, UncheckedExtrinsic,
};
use bulletin_transaction_storage_primitives::cids::{calculate_cid, CidConfig, HashingAlgorithm};
use frame_support::{
assert_err, assert_ok, dispatch::GetDispatchInfo, pallet_prelude::Hooks, traits::Get,
assert_err, assert_noop, assert_ok,
dispatch::GetDispatchInfo,
pallet_prelude::Hooks,
traits::{Contains, Get, GetCallMetadata},
BoundedVec,
};
use pallet_bulletin_transaction_storage::{
extension::{AllowanceBasedPriority, ALLOWANCE_PRIORITY_BOOST},
AuthorizationExtent, AuthorizationScope, Call as TxStorageCall, Config as TxStorageConfig,
Origin as TxStorageOrigin, TransactionRef,
};
use pallet_tx_pause::{Error as TxPauseError, RuntimeCallNameOf};
use parachains_common::{AccountId, AuraId, BlockNumber, Hash as PcHash, Signature as PcSignature};
use parachains_runtimes_test_utils::{ExtBuilder, GovernanceOrigin, RuntimeHelper};
use sp_core::{crypto::Ss58Codec, Encode, Pair};
Expand Down Expand Up @@ -2110,3 +2115,142 @@ fn non_sudo_cannot_add_authorizer() {
assert_eq!(applied.map(|_| ()), Err(DispatchError::BadOrigin));
});
}

type TxPauseWhitelist = bulletin_pallets_common::TxPauseWhitelist<Runtime, TransactionStorage>;

const RENEW_CALLS: &[&[u8]] =
&[b"renew", b"force_renew", b"enable_auto_renew", b"disable_auto_renew"];

fn pause_call_name(pallet: &[u8], call: &[u8]) -> RuntimeCallNameOf<Runtime> {
let max = TxPauseMaxNameLen::get() as usize;
(
BoundedVec::try_from(pallet.to_vec())
.unwrap_or_else(|_| panic!("pallet name '{pallet:?}' > MaxNameLen={max}")),
BoundedVec::try_from(call.to_vec())
.unwrap_or_else(|_| panic!("call name '{call:?}' > MaxNameLen={max}")),
)
}

fn sample_renew_call() -> RuntimeCall {
RuntimeCall::TransactionStorage(TxStorageCall::<Runtime>::renew {
entry: TransactionRef::Position { block: 0, index: 0 },
})
}

#[test]
fn tx_pause_renew_call_metadata_fits_in_max_name_len() {
let max = TxPauseMaxNameLen::get() as usize;
let call = sample_renew_call();
let meta = call.get_call_metadata();
assert!(meta.pallet_name.len() <= max, "pallet '{}' > MaxNameLen", meta.pallet_name);
assert!(meta.function_name.len() <= max, "call '{}' > MaxNameLen", meta.function_name);
assert_eq!(meta.pallet_name, "TransactionStorage");
for call in RENEW_CALLS {
assert!(call.len() <= max);
}
}

#[test]
fn tx_pause_whitelist_blocks_every_non_renew_call() {
for pallet in [&b"System"[..], b"Sudo", b"Balances", b"TxPause", b"PolkadotXcm", b"Utility"] {
assert!(
TxPauseWhitelist::contains(&pause_call_name(pallet, b"some_call")),
"pallet {:?} must be whitelisted",
core::str::from_utf8(pallet).unwrap()
);
}
for call in [&b"store"[..], b"add_authorizer", b"apply_block_inherents"] {
assert!(
TxPauseWhitelist::contains(&pause_call_name(b"TransactionStorage", call)),
"TransactionStorage.{} must be whitelisted",
core::str::from_utf8(call).unwrap()
);
}
}

#[test]
fn tx_pause_whitelist_allows_renew_family_to_be_paused() {
for call in RENEW_CALLS {
assert!(
!TxPauseWhitelist::contains(&pause_call_name(b"TransactionStorage", call)),
"TransactionStorage.{} must be pausable",
core::str::from_utf8(call).unwrap()
);
}
}

#[test]
fn tx_pause_pause_renew_via_root_filters_dispatch() {
sp_io::TestExternalities::new(RuntimeGenesisConfig::default().build_storage().unwrap())
.execute_with(|| {
let call = sample_renew_call();
assert!(
<Runtime as frame_system::Config>::BaseCallFilter::contains(&call),
"renew should dispatch when not paused"
);

assert_ok!(pallet_tx_pause::Pallet::<Runtime>::pause(
RuntimeOrigin::root(),
pause_call_name(b"TransactionStorage", b"renew"),
));

assert!(
!<Runtime as frame_system::Config>::BaseCallFilter::contains(&call),
"BaseCallFilter must reject renew after pause"
);

assert_ok!(pallet_tx_pause::Pallet::<Runtime>::unpause(
RuntimeOrigin::root(),
pause_call_name(b"TransactionStorage", b"renew"),
));
assert!(<Runtime as frame_system::Config>::BaseCallFilter::contains(&call));
});
}

#[test]
fn tx_pause_pause_non_renew_is_rejected_as_unpausable() {
sp_io::TestExternalities::new(RuntimeGenesisConfig::default().build_storage().unwrap())
.execute_with(|| {
for (pallet, call) in [
(&b"System"[..], &b"set_storage"[..]),
(b"Sudo", b"sudo"),
(b"Balances", b"transfer_keep_alive"),
(b"TransactionStorage", b"store"),
] {
assert_noop!(
pallet_tx_pause::Pallet::<Runtime>::pause(
RuntimeOrigin::root(),
pause_call_name(pallet, call),
),
TxPauseError::<Runtime>::Unpausable
);
}
});
}

#[test]
fn tx_pause_pause_requires_root_origin() {
sp_io::TestExternalities::new(RuntimeGenesisConfig::default().build_storage().unwrap())
.execute_with(|| {
let signed: RuntimeOrigin =
frame_system::RawOrigin::Signed(AccountId::new([1u8; 32])).into();
assert!(pallet_tx_pause::Pallet::<Runtime>::pause(
signed,
pause_call_name(b"TransactionStorage", b"renew"),
)
.is_err());
});
}

#[test]
fn tx_pause_paused_call_name_round_trip_fits_storage() {
// Belt-and-braces: encode a `RuntimeCallNameOf` for every renew variant so the
// `BoundedVec<u8, MaxNameLen>` conversion can't silently regress if MaxNameLen shrinks.
let max = TxPauseMaxNameLen::get() as usize;
for call in RENEW_CALLS {
let key = pause_call_name(b"TransactionStorage", call);
let encoded = key.encode();
assert!(!encoded.is_empty());
assert!(call.len() <= max);
}
}
Loading
Loading