Skip to content

Commit

Permalink
Merge pull request #2896 from subspace/domain_freeze_unfreeze
Browse files Browse the repository at this point in the history
Domains: Freeze, Unfreeze, and Prune Execution receipt of Domain through Sudo
  • Loading branch information
vedhavyas authored Jul 16, 2024
2 parents 586f5e1 + cc6ed8a commit 8a1e2ed
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 9 deletions.
38 changes: 37 additions & 1 deletion crates/pallet-domains/src/block_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -600,10 +600,12 @@ mod tests {
use crate::tests::{
create_dummy_bundle_with_receipts, create_dummy_receipt, extend_block_tree,
extend_block_tree_from_zero, get_block_tree_node_at, new_test_ext_with_extensions,
register_genesis_domain, run_to_block, BlockTreePruningDepth, Test,
register_genesis_domain, run_to_block, BlockTreePruningDepth, Domains, Test,
};
use crate::FrozenDomains;
use frame_support::dispatch::RawOrigin;
use frame_support::{assert_err, assert_ok};
use frame_system::Origin;
use sp_core::H256;
use sp_domains::{BundleDigest, InboxedBundle, InvalidBundleType};

Expand Down Expand Up @@ -955,6 +957,40 @@ mod tests {
});
}

#[test]
fn test_prune_domain_execution_receipt() {
let creator = 0u128;
let operator_id = 1u64;
let mut ext = new_test_ext_with_extensions();
ext.execute_with(|| {
let domain_id = register_genesis_domain(creator, vec![operator_id]);
let _next_receipt = extend_block_tree_from_zero(domain_id, operator_id, 3);
let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);

// freeze domain
assert!(!FrozenDomains::<Test>::get().contains(&domain_id));
Domains::freeze_domain(Origin::<Test>::Root.into(), domain_id).unwrap();
assert!(FrozenDomains::<Test>::get().contains(&domain_id));

// prune execution recept
let head_receipt_hash = BlockTree::<Test>::get(domain_id, head_receipt_number).unwrap();
Domains::prune_domain_execution_receipt(
Origin::<Test>::Root.into(),
domain_id,
head_receipt_hash,
)
.unwrap();
assert_eq!(
HeadReceiptNumber::<Test>::get(domain_id),
head_receipt_number - 1
);

// unfreeze domain
Domains::unfreeze_domain(Origin::<Test>::Root.into(), domain_id).unwrap();
assert!(!FrozenDomains::<Test>::get().contains(&domain_id));
})
}

