Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Domains: Freeze, Unfreeze, and Prune Execution receipt of Domain through Sudo #2896

Merged
merged 1 commit into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
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 @@ -616,10 +616,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 @@ -971,6 +973,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 @@ -713,6 +713,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 @@ -752,6 +757,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 @@ -880,6 +887,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 @@ -977,6 +986,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 @@ -1541,6 +1560,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 @@ -1950,6 +2048,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 @@ -2466,6 +2569,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))
}
Comment on lines +2572 to +2575
Copy link
Member

Choose a reason for hiding this comment

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

The extrinsic weights are not just the database write & read but also a base weight, thus would prefer to have proper benchmarking like submit_fraud_proof, though non-blocker for this PR.


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