Skip to content

Commit

Permalink
Merge pull request #1999 from subspace/fraud_proof/domain_block_extri…
Browse files Browse the repository at this point in the history
…nsic_root
  • Loading branch information
vedhavyas authored Sep 27, 2023
2 parents 2424541 + b700e16 commit a5f37ee
Show file tree
Hide file tree
Showing 27 changed files with 1,269 additions and 195 deletions.
6 changes: 6 additions & 0 deletions Cargo.lock

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

21 changes: 12 additions & 9 deletions crates/pallet-domains/src/block_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

use crate::pallet::StateRoots;
use crate::{
BalanceOf, BlockTree, Config, ConsensusBlockHash, DomainBlockDescendants, DomainBlocks,
BalanceOf, BlockTree, Config, ConsensusBlockInfo, DomainBlockDescendants, DomainBlocks,
ExecutionInbox, ExecutionReceiptOf, HeadReceiptNumber, InboxedBundle,
};
use codec::{Decode, Encode};
Expand Down Expand Up @@ -169,8 +169,8 @@ pub(crate) fn verify_execution_receipt<T: Config>(
);

let excepted_consensus_block_hash =
match ConsensusBlockHash::<T>::get(domain_id, consensus_block_number) {
Some(hash) => hash,
match ConsensusBlockInfo::<T>::get(domain_id, consensus_block_number) {
Some((hash, _)) => hash,
// The `initialize_block` of non-system pallets is skipped in the `validate_transaction`,
// thus the hash of best block, which is recorded in the this pallet's `on_initialize` hook,
// is unavailable at this point.
Expand Down Expand Up @@ -312,7 +312,7 @@ pub(crate) fn process_execution_receipt<T: Config>(
// its receipt's `extrinsics_root` anymore.
let _ = ExecutionInbox::<T>::clear_prefix((domain_id, to_prune), u32::MAX, None);

ConsensusBlockHash::<T>::remove(
ConsensusBlockInfo::<T>::remove(
domain_id,
execution_receipt.consensus_block_number,
);
Expand Down Expand Up @@ -471,8 +471,11 @@ mod tests {
if block_number != 1 {
// `ConsensusBlockHash` should be set to `Some` since last consensus block contains bundle
assert_eq!(
ConsensusBlockHash::<Test>::get(domain_id, block_number - 1),
Some(frame_system::Pallet::<Test>::block_hash(block_number - 1))
ConsensusBlockInfo::<Test>::get(domain_id, block_number - 1),
Some((
frame_system::Pallet::<Test>::block_hash(block_number - 1),
H256::default()
))
);
// ER point to last consensus block should have `NewHead` type
assert_eq!(
Expand Down Expand Up @@ -551,7 +554,7 @@ mod tests {
verify_execution_receipt::<Test>(domain_id, &pruned_receipt),
Error::InvalidExtrinsicsRoots
);
assert!(ConsensusBlockHash::<Test>::get(
assert!(ConsensusBlockInfo::<Test>::get(
domain_id,
pruned_receipt.consensus_block_number,
)
Expand Down Expand Up @@ -757,10 +760,10 @@ mod tests {
verify_execution_receipt::<Test>(domain_id, &future_receipt),
Error::UnavailableConsensusBlockHash
);
ConsensusBlockHash::<Test>::insert(
ConsensusBlockInfo::<Test>::insert(
domain_id,
future_receipt.consensus_block_number,
future_receipt.consensus_block_hash,
(future_receipt.consensus_block_hash, H256::default()),
);

// Return `UnknownParentBlockReceipt` error as its parent receipt is missing from the block tree
Expand Down
159 changes: 126 additions & 33 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,16 @@ use frame_system::offchain::SubmitTransaction;
use frame_system::pallet_prelude::*;
pub use pallet::*;
use scale_info::TypeInfo;
use sp_core::storage::StorageKey;
use sp_core::H256;
use sp_domains::bundle_producer_election::{is_below_threshold, BundleProducerElectionParams};
use sp_domains::fraud_proof::{FraudProof, InvalidTotalRewardsProof};
use sp_domains::verification::StorageProofVerifier;
use sp_domains::verification::{
verify_invalid_domain_extrinsics_root_fraud_proof, verify_invalid_total_rewards_fraud_proof,
};
use sp_domains::{
DomainBlockLimit, DomainId, DomainInstanceData, ExecutionReceipt, OpaqueBundle, OperatorId,
OperatorPublicKey, ProofOfElection, RuntimeId, EMPTY_EXTRINSIC_ROOT,
OperatorPublicKey, ProofOfElection, RuntimeId, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT,
EMPTY_EXTRINSIC_ROOT,
};
use sp_runtime::traits::{BlakeTwo256, CheckedSub, Hash, One, Zero};
use sp_runtime::{RuntimeAppPublic, SaturatedConversion, Saturating};
Expand Down Expand Up @@ -136,7 +138,8 @@ mod pallet {
use frame_support::{Identity, PalletError};
use frame_system::pallet_prelude::*;
use sp_core::H256;
use sp_domains::fraud_proof::FraudProof;
use sp_domains::fraud_proof::{FraudProof, StorageKeys};
use sp_domains::inherents::{InherentError, InherentType, INHERENT_IDENTIFIER};
use sp_domains::transaction::InvalidTransactionCode;
use sp_domains::{
BundleDigest, DomainId, EpochIndex, GenesisDomain, OperatorId, ReceiptHash, RuntimeId,
Expand Down Expand Up @@ -281,6 +284,9 @@ mod pallet {

/// Randomness source.
type Randomness: RandomnessT<Self::Hash, BlockNumberFor<Self>>;

/// Trait impl to fetch storage keys.
type StorageKeys: StorageKeys;
}

#[pallet::pallet]
Expand Down Expand Up @@ -482,16 +488,24 @@ mod pallet {
OptionQuery,
>;

/// The consensus block hash used to verify ER, only store the consensus block hash for a domain
/// The consensus block hash and state root used to verify ER and storage proofs,
/// only store the consensus block hash for a domain
/// if that consensus block contains bundle of the domain, the hash will be pruned when the ER
/// that point to the consensus block is pruned.
///
/// TODO: this storage is unbounded in some cases, see https://github.com/subspace/subspace/issues/1673
/// for more details, this will be fixed once https://github.com/subspace/subspace/issues/1731 is implemented.
#[pallet::storage]
#[pallet::getter(fn consensus_hash)]
pub type ConsensusBlockHash<T: Config> =
StorageDoubleMap<_, Identity, DomainId, Identity, BlockNumberFor<T>, T::Hash, OptionQuery>;
#[pallet::getter(fn consensus_block_info)]
pub type ConsensusBlockInfo<T: Config> = StorageDoubleMap<
_,
Identity,
DomainId,
Identity,
BlockNumberFor<T>,
(T::Hash, T::Hash),
OptionQuery,
>;

/// A set of `BundleDigest` from all bundles that successfully submitted to the consensus block,
/// these bundles will be used to construct the domain block and `ExecutionInbox` is used to:
Expand Down Expand Up @@ -580,12 +594,12 @@ mod pallet {
ChallengingGenesisReceipt,
/// The descendants of the fraudulent ER is not pruned
DescendantsOfFraudulentERNotPruned,
/// Proof of total rewards is invalid.
InvalidTotalRewardsProof,
/// Invalid fraud proof since total rewards are not mismatched.
InvalidTotalRewardsFraudProof,
/// Invalid state root.
FailedToDecodeDomainBlockHash,
InvalidTotalRewardsFraudProof(sp_domains::verification::VerificationError),
/// Missing state root for a given consensus block
MissingConsensusStateRoot,
/// Invalid domain extrinsic fraud proof
InvalidExtrinsicRootFraudProof(sp_domains::verification::VerificationError),
}

impl<T> From<FraudProofError> for Error<T> {
Expand Down Expand Up @@ -1117,6 +1131,27 @@ mod pallet {

Ok(())
}

/// Submit parent state root to the blockchain.
#[pallet::call_index(11)]
#[pallet::weight((Weight::from_all(10_000), DispatchClass::Mandatory, Pays::No))]
pub fn store_parent_state_root(
origin: OriginFor<T>,
parent_state_root: T::Hash,
) -> DispatchResult {
ensure_none(origin)?;
let block_number = frame_system::Pallet::<T>::block_number();
let parent_number = block_number - One::one();

let domains_ids = DomainRegistry::<T>::iter_keys().collect::<Vec<DomainId>>();
for domain_id in domains_ids {
if let Some(info) = ConsensusBlockInfo::<T>::get(domain_id, parent_number) {
let info = (info.0, parent_state_root);
ConsensusBlockInfo::<T>::insert(domain_id, parent_number, info);
}
}
Ok(())
}
}

#[pallet::genesis_config]
Expand Down Expand Up @@ -1207,7 +1242,11 @@ mod pallet {
let parent_number = block_number - One::one();
let parent_hash = frame_system::Pallet::<T>::block_hash(parent_number);
for (domain_id, _) in SuccessfulBundles::<T>::drain() {
ConsensusBlockHash::<T>::insert(domain_id, parent_number, parent_hash);
ConsensusBlockInfo::<T>::insert(
domain_id,
parent_number,
(parent_hash, T::Hash::default()),
);
}

Weight::zero()
Expand All @@ -1229,6 +1268,42 @@ mod pallet {
.build()
}

#[pallet::inherent]
impl<T: Config> ProvideInherent for Pallet<T> {
type Call = Call<T>;
type Error = InherentError;
const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER;

fn create_inherent(data: &InherentData) -> Option<Self::Call> {
let inherent_data = data
.get_data::<InherentType<T::Hash>>(&INHERENT_IDENTIFIER)
.expect("Domains inherent data not correctly encoded")
.expect("Domains inherent data must be provided");

let parent_state_root = inherent_data.parent_state_root;
Some(Call::store_parent_state_root { parent_state_root })
}

fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
if let Call::store_parent_state_root { parent_state_root } = call {
let inherent_data = data
.get_data::<InherentType<T::Hash>>(&INHERENT_IDENTIFIER)
.expect("Domains inherent data not correctly encoded")
.expect("Domains inherent data must be provided");

if parent_state_root != &inherent_data.parent_state_root {
return Err(InherentError::IncorrectParentStateRoot);
}
}

Ok(())
}

fn is_inherent(call: &Self::Call) -> bool {
matches!(call, Call::store_parent_state_root { .. })
}
}

#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
type Call = Call<T>;
Expand All @@ -1238,6 +1313,7 @@ mod pallet {
.map_err(|_| InvalidTransaction::Call.into()),
Call::submit_fraud_proof { fraud_proof } => Self::validate_fraud_proof(fraud_proof)
.map_err(|_| InvalidTransaction::Call.into()),
Call::store_parent_state_root { .. } => Ok(()),
_ => Err(InvalidTransaction::Call.into()),
}
}
Expand Down Expand Up @@ -1278,6 +1354,13 @@ mod pallet {
// TODO: proper tag value.
unsigned_validity("SubspaceSubmitFraudProof", fraud_proof)
}
Call::store_parent_state_root { .. } => {
ValidTransaction::with_tag_prefix("Domain Parent State root Inherent")
.priority(TransactionPriority::MAX)
.longevity(0)
.propagate(false)
.build()
}

_ => InvalidTransaction::Call.into(),
}
Expand Down Expand Up @@ -1494,25 +1577,35 @@ impl<T: Config> Pallet<T> {
FraudProofError::ChallengingGenesisReceipt
);

if let FraudProof::InvalidTotalRewards(InvalidTotalRewardsProof { storage_proof, .. }) =
fraud_proof
{
let state_root = bad_receipt.final_state_root.encode();
let state_root = T::Hash::decode(&mut state_root.as_slice())
.map_err(|_| FraudProofError::FailedToDecodeDomainBlockHash)?;
let storage_key =
StorageKey(sp_domains::fraud_proof::operator_block_rewards_final_key());
let storage_proof = storage_proof.clone();

let total_rewards = StorageProofVerifier::<T::Hashing>::verify_and_get_value::<
BalanceOf<T>,
>(&state_root, storage_proof, storage_key)
.map_err(|_| FraudProofError::InvalidTotalRewardsProof)?;

// if the rewards matches, then this is an invalid fraud proof since rewards must be different.
if bad_receipt.total_rewards == total_rewards {
return Err(FraudProofError::InvalidTotalRewardsFraudProof);
match fraud_proof {
FraudProof::InvalidTotalRewards(InvalidTotalRewardsProof { storage_proof, .. }) => {
verify_invalid_total_rewards_fraud_proof::<
T::Block,
T::DomainNumber,
T::DomainHash,
BalanceOf<T>,
T::Hashing,
>(bad_receipt, storage_proof)
.map_err(FraudProofError::InvalidTotalRewardsFraudProof)?;
}
FraudProof::InvalidExtrinsicsRoot(proof) => {
let consensus_state_root = ConsensusBlockInfo::<T>::get(
proof.domain_id,
bad_receipt.consensus_block_number,
)
.ok_or(FraudProofError::MissingConsensusStateRoot)?
.1;
verify_invalid_domain_extrinsics_root_fraud_proof::<
T::Block,
T::DomainNumber,
T::DomainHash,
BalanceOf<T>,
T::Hashing,
T::StorageKeys,
>(consensus_state_root, bad_receipt, proof)
.map_err(FraudProofError::InvalidExtrinsicRootFraudProof)?;
}
_ => {}
}

Ok(())
Expand Down Expand Up @@ -1691,7 +1784,7 @@ impl<T: Config> Pallet<T> {
}

pub fn extrinsics_shuffling_seed() -> T::Hash {
let seed: &[u8] = b"extrinsics-shuffling-seed";
let seed = DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT;
let (randomness, _) = T::Randomness::random(seed);
randomness
}
Expand Down
Loading

0 comments on commit a5f37ee

Please sign in to comment.