From aabb9bf2abe2091f4b663dee0e417700ff65f758 Mon Sep 17 00:00:00 2001 From: Roy Yang Date: Fri, 15 Sep 2023 15:10:44 +1200 Subject: [PATCH] Improved how permissions are defined. Added a unit test for the call filter system. --- state-chain/pallets/cf-witnesser/src/lib.rs | 38 ++----- state-chain/pallets/cf-witnesser/src/mock.rs | 12 +- state-chain/pallets/cf-witnesser/src/tests.rs | 35 ++++++ state-chain/runtime/src/chainflip.rs | 106 +++++++++++++++--- state-chain/runtime/src/lib.rs | 7 +- 5 files changed, 148 insertions(+), 50 deletions(-) diff --git a/state-chain/pallets/cf-witnesser/src/lib.rs b/state-chain/pallets/cf-witnesser/src/lib.rs index 2a014c7af1..8a9463a3f7 100644 --- a/state-chain/pallets/cf-witnesser/src/lib.rs +++ b/state-chain/pallets/cf-witnesser/src/lib.rs @@ -27,8 +27,6 @@ use frame_support::{ }; use sp_std::prelude::*; -/// Contains permissions for different Runtime calls. Only calls allowed here can be dispatched -/// with Witnesser origin. #[derive( codec::Encode, codec::Decode, @@ -40,40 +38,19 @@ use sp_std::prelude::*; Eq, frame_support::RuntimeDebug, )] -pub struct WitnesserCallPermission { - pub system: bool, // system, sessions, timestamp etc. - pub swappings: bool, // Swapping, pools, etc. - pub liquidity: bool, // LP - pub rotation: bool, // Validator, Vaults - pub ingress_egress: bool, // for transfering funds in and out of the chain. - pub broadcast: bool, // for broadcasting messages to other chains. - pub others: bool, // All other pallets -} - -#[derive( - codec::Encode, - codec::Decode, - codec::MaxEncodedLen, - scale_info::TypeInfo, - Copy, - Clone, - PartialEq, - Eq, - frame_support::RuntimeDebug, -)] -pub enum PalletSafeMode { +pub enum PalletSafeMode { CodeGreen, CodeRed, - CodeAmber(WitnesserCallPermission), + CodeAmber(CallPermission), } -impl Default for PalletSafeMode { +impl Default for PalletSafeMode { fn default() -> Self { - ::CODE_GREEN + as SafeMode>::CODE_GREEN } } -impl SafeMode for PalletSafeMode { +impl SafeMode for PalletSafeMode { const CODE_RED: Self = PalletSafeMode::CodeRed; const CODE_GREEN: Self = PalletSafeMode::CodeGreen; } @@ -112,8 +89,11 @@ pub mod pallet { + GetDispatchInfo + WitnessDataExtraction; + /// A struct that contains permissions for different calls. + type CallPermission: Parameter; + /// Safe Mode access. - type SafeMode: Get; + type SafeMode: Get>; /// Filter for dispatching witnessed calls. type CallDispatchFilter: CallDispatchFilter<::RuntimeCall>; diff --git a/state-chain/pallets/cf-witnesser/src/mock.rs b/state-chain/pallets/cf-witnesser/src/mock.rs index 6ddad666ca..bae1d24ca2 100644 --- a/state-chain/pallets/cf-witnesser/src/mock.rs +++ b/state-chain/pallets/cf-witnesser/src/mock.rs @@ -55,12 +55,19 @@ impl system::Config for Test { type MaxConsumers = frame_support::traits::ConstU32<5>; } -impl_mock_runtime_safe_mode! { witnesser: pallet_cf_witness::PalletSafeMode } +impl_mock_runtime_safe_mode! { witnesser: pallet_cf_witness::PalletSafeMode<()> } +parameter_types! { + pub static AllowCall: bool = true; +} pub struct MockCallFilter; impl CallDispatchFilter for MockCallFilter { fn should_dispatch(_call: &RuntimeCall) -> bool { - matches!(MockSafeModeStorage::get().witnesser, pallet_cf_witness::PalletSafeMode::CodeGreen) + match MockSafeModeStorage::get().witnesser { + pallet_cf_witness::PalletSafeMode::CodeGreen => true, + pallet_cf_witness::PalletSafeMode::CodeRed => false, + pallet_cf_witness::PalletSafeMode::CodeAmber(()) => AllowCall::get(), + } } } @@ -69,6 +76,7 @@ impl pallet_cf_witness::Config for Test { type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; type SafeMode = MockRuntimeSafeMode; + type CallPermission = (); type CallDispatchFilter = MockCallFilter; type WeightInfo = (); } diff --git a/state-chain/pallets/cf-witnesser/src/tests.rs b/state-chain/pallets/cf-witnesser/src/tests.rs index 49e4592dc0..5f79ee8a68 100644 --- a/state-chain/pallets/cf-witnesser/src/tests.rs +++ b/state-chain/pallets/cf-witnesser/src/tests.rs @@ -389,3 +389,38 @@ fn test_safe_mode() { assert_eq!(pallet_dummy::Something::::get(), Some(0u32)); }); } + +#[test] +fn safe_mode_code_amber_can_filter_calls() { + new_test_ext().execute_with(|| { + // Block calls via SafeMode::CodeAmber + MockRuntimeSafeMode::set_safe_mode(MockRuntimeSafeMode { + witnesser: PalletSafeMode::CodeAmber(()), + }); + AllowCall::set(false); + + // Sign the call so its ready to be dispatched + let call = Box::new(RuntimeCall::Dummy(pallet_dummy::Call::::increment_value {})); + let current_epoch = MockEpochInfo::epoch_index(); + for s in [ALISSA, BOBSON] { + assert_ok!(Witnesser::witness_at_epoch( + RuntimeOrigin::signed(s), + call.clone(), + current_epoch + )); + } + assert_eq!(WitnessedCallsScheduledForDispatch::::decode_len(), Some(1)); + + // Call is not dispatched because its blocked by the CallDispatchFilter + Witnesser::on_idle(1, Weight::zero().set_ref_time(1_000_000_000_000u64)); + assert!(!WitnessedCallsScheduledForDispatch::::get().is_empty()); + + // Allow the call to pass the filter + AllowCall::set(true); + + // Call should be dispatched now. + Witnesser::on_idle(2, Weight::zero().set_ref_time(1_000_000_000_000u64)); + assert!(WitnessedCallsScheduledForDispatch::::get().is_empty()); + assert_eq!(pallet_dummy::Something::::get(), Some(0u32)); + }); +} diff --git a/state-chain/runtime/src/chainflip.rs b/state-chain/runtime/src/chainflip.rs index dd8bee022c..a5870594b5 100644 --- a/state-chain/runtime/src/chainflip.rs +++ b/state-chain/runtime/src/chainflip.rs @@ -85,7 +85,7 @@ impl_runtime_safe_mode! { pools: pallet_cf_pools::PalletSafeMode, reputation: pallet_cf_reputation::PalletSafeMode, vault: pallet_cf_vaults::PalletSafeMode, - witnesser: pallet_cf_witnesser::PalletSafeMode, + witnesser: pallet_cf_witnesser::PalletSafeMode, broadcast: pallet_cf_broadcast::PalletSafeMode, } struct BackupNodeEmissions; @@ -624,27 +624,101 @@ impl QualifyNode<::ValidatorId> for ValidatorRoleQualifica } } +/// Contains permissions for different Runtime calls. +/// This is done through the SafeMode::CodeAmber of the Witnesser pallet. +/// Only calls allowed here can be dispatched with Witnesser origin. +#[derive( + codec::Encode, + codec::Decode, + codec::MaxEncodedLen, + scale_info::TypeInfo, + Default, + Copy, + Clone, + PartialEq, + Eq, + frame_support::RuntimeDebug, +)] +pub struct WitnesserCallPermission { + // Non-instantiable pallets + pub governance: bool, + pub funding: bool, + pub swapping: bool, + + // Ethereum pallets + pub ethereum_broadcast: bool, + pub ethereum_chain_tracking: bool, + pub ethereum_ingress_egress: bool, + pub ethereum_vault: bool, + + // Polkadot pallets + pub polkadot_broadcast: bool, + pub polkadot_chain_tracking: bool, + pub polkadot_ingress_egress: bool, + pub polkadot_vault: bool, + + // Bitcoin pallets + pub bitcoin_broadcast: bool, + pub bitcoin_chain_tracking: bool, + pub bitcoin_ingress_egress: bool, + pub bitcoin_vault: bool, +} + +impl WitnesserCallPermission { + pub fn allow_all() -> Self { + WitnesserCallPermission { + governance: true, + funding: true, + swapping: true, + ethereum_broadcast: true, + ethereum_chain_tracking: true, + ethereum_ingress_egress: true, + ethereum_vault: true, + polkadot_broadcast: true, + polkadot_chain_tracking: true, + polkadot_ingress_egress: true, + polkadot_vault: true, + bitcoin_broadcast: true, + bitcoin_chain_tracking: true, + bitcoin_ingress_egress: true, + bitcoin_vault: true, + } + } +} + pub struct ChainflipCallFilter; impl CallDispatchFilter for ChainflipCallFilter { fn should_dispatch(call: &RuntimeCall) -> bool { - match >::get() { + match , + >>::get() + { pallet_cf_witnesser::PalletSafeMode::CodeGreen => true, pallet_cf_witnesser::PalletSafeMode::CodeRed => false, pallet_cf_witnesser::PalletSafeMode::CodeAmber(permission) => match call { - RuntimeCall::System(..) | RuntimeCall::Timestamp(..) => permission.system, - RuntimeCall::Swapping(..) | RuntimeCall::LiquidityPools(..) => permission.swappings, - RuntimeCall::LiquidityProvider(..) => permission.liquidity, - RuntimeCall::Validator(..) | - RuntimeCall::EthereumVault(..) | - RuntimeCall::PolkadotVault(..) | - RuntimeCall::BitcoinVault(..) => permission.rotation, - RuntimeCall::EthereumIngressEgress(..) | - RuntimeCall::PolkadotIngressEgress(..) | - RuntimeCall::BitcoinIngressEgress(..) => permission.ingress_egress, - RuntimeCall::EthereumBroadcaster(..) | - RuntimeCall::PolkadotBroadcaster(..) | - RuntimeCall::BitcoinBroadcaster(..) => permission.broadcast, - _ => permission.others, + RuntimeCall::Governance(..) => permission.governance, + RuntimeCall::Funding(..) => permission.funding, + RuntimeCall::Swapping(..) => permission.swapping, + + RuntimeCall::EthereumBroadcaster(..) => permission.ethereum_broadcast, + RuntimeCall::EthereumChainTracking(..) => permission.ethereum_chain_tracking, + RuntimeCall::EthereumIngressEgress(..) => permission.ethereum_ingress_egress, + RuntimeCall::EthereumVault(..) => permission.ethereum_vault, + + RuntimeCall::PolkadotBroadcaster(..) => permission.polkadot_broadcast, + RuntimeCall::PolkadotChainTracking(..) => permission.polkadot_chain_tracking, + RuntimeCall::PolkadotIngressEgress(..) => permission.polkadot_ingress_egress, + RuntimeCall::PolkadotVault(..) => permission.polkadot_vault, + + RuntimeCall::BitcoinBroadcaster(..) => permission.bitcoin_broadcast, + RuntimeCall::BitcoinChainTracking(..) => permission.bitcoin_chain_tracking, + RuntimeCall::BitcoinIngressEgress(..) => permission.bitcoin_ingress_egress, + RuntimeCall::BitcoinVault(..) => permission.bitcoin_vault, + + _ => { + log::warn!("All witnesser calls must be controllable through `WitnesserCallPermission` during SafeMode: CodeAmber. Call: {:?}", call); + false + }, }, } } diff --git a/state-chain/runtime/src/lib.rs b/state-chain/runtime/src/lib.rs index 9c5e762164..79b6cfaf6c 100644 --- a/state-chain/runtime/src/lib.rs +++ b/state-chain/runtime/src/lib.rs @@ -84,12 +84,12 @@ pub use cf_traits::{EpochInfo, QualifyNode, SessionKeysRegistered, SwappingApi}; pub use chainflip::chain_instances::*; use chainflip::{ - epoch_transition::ChainflipEpochTransitions, BroadcastReadyProvider, BtcEnvironment, - BtcVaultTransitionHandler, ChainAddressConverter, ChainflipCallFilter, ChainflipHeartbeat, + all_vaults_rotator::AllVaultRotator, epoch_transition::ChainflipEpochTransitions, + BroadcastReadyProvider, BtcEnvironment, BtcVaultTransitionHandler, ChainAddressConverter, + ChainflipCallFilter, ChainflipHeartbeat, DotEnvironment, DotVaultTransitionHandler, EthEnvironment, EthVaultTransitionHandler, TokenholderGovernanceBroadcaster, }; -use chainflip::{all_vaults_rotator::AllVaultRotator, DotEnvironment, DotVaultTransitionHandler}; use constants::common::*; use pallet_cf_flip::{Bonder, FlipSlasher}; use pallet_cf_vaults::Vault; @@ -520,6 +520,7 @@ impl pallet_cf_witnesser::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; type SafeMode = chainflip::RuntimeSafeMode; + type CallPermission = chainflip::WitnesserCallPermission; type CallDispatchFilter = ChainflipCallFilter; type WeightInfo = pallet_cf_witnesser::weights::PalletWeight; }