Skip to content

Commit

Permalink
Merge pull request #2072 from subspace/revamp-invalid-state-proof
Browse files Browse the repository at this point in the history
Revamp invalid state transition proof
  • Loading branch information
NingLin-P authored Oct 19, 2023
2 parents fdc61bb + 1063a3c commit 5d06efe
Show file tree
Hide file tree
Showing 34 changed files with 762 additions and 653 deletions.
4 changes: 4 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions crates/pallet-domains/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ subspace-core-primitives = { version = "0.1.0", default-features = false, path =
subspace-runtime-primitives = { version = "0.1.0", default-features = false, path = "../subspace-runtime-primitives" }

[dev-dependencies]
domain-runtime-primitives = { version = "0.1.0", default-features = false, path = "../../domains/primitives/runtime" }
pallet-balances = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "74509ce016358e7f56ed2825fd75c324f8c22ef9" }
pallet-timestamp = { version = "4.0.0-dev", git = "https://github.com/subspace/polkadot-sdk", rev = "74509ce016358e7f56ed2825fd75c324f8c22ef9" }
sp-externalities = { version = "0.19.0", git = "https://github.com/subspace/polkadot-sdk", rev = "74509ce016358e7f56ed2825fd75c324f8c22ef9" }
Expand Down
2 changes: 1 addition & 1 deletion crates/pallet-domains/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ mod benchmarks {
let mut receipt =
BlockTree::<T>::get::<_, DomainBlockNumberFor<T>>(domain_id, Zero::zero())
.first()
.and_then(DomainBlocks::<T>::get)
.and_then(BlockTreeNodes::<T>::get)
.expect("genesis receipt must exist")
.execution_receipt;
for i in 1..=(block_tree_pruning_depth + 1) {
Expand Down
55 changes: 28 additions & 27 deletions crates/pallet-domains/src/block_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
use crate::pallet::StateRoots;
use crate::{
BalanceOf, BlockTree, Config, ConsensusBlockHash, DomainBlockDescendants, DomainBlockNumberFor,
DomainBlocks, ExecutionInbox, ExecutionReceiptOf, HeadReceiptNumber, InboxedBundleAuthor,
BalanceOf, BlockTree, BlockTreeNodes, Config, ConsensusBlockHash, DomainBlockDescendants,
DomainBlockNumberFor, ExecutionInbox, ExecutionReceiptOf, HeadReceiptNumber,
InboxedBundleAuthor,
};
use codec::{Decode, Encode};
use frame_support::{ensure, PalletError};
Expand Down Expand Up @@ -33,7 +34,7 @@ pub enum Error {
}

#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
pub struct DomainBlock<Number, Hash, DomainNumber, DomainHash, Balance> {
pub struct BlockTreeNode<Number, Hash, DomainNumber, DomainHash, Balance> {
/// The full ER for this block.
pub execution_receipt: ExecutionReceipt<Number, Hash, DomainNumber, DomainHash, Balance>,
/// A set of all operators who have committed to this ER within a bundle. Used to determine who to
Expand Down Expand Up @@ -277,10 +278,10 @@ pub(crate) fn process_execution_receipt<T: Config>(
.cloned()
.expect("should always have a value due to check above");

let DomainBlock {
let BlockTreeNode {
execution_receipt,
operator_ids,
} = DomainBlocks::<T>::take(receipt_hash).ok_or(Error::MissingDomainBlock)?;
} = BlockTreeNodes::<T>::take(receipt_hash).ok_or(Error::MissingDomainBlock)?;

_ = StateRoots::<T>::take((
domain_id,
Expand Down Expand Up @@ -326,11 +327,11 @@ pub(crate) fn process_execution_receipt<T: Config>(
AcceptedReceiptType::CurrentHead => {
// Add confirmation to the current head receipt
let er_hash = execution_receipt.hash();
DomainBlocks::<T>::mutate(er_hash, |maybe_domain_block| {
let domain_block = maybe_domain_block.as_mut().expect(
BlockTreeNodes::<T>::mutate(er_hash, |maybe_node| {
let node = maybe_node.as_mut().expect(
"The domain block of `CurrentHead` receipt is checked to be exist in `execution_receipt_type`; qed"
);
domain_block.operator_ids.push(submitter);
node.operator_ids.push(submitter);
});
}
}
Expand Down Expand Up @@ -363,11 +364,11 @@ fn add_new_receipt_to_block_tree<T: Config>(
er_hashes.insert(er_hash);
},
);
let domain_block = DomainBlock {
let block_tree_node = BlockTreeNode {
execution_receipt,
operator_ids: sp_std::vec![submitter],
};
DomainBlocks::<T>::insert(er_hash, domain_block);
BlockTreeNodes::<T>::insert(er_hash, block_tree_node);
}

/// Import the genesis receipt to the block tree
Expand All @@ -377,7 +378,7 @@ pub(crate) fn import_genesis_receipt<T: Config>(
) {
let er_hash = genesis_receipt.hash();
let domain_block_number = genesis_receipt.domain_block_number;
let domain_block = DomainBlock {
let block_tree_node = BlockTreeNode {
execution_receipt: genesis_receipt,
operator_ids: sp_std::vec![],
};
Expand All @@ -389,11 +390,11 @@ pub(crate) fn import_genesis_receipt<T: Config>(
(
domain_id,
domain_block_number,
domain_block.execution_receipt.domain_block_hash,
block_tree_node.execution_receipt.domain_block_hash,
),
domain_block.execution_receipt.final_state_root,
block_tree_node.execution_receipt.final_state_root,
);
DomainBlocks::<T>::insert(er_hash, domain_block);
BlockTreeNodes::<T>::insert(er_hash, block_tree_node);
}

#[cfg(test)]
Expand Down Expand Up @@ -421,7 +422,7 @@ mod tests {
assert_eq!(block_tree_node_at_0.len(), 1);

let genesis_node =
DomainBlocks::<Test>::get(block_tree_node_at_0.first().unwrap()).unwrap();
BlockTreeNodes::<Test>::get(block_tree_node_at_0.first().unwrap()).unwrap();
assert!(genesis_node.operator_ids.is_empty());
assert_eq!(HeadReceiptNumber::<Test>::get(domain_id), 0);

Expand All @@ -447,7 +448,7 @@ mod tests {
fn test_new_head_receipt() {
let creator = 0u64;
let operator_id = 1u64;
let block_tree_pruning_depth = <Test as Config>::BlockTreePruningDepth::get() as u64;
let block_tree_pruning_depth = <Test as Config>::BlockTreePruningDepth::get();

let mut ext = new_test_ext_with_extensions();
ext.execute_with(|| {
Expand All @@ -462,7 +463,7 @@ mod tests {
);
let mut receipt_of_block_1 = None;
let mut bundle_header_hash_of_block_1 = None;
for block_number in 1..=(block_tree_pruning_depth + 3) {
for block_number in 1..=(block_tree_pruning_depth as u64 + 3) {
// Finilize parent block and initialize block at `block_number`
run_to_block::<Test>(block_number, receipt.consensus_block_hash);

Expand Down Expand Up @@ -495,7 +496,7 @@ mod tests {
));
// `bundle_extrinsics_root` should be tracked in `ExecutionInbox`
assert_eq!(
ExecutionInbox::<Test>::get((domain_id, block_number, block_number)),
ExecutionInbox::<Test>::get((domain_id, block_number as u32, block_number)),
vec![BundleDigest {
header_hash: bundle_header_hash,
extrinsics_root: bundle_extrinsics_root,
Expand All @@ -507,7 +508,7 @@ mod tests {

// Head receipt number should be updated
let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
assert_eq!(head_receipt_number, block_number - 1);
assert_eq!(head_receipt_number, block_number as u32 - 1);

// As we only extending the block tree there should be no fork
let parent_block_tree_nodes =
Expand All @@ -516,7 +517,7 @@ mod tests {

// The submitter should be added to `operator_ids`
let parent_domain_block_receipt = parent_block_tree_nodes.first().unwrap();
let parent_node = DomainBlocks::<Test>::get(parent_domain_block_receipt).unwrap();
let parent_node = BlockTreeNodes::<Test>::get(parent_domain_block_receipt).unwrap();
assert_eq!(parent_node.operator_ids.len(), 1);
assert_eq!(parent_node.operator_ids[0], operator_id);

Expand Down Expand Up @@ -641,7 +642,7 @@ mod tests {
));

assert_eq!(
DomainBlocks::<Test>::get(stale_receipt_hash)
BlockTreeNodes::<Test>::get(stale_receipt_hash)
.unwrap()
.operator_ids,
vec![operator_id1]
Expand Down Expand Up @@ -702,11 +703,11 @@ mod tests {
let nodes = BlockTree::<Test>::get(domain_id, head_receipt_number);
assert_eq!(nodes.len(), 2);
for n in nodes.iter() {
let block = DomainBlocks::<Test>::get(n).unwrap();
let node = BlockTreeNodes::<Test>::get(n).unwrap();
if *n == new_branch_receipt_hash {
assert_eq!(block.operator_ids, vec![operator_id2]);
assert_eq!(node.operator_ids, vec![operator_id2]);
} else {
assert_eq!(block.operator_ids, vec![operator_id1]);
assert_eq!(node.operator_ids, vec![operator_id1]);
}
}
});
Expand All @@ -730,7 +731,7 @@ mod tests {
// Construct a future receipt
let mut future_receipt = current_head_receipt.clone();
future_receipt.domain_block_number = head_receipt_number + 2;
future_receipt.consensus_block_number = head_receipt_number + 2;
future_receipt.consensus_block_number = head_receipt_number as u64 + 2;
ExecutionInbox::<Test>::insert(
(
domain_id,
Expand Down Expand Up @@ -883,7 +884,7 @@ mod tests {
let current_block_number = frame_system::Pallet::<Test>::current_block_number();
let execution_inbox = ExecutionInbox::<Test>::get((
domain_id,
current_block_number,
current_block_number as u32,
current_block_number,
));
let bundles_extrinsics_roots: Vec<_> = execution_inbox
Expand Down Expand Up @@ -934,7 +935,7 @@ mod tests {
let next_receipt = extend_block_tree(
domain_id,
operator_set[0],
current_block_number + challenge_period - 1,
(current_block_number + challenge_period) as u32 - 1,
);
// Confirm `target_receipt`
let confirmed_domain_block = process_execution_receipt::<Test>(
Expand Down
54 changes: 39 additions & 15 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#![cfg_attr(not(feature = "std"), no_std)]
#![feature(array_windows)]
#![feature(associated_type_bounds)]

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;
Expand Down Expand Up @@ -49,12 +50,13 @@ use sp_domains::fraud_proof::{FraudProof, InvalidDomainBlockHashProof, InvalidTo
use sp_domains::verification::verify_invalid_total_rewards_fraud_proof;
use sp_domains::{
DomainBlockLimit, DomainId, DomainInstanceData, ExecutionReceipt, OpaqueBundle, OperatorId,
OperatorPublicKey, ProofOfElection, RuntimeId, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT,
EMPTY_EXTRINSIC_ROOT,
OperatorPublicKey, ProofOfElection, ReceiptHash, RuntimeId,
DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, EMPTY_EXTRINSIC_ROOT,
};
use sp_domains_fraud_proof::fraud_proof_runtime_interface::get_fraud_proof_verification_info;
use sp_domains_fraud_proof::verification::{
verify_invalid_domain_block_hash_fraud_proof, verify_invalid_domain_extrinsics_root_fraud_proof,
verify_invalid_domain_block_hash_fraud_proof,
verify_invalid_domain_extrinsics_root_fraud_proof, verify_invalid_state_transition_fraud_proof,
};
use sp_domains_fraud_proof::{
FraudProofVerificationInfoRequest, FraudProofVerificationInfoResponse,
Expand Down Expand Up @@ -112,7 +114,7 @@ mod pallet {
#![allow(clippy::large_enum_variant)]

use crate::block_tree::{
execution_receipt_type, process_execution_receipt, DomainBlock, Error as BlockTreeError,
execution_receipt_type, process_execution_receipt, BlockTreeNode, Error as BlockTreeError,
ReceiptType,
};
use crate::domain_registry::{
Expand Down Expand Up @@ -171,9 +173,12 @@ mod pallet {
use subspace_core_primitives::U256;

#[pallet::config]
pub trait Config: frame_system::Config {
pub trait Config: frame_system::Config<Hash: Into<H256>> {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

// TODO: `DomainHash` can be derived from `DomainHeader`, it is still needed just for
// converting `DomainHash` to/from `H256` without encode/decode, remove it once we found
// other ways to do this.
/// Domain block hash type.
type DomainHash: Parameter
+ Member
Expand Down Expand Up @@ -455,8 +460,8 @@ mod pallet {
OptionQuery,
>;

/// The domain block tree, map (`domain_id`, `domain_block_number`) to the hash of a domain blocks,
/// which can be used get the domain block in `DomainBlocks`
/// The domain block tree, map (`domain_id`, `domain_block_number`) to the hash of ER,
/// which can be used get the block tree node in `BlockTreeNodes`
#[pallet::storage]
pub(super) type BlockTree<T: Config> = StorageDoubleMap<
_,
Expand All @@ -468,13 +473,13 @@ mod pallet {
ValueQuery,
>;

/// Mapping of domain block hash to domain block
/// Mapping of block tree node hash to the node, each node represent a domain block
#[pallet::storage]
pub(super) type DomainBlocks<T: Config> = StorageMap<
pub(super) type BlockTreeNodes<T: Config> = StorageMap<
_,
Identity,
ReceiptHash,
DomainBlock<
BlockTreeNode<
BlockNumberFor<T>,
T::Hash,
DomainBlockNumberFor<T>,
Expand Down Expand Up @@ -607,6 +612,8 @@ mod pallet {
InvalidDomainBlockHashFraudProof(sp_domains::verification::VerificationError),
/// Invalid domain extrinsic fraud proof
InvalidExtrinsicRootFraudProof(sp_domains::verification::VerificationError),
/// Invalid state transition fraud proof
InvalidStateTransitionFraudProof,
/// Failed to get block randomness.
FailedToGetBlockRandomness,
/// Failed to get domain timestamp extrinsic.
Expand Down Expand Up @@ -891,10 +898,10 @@ mod pallet {
// Prune the bad ER and all of its descendants from the block tree. ER are pruning
// with BFS order from lower height to higher height.
while let Some(receipt_hash) = receipt_to_remove.pop() {
let DomainBlock {
let BlockTreeNode {
execution_receipt,
operator_ids,
} = DomainBlocks::<T>::take(receipt_hash)
} = BlockTreeNodes::<T>::take(receipt_hash)
.ok_or::<Error<T>>(FraudProofError::BadReceiptNotFound.into())?;

BlockTree::<T>::mutate_exists(
Expand Down Expand Up @@ -1369,7 +1376,7 @@ impl<T: Config> Pallet<T> {
pub fn genesis_state_root(domain_id: DomainId) -> Option<H256> {
BlockTree::<T>::get(domain_id, DomainBlockNumberFor::<T>::zero())
.first()
.and_then(DomainBlocks::<T>::get)
.and_then(BlockTreeNodes::<T>::get)
.map(|block| block.execution_receipt.final_state_root.into())
}

Expand Down Expand Up @@ -1522,7 +1529,7 @@ impl<T: Config> Pallet<T> {
fn validate_fraud_proof(
fraud_proof: &FraudProof<BlockNumberFor<T>, T::Hash>,
) -> Result<(), FraudProofError> {
let bad_receipt = DomainBlocks::<T>::get(fraud_proof.bad_receipt_hash())
let bad_receipt = BlockTreeNodes::<T>::get(fraud_proof.bad_receipt_hash())
.ok_or(FraudProofError::BadReceiptNotFound)?
.execution_receipt;

Expand All @@ -1547,7 +1554,7 @@ impl<T: Config> Pallet<T> {
..
}) => {
let parent_receipt =
DomainBlocks::<T>::get(bad_receipt.parent_domain_block_receipt_hash)
BlockTreeNodes::<T>::get(bad_receipt.parent_domain_block_receipt_hash)
.ok_or(FraudProofError::ParentReceiptNotFound)?
.execution_receipt;
verify_invalid_domain_block_hash_fraud_proof::<
Expand Down Expand Up @@ -1602,6 +1609,19 @@ impl<T: Config> Pallet<T> {
)
.map_err(FraudProofError::InvalidExtrinsicRootFraudProof)?;
}
FraudProof::InvalidStateTransition(proof) => {
let bad_receipt_parent =
BlockTreeNodes::<T>::get(bad_receipt.parent_domain_block_receipt_hash)
.ok_or(FraudProofError::ParentReceiptNotFound)?
.execution_receipt;

verify_invalid_state_transition_fraud_proof::<
T::Block,
T::DomainHeader,
BalanceOf<T>,
>(bad_receipt, bad_receipt_parent, proof)
.map_err(|_| FraudProofError::InvalidStateTransitionFraudProof)?;
}
_ => {}
}

Expand Down Expand Up @@ -1778,6 +1798,10 @@ impl<T: Config> Pallet<T> {
let (randomness, _) = T::Randomness::random(seed);
randomness
}

pub fn execution_receipt(receipt_hash: ReceiptHash) -> Option<ExecutionReceiptOf<T>> {
BlockTreeNodes::<T>::get(receipt_hash).map(|db| db.execution_receipt)
}
}

impl<T> Pallet<T>
Expand Down
Loading

0 comments on commit 5d06efe

Please sign in to comment.