#[test]
fn test_invalid_receipt() {
let creator = 0u128;
Expand Down
124 changes: 116 additions & 8 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,12 +167,13 @@ pub(crate) type StateRootOf<T> = <<T as frame_system::Config>::Hashing as Hash>:
mod pallet {
#![allow(clippy::large_enum_variant)]

#[cfg(not(feature = "runtime-benchmarks"))]
use crate::block_tree::AcceptedReceiptType;
use crate::block_tree::{
execution_receipt_type, process_execution_receipt, Error as BlockTreeError, ReceiptType,
execution_receipt_type, process_execution_receipt, prune_receipt, Error as BlockTreeError,
ReceiptType,
};
#[cfg(not(feature = "runtime-benchmarks"))]
use crate::block_tree::{prune_receipt, AcceptedReceiptType};
#[cfg(not(feature = "runtime-benchmarks"))]
use crate::bundle_storage_fund::refund_storage_fee;
use crate::bundle_storage_fund::{charge_bundle_storage_fee, Error as BundleStorageFundError};
use crate::domain_registry::{
Expand All @@ -185,13 +186,12 @@ mod pallet {
ScheduledRuntimeUpgrade,
};
#[cfg(not(feature = "runtime-benchmarks"))]
use crate::staking::do_mark_operators_as_slashed;
#[cfg(not(feature = "runtime-benchmarks"))]
use crate::staking::do_reward_operators;
use crate::staking::{
do_deregister_operator, do_nominate_operator, do_register_operator, do_unlock_funds,
do_unlock_nominator, do_withdraw_stake, Deposit, DomainEpoch, Error as StakingError,
Operator, OperatorConfig, SharePrice, StakingSummary, Withdrawal,
do_deregister_operator, do_mark_operators_as_slashed, do_nominate_operator,
do_register_operator, do_unlock_funds, do_unlock_nominator, do_withdraw_stake, Deposit,
DomainEpoch, Error as StakingError, Operator, OperatorConfig, SharePrice, StakingSummary,
Withdrawal,
};
#[cfg(not(feature = "runtime-benchmarks"))]
use crate::staking_epoch::do_slash_operator;
Expand Down Expand Up @@ -707,6 +707,11 @@ mod pallet {
pub type DomainSudoCalls<T: Config> =
StorageMap<_, Identity, DomainId, DomainSudoCall, ValueQuery>;

/// Storage that hold a list of all frozen domains.
/// A frozen domain does not accept the bundles but does accept a fraud proof.
#[pallet::storage]
pub type FrozenDomains<T> = StorageValue<_, BTreeSet<DomainId>, ValueQuery>;

#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
pub enum BundleError {
/// Can not find the operator for given operator id.
Expand Down Expand Up @@ -742,6 +747,8 @@ mod pallet {
SlotSmallerThanPreviousBlockBundle,
/// Equivocated bundle in current block
EquivocatedBundle,
/// Domain is frozen and cannot accept new bundles
DomainFrozen,
}

#[derive(TypeInfo, Encode, Decode, PalletError, Debug, PartialEq)]
Expand Down Expand Up @@ -870,6 +877,8 @@ mod pallet {
DomainSudoCallExists,
/// Invalid Domain sudo call.
InvalidDomainSudoCall,
/// Domain must be frozen before execution receipt can be pruned.
DomainNotFrozen,
}

/// Reason for slashing an operator
Expand Down Expand Up @@ -967,6 +976,16 @@ mod pallet {
nominator_id: NominatorId<T>,
amount: BalanceOf<T>,
},
DomainFrozen {
domain_id: DomainId,
},
DomainUnfrozen {
domain_id: DomainId,
},
PrunedExecutionReceipt {
domain_id: DomainId,
new_head_receipt_number: Option<DomainBlockNumberFor<T>>,
},
}

/// Per-domain state for tx range calculation.
Expand Down Expand Up @@ -1531,6 +1550,85 @@ mod pallet {
);
Ok(())
}

/// Freezes a given domain.
/// A frozen domain does not accept new bundles but accepts fraud proofs.
#[pallet::call_index(17)]
#[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
pub fn freeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
ensure_root(origin)?;
FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.insert(domain_id));
Self::deposit_event(Event::DomainFrozen { domain_id });
Ok(())
}

/// Unfreezes a frozen domain.
#[pallet::call_index(18)]
#[pallet::weight(<T as frame_system::Config>::DbWeight::get().reads_writes(0, 1))]
pub fn unfreeze_domain(origin: OriginFor<T>, domain_id: DomainId) -> DispatchResult {
ensure_root(origin)?;
FrozenDomains::<T>::mutate(|frozen_domains| frozen_domains.remove(&domain_id));
Self::deposit_event(Event::DomainUnfrozen { domain_id });
Ok(())
}

/// Prunes a given execution receipt for given frozen domain.
/// This call assumes the execution receipt to be bad and implicitly trusts Sudo
/// to do necessary validation of the ER before dispatching this call.
#[pallet::call_index(19)]
#[pallet::weight(Pallet::<T>::max_prune_domain_execution_receipt())]
pub fn prune_domain_execution_receipt(
origin: OriginFor<T>,
domain_id: DomainId,
bad_receipt_hash: ReceiptHashFor<T>,
) -> DispatchResultWithPostInfo {
ensure_root(origin)?;
ensure!(
FrozenDomains::<T>::get().contains(&domain_id),
Error::<T>::DomainNotFrozen
);

let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
let bad_receipt_number = BlockTreeNodes::<T>::get(bad_receipt_hash)
.ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?
.execution_receipt
.domain_block_number;
// The `head_receipt_number` must greater than or equal to any existing receipt, including
// the bad receipt.
ensure!(
head_receipt_number >= bad_receipt_number,
Error::<T>::from(FraudProofError::BadReceiptNotFound),
);

let mut actual_weight = T::DbWeight::get().reads(3);

// prune the bad ER
let block_tree_node = prune_receipt::<T>(domain_id, bad_receipt_number)
.map_err(Error::<T>::from)?
.ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;

actual_weight = actual_weight.saturating_add(T::WeightInfo::handle_bad_receipt(
(block_tree_node.operator_ids.len() as u32).min(MAX_BUNLDE_PER_BLOCK),
));

do_mark_operators_as_slashed::<T>(
block_tree_node.operator_ids.into_iter(),
SlashedReason::BadExecutionReceipt(bad_receipt_hash),
)
.map_err(Error::<T>::from)?;

// Update the head receipt number to `bad_receipt_number - 1`
let new_head_receipt_number = bad_receipt_number.saturating_sub(One::one());
HeadReceiptNumber::<T>::insert(domain_id, new_head_receipt_number);
actual_weight = actual_weight.saturating_add(T::DbWeight::get().reads_writes(0, 1));

Self::deposit_event(Event::PrunedExecutionReceipt {
domain_id,
new_head_receipt_number: Some(new_head_receipt_number),
});

Ok(Some(actual_weight).into())
}
}

#[pallet::genesis_config]
Expand Down Expand Up @@ -1940,6 +2038,11 @@ impl<T: Config> Pallet<T> {
pre_dispatch: bool,
) -> Result<(), BundleError> {
let domain_id = opaque_bundle.domain_id();
ensure!(
!FrozenDomains::<T>::get().contains(&domain_id),
BundleError::DomainFrozen
);

let operator_id = opaque_bundle.operator_id();
let sealed_header = &opaque_bundle.sealed_header;
let slot_number = opaque_bundle.slot_number();
Expand Down Expand Up @@ -2459,6 +2562,11 @@ impl<T: Config> Pallet<T> {
)
}

pub fn max_prune_domain_execution_receipt() -> Weight {
T::WeightInfo::handle_bad_receipt(MAX_BUNLDE_PER_BLOCK)
.saturating_add(T::DbWeight::get().reads_writes(3, 1))
}

fn actual_epoch_transition_weight(epoch_transition_res: EpochTransitionResult) -> Weight {
let EpochTransitionResult {
rewarded_operator_count,
Expand Down

0 comments on commit 8a1e2ed

Please sign in to comment.