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

Add transaction tag for fraud proof and re-define fraud proof priority #2617

Merged
merged 4 commits into from
Mar 28, 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
90 changes: 64 additions & 26 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ use sp_domains_fraud_proof::verification::{
verify_invalid_transfers_fraud_proof, verify_valid_bundle_fraud_proof,
};
use sp_runtime::traits::{BlockNumberProvider, CheckedSub, Hash, Header, One, Zero};
use sp_runtime::transaction_validity::TransactionPriority;
use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating};
pub use staking::OperatorConfig;
use subspace_core_primitives::{BlockHash, PotOutput, SlotNumber, U256};
Expand Down Expand Up @@ -124,6 +125,12 @@ pub(crate) struct ElectionVerificationParams<Balance> {
total_domain_stake: Balance,
}

#[derive(Debug, Decode, Encode, TypeInfo, PartialEq, Eq, Clone)]
pub(crate) enum FraudProofTag {
BadER(DomainId),
BundleEquivocation(OperatorId),
}

pub type DomainBlockNumberFor<T> = <<T as Config>::DomainHeader as Header>::Number;
pub type DomainHashingFor<T> = <<T as Config>::DomainHeader as Header>::Hashing;
pub type ReceiptHashFor<T> = <<T as Config>::DomainHeader as Header>::Hash;
Expand Down Expand Up @@ -1457,17 +1464,6 @@ mod pallet {
}
}

/// Constructs a `TransactionValidity` with pallet-executor specific defaults.
fn unsigned_validity(prefix: &'static str, tag: impl Encode) -> TransactionValidity {
ValidTransaction::with_tag_prefix(prefix)
.priority(TransactionPriority::MAX)
.and_provides(tag)
.longevity(TransactionLongevity::MAX)
// We need this extrinsic to be propagated to the farmer nodes.
.propagate(true)
.build()
}

#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
type Call = Call<T>;
Expand All @@ -1483,6 +1479,7 @@ mod pallet {
.map_err(|_| InvalidTransaction::Call.into())
}),
Call::submit_fraud_proof { fraud_proof } => Self::validate_fraud_proof(fraud_proof)
.map(|_| ())
.map_err(|_| InvalidTransaction::Call.into()),
_ => Err(InvalidTransaction::Call.into()),
}
Expand Down Expand Up @@ -1539,7 +1536,9 @@ mod pallet {
}

ValidTransaction::with_tag_prefix("SubspaceSubmitBundle")
.priority(TransactionPriority::MAX)
// Bundle have a bit higher priority than normal extrinsic but must less than
// fraud proof
.priority(1)
.longevity(T::ConfirmationDepthK::get().try_into().unwrap_or_else(|_| {
panic!("Block number always fits in TransactionLongevity; qed")
}))
Expand All @@ -1548,16 +1547,24 @@ mod pallet {
.build()
}
Call::submit_fraud_proof { fraud_proof } => {
if let Err(e) = Self::validate_fraud_proof(fraud_proof) {
log::warn!(
target: "runtime::domains",
"Bad fraud proof {fraud_proof:?}, error: {e:?}",
);
return InvalidTransactionCode::FraudProof.into();
}

// TODO: proper tag value.
unsigned_validity("SubspaceSubmitFraudProof", fraud_proof)
let (tag, priority) = match Self::validate_fraud_proof(fraud_proof) {
Err(e) => {
log::warn!(
target: "runtime::domains",
"Bad fraud proof {fraud_proof:?}, error: {e:?}",
);
return InvalidTransactionCode::FraudProof.into();
}
Ok(tp) => tp,
};

ValidTransaction::with_tag_prefix("SubspaceSubmitFraudProof")
.priority(priority)
.and_provides(tag)
.longevity(TransactionLongevity::MAX)
// We need this extrinsic to be propagated to the farmer nodes.
.propagate(true)
.build()
}

