Skip to content

Commit

Permalink
Merge pull request #2258 from subspace/slash_event
Browse files Browse the repository at this point in the history
Add slashed event when an operator is slashed
  • Loading branch information
vedhavyas authored Nov 21, 2023
2 parents 85129f8 + 1dc070e commit ec2cb16
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 21 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

2 changes: 2 additions & 0 deletions crates/pallet-domains/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ frame-support = { version = "4.0.0-dev", default-features = false, git = "https:
frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1" }
log = { version = "0.4.20", default-features = false }
scale-info = { version = "2.7.0", default-features = false, features = ["derive"] }
sp-consensus-slots = { version = "0.10.0-dev", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1" }
sp-core = { version = "21.0.0", default-features = false, git = "https://github.com/subspace/polkadot-sdk", rev = "892bf8e938c6bd2b893d3827d1093cd81baa59a1" }
sp-domains = { version = "0.1.0", default-features = false, path = "../sp-domains" }
sp-domains-fraud-proof = { version = "0.1.0", default-features = false, path = "../sp-domains-fraud-proof" }
Expand Down Expand Up @@ -47,6 +48,7 @@ std = [
"frame-system/std",
"log/std",
"scale-info/std",
"sp-consensus-slots/std",
"sp-core/std",
"sp-domains/std",
"sp-domains-fraud-proof/std",
Expand Down
61 changes: 51 additions & 10 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ mod pallet {
use frame_support::weights::Weight;
use frame_support::{Identity, PalletError};
use frame_system::pallet_prelude::*;
use sp_consensus_slots::Slot;
use sp_core::H256;
use sp_domains::bundle_producer_election::ProofOfElectionError;
use sp_domains::{
Expand Down Expand Up @@ -709,6 +710,17 @@ mod pallet {
BlockTree(BlockTreeError),
}

/// Reason for slashing an operator
#[derive(Clone, Debug, PartialEq, Encode, Decode, TypeInfo)]
pub enum SlashedReason<DomainBlock, ReceiptHash> {
/// Operator produced bad bundle.
InvalidBundle(DomainBlock),
/// Operator submitted bad Execution receipt.
BadExecutionReceipt(ReceiptHash),
/// Operator caused Bundle equivocation
BundleEquivocation(Slot),
}

#[pallet::event]
#[pallet::generate_deposit(pub (super) fn deposit_event)]
pub enum Event<T: Config> {
Expand Down Expand Up @@ -774,6 +786,10 @@ mod pallet {
DomainOperatorAllowListUpdated {
domain_id: DomainId,
},
OperatorSlashed {
operator_id: OperatorId,
reason: SlashedReason<DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
},
}

/// Per-domain state for tx range calculation.
Expand Down Expand Up @@ -849,7 +865,16 @@ mod pallet {
.map_err(Error::<T>::from)?;

do_slash_operators::<T, _>(
confirmed_block_info.invalid_bundle_authors.into_iter(),
confirmed_block_info.invalid_bundle_authors.into_iter().map(
|operator_id| {
(
operator_id,
SlashedReason::InvalidBundle(
confirmed_block_info.domain_block_number,
),
)
},
),
)
.map_err(Error::<T>::from)?;

Expand Down Expand Up @@ -921,10 +946,10 @@ mod pallet {
ensure_none(origin)?;

log::trace!(target: "runtime::domains", "Processing fraud proof: {fraud_proof:?}");
let mut operators_to_slash = BTreeSet::new();
let domain_id = fraud_proof.domain_id();

if let Some(bad_receipt_hash) = fraud_proof.targeted_bad_receipt_hash() {
let mut operators_to_slash = BTreeMap::new();
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())?
Expand Down Expand Up @@ -957,9 +982,12 @@ mod pallet {
execution_receipt.domain_block_hash,
));

// NOTE: the operator id will be deduplicated since we are using `BTreeSet`
// NOTE: the operator id will be deduplicated since we are using `BTreeMap`
// and slashed reason will hold earliest bad execution receipt hash which this
// operator submitted.
operator_ids.into_iter().for_each(|id| {
operators_to_slash.insert(id);
operators_to_slash
.insert(id, SlashedReason::BadExecutionReceipt(receipt_hash));
});

to_prune -= One::one();
Expand All @@ -973,16 +1001,27 @@ mod pallet {
domain_id,
new_head_receipt_number: Some(new_head_receipt_number),
});
} else if let Some(targeted_bad_operator) = fraud_proof.targeted_bad_operator() {
operators_to_slash.insert(targeted_bad_operator);

// Slash bad operators
do_slash_operators::<T, _>(operators_to_slash.into_iter())
.map_err(Error::<T>::from)?;
} else if let Some((targeted_bad_operator, slot)) =
fraud_proof.targeted_bad_operator_and_slot_for_bundle_equivocation()
{
Self::deposit_event(Event::FraudProofProcessed {
domain_id,
new_head_receipt_number: None,
});
}

// Slash bad operators
do_slash_operators::<T, _>(operators_to_slash.into_iter()).map_err(Error::<T>::from)?;
do_slash_operators::<T, _>(
vec![(
targeted_bad_operator,
SlashedReason::BundleEquivocation(slot),
)]
.into_iter(),
)
.map_err(Error::<T>::from)?;
}

SuccessfulFraudProofs::<T>::append(domain_id, fraud_proof.hash());

Expand Down Expand Up @@ -1677,7 +1716,9 @@ impl<T: Config> Pallet<T> {
})?,
_ => return Err(FraudProofError::UnexpectedFraudProof),
}
} else if let Some(bad_operator_id) = fraud_proof.targeted_bad_operator() {
} else if let Some((bad_operator_id, _)) =
fraud_proof.targeted_bad_operator_and_slot_for_bundle_equivocation()
{
let operator =
Operators::<T>::get(bad_operator_id).ok_or(FraudProofError::MissingOperator)?;
match fraud_proof {
Expand Down
30 changes: 23 additions & 7 deletions crates/pallet-domains/src/staking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@ use crate::pallet::{
PendingSlashes, PendingStakingOperationCount, PendingWithdrawals,
};
use crate::staking_epoch::{mint_funds, PendingNominatorUnlock, PendingOperatorSlashInfo};
use crate::{BalanceOf, Config, Event, HoldIdentifier, NominatorId, Pallet};
use crate::{
BalanceOf, Config, DomainBlockNumberFor, Event, HoldIdentifier, NominatorId, Pallet,
ReceiptHashFor, SlashedReason,
};
use codec::{Decode, Encode};
use frame_support::traits::fungible::{Inspect, MutateHold};
use frame_support::traits::tokens::{Fortitude, Preservation};
Expand Down Expand Up @@ -531,10 +534,16 @@ pub(crate) fn do_reward_operators<T: Config>(

/// Freezes the slashed operators and moves the operator to be removed once the domain they are
/// operating finishes the epoch.
pub(crate) fn do_slash_operators<T: Config, Iter: Iterator<Item = OperatorId>>(
operator_ids: Iter,
) -> Result<(), Error> {
for operator_id in operator_ids {
pub(crate) fn do_slash_operators<T: Config, Iter>(operator_ids: Iter) -> Result<(), Error>
where
Iter: Iterator<
Item = (
OperatorId,
SlashedReason<DomainBlockNumberFor<T>, ReceiptHashFor<T>>,
),
>,
{
for (operator_id, reason) in operator_ids {
Operators::<T>::try_mutate(operator_id, |maybe_operator| {
let operator = maybe_operator.as_mut().ok_or(Error::UnknownOperator)?;
let mut pending_slashes =
Expand Down Expand Up @@ -597,6 +606,10 @@ pub(crate) fn do_slash_operators<T: Config, Iter: Iterator<Item = OperatorId>>(
);

PendingSlashes::<T>::insert(operator.current_domain_id, pending_slashes);
Pallet::<T>::deposit_event(Event::OperatorSlashed {
operator_id,
reason,
});
Ok(())
},
)
Expand Down Expand Up @@ -624,7 +637,7 @@ pub(crate) mod tests {
do_unlock_pending_withdrawals, PendingNominatorUnlock,
};
use crate::tests::{new_test_ext, ExistentialDeposit, RuntimeOrigin, Test};
use crate::{BalanceOf, Error, NominatorId};
use crate::{BalanceOf, Error, NominatorId, SlashedReason};
use frame_support::traits::fungible::Mutate;
use frame_support::traits::Currency;
use frame_support::weights::Weight;
Expand Down Expand Up @@ -1485,7 +1498,10 @@ pub(crate) mod tests {
do_nominate_operator::<Test>(operator_id, deposit.0, deposit.1).unwrap();
}

do_slash_operators::<Test, _>(vec![operator_id].into_iter()).unwrap();
do_slash_operators::<Test, _>(
vec![(operator_id, SlashedReason::InvalidBundle(1))].into_iter(),
)
.unwrap();

let domain_stake_summary = DomainStakingSummary::<Test>::get(domain_id).unwrap();
assert!(!domain_stake_summary.next_operators.contains(&operator_id));
Expand Down
11 changes: 7 additions & 4 deletions crates/sp-domains-fraud-proof/src/fraud_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -424,11 +424,14 @@ impl<Number, Hash, DomainHeader: HeaderT> FraudProof<Number, Hash, DomainHeader>
}
}

pub fn targeted_bad_operator(&self) -> Option<OperatorId> {
pub fn targeted_bad_operator_and_slot_for_bundle_equivocation(
&self,
) -> Option<(OperatorId, Slot)> {
match self {
Self::BundleEquivocation(proof) => {
Some(proof.first_header.header.proof_of_election.operator_id)
}
Self::BundleEquivocation(proof) => Some((
proof.first_header.header.proof_of_election.operator_id,
proof.slot,
)),
_ => None,
}
}
Expand Down

0 comments on commit ec2cb16

Please sign in to comment.