_ => InvalidTransaction::Call.into(),
Expand Down Expand Up @@ -1811,11 +1818,14 @@ impl<T: Config> Pallet<T> {

fn validate_fraud_proof(
fraud_proof: &FraudProof<BlockNumberFor<T>, T::Hash, T::DomainHeader>,
) -> Result<(), FraudProofError> {
if let Some(bad_receipt_hash) = fraud_proof.targeted_bad_receipt_hash() {
) -> Result<(FraudProofTag, TransactionPriority), FraudProofError> {
let tag_and_priority = if let Some(bad_receipt_hash) =
fraud_proof.targeted_bad_receipt_hash()
{
let bad_receipt = BlockTreeNodes::<T>::get(bad_receipt_hash)
.ok_or(FraudProofError::BadReceiptNotFound)?
.execution_receipt;
let domain_block_number = bad_receipt.domain_block_number;

ensure!(
!bad_receipt.domain_block_number.is_zero(),
Expand Down Expand Up @@ -1956,6 +1966,20 @@ impl<T: Config> Pallet<T> {
})?,
_ => return Err(FraudProofError::UnexpectedFraudProof),
}

// The priority of fraud proof is determined by how many blocks left before the bad ER
// is confirmed, the less the more emergency it is, thus give a higher priority.
let block_before_bad_er_confirm = domain_block_number.saturating_sub(
Self::latest_confirmed_domain_block_number(fraud_proof.domain_id()),
);
let priority =
TransactionPriority::MAX - block_before_bad_er_confirm.saturated_into::<u64>();

// Use the domain id as tag thus the consensus node only accept one fraud proof for a
// specific domain at a time
let tag = FraudProofTag::BadER(fraud_proof.domain_id());

(tag, priority)
} else if let Some((bad_operator_id, _)) =
fraud_proof.targeted_bad_operator_and_slot_for_bundle_equivocation()
{
Expand All @@ -1980,9 +2004,23 @@ impl<T: Config> Pallet<T> {

_ => return Err(FraudProofError::UnexpectedFraudProof),
}
}

Ok(())
// Bundle equivacotion fraud proof doesn't target bad ER thus we give it the lowest priority
// compared to other fraud proofs
let priority = TransactionPriority::MAX
- T::BlockTreePruningDepth::get().saturated_into::<u64>()
- 1;

// Use the operator id as tag thus the consensus node only accept one bundle equivacotion fraud proof
// for a specific operator at a time
let tag = FraudProofTag::BundleEquivocation(bad_operator_id);

(tag, priority)
} else {
return Err(FraudProofError::UnexpectedFraudProof);
};

Ok(tag_and_priority)
}

/// Return operators specific election verification params for Proof of Election verification.
Expand Down
4 changes: 4 additions & 0 deletions crates/subspace-service/src/transaction_pool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ where
let slot_probability = self.chain_constants.slot_probability();
let fraud_proof_submit_sink = self.fraud_proof_submit_sink.clone();
async move {
// TODO: after https://github.com/paritytech/polkadot-sdk/issues/3705 is resolved, check if
// there is already a fraud proof with the same tag and higher priority in the tx pool, if so
// drop the incoming fraud proof before validating it.

let uxt_validity = chain_api
.validate_transaction(at, source, uxt.clone())
.await?;
Expand Down
8 changes: 6 additions & 2 deletions domains/pallets/messenger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,9 @@ mod pallet {
));
};
valid_tx_builder
.priority(TransactionPriority::MAX)
// XDM have a bit higher priority than normal extrinsic but must less than
// fraud proof
.priority(1)
.longevity(TransactionLongevity::MAX)
.and_provides((msg.dst_chain_id, msg.channel_id, msg.nonce))
.propagate(true)
Expand All @@ -456,7 +458,9 @@ mod pallet {
));
};
valid_tx_builder
.priority(TransactionPriority::MAX)
// XDM have a bit higher priority than normal extrinsic but must less than
// fraud proof
.priority(1)
.longevity(TransactionLongevity::MAX)
.and_provides((msg.dst_chain_id, msg.channel_id, msg.nonce))
.propagate(true)
Expand Down
Loading