Skip to content

Commit

Permalink
Merge pull request #1995 from subspace/ER-refactoring
Browse files Browse the repository at this point in the history
Merge the `valid_bundles`, `invalid_bundles`, and `block_extrinsics_roots` fields of the ER into one `bundles` field and add more precise fraud detection
  • Loading branch information
NingLin-P authored Oct 6, 2023
2 parents ca754e7 + d957fc4 commit 7728b93
Show file tree
Hide file tree
Showing 10 changed files with 515 additions and 345 deletions.
85 changes: 44 additions & 41 deletions crates/pallet-domains/src/block_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use crate::pallet::StateRoots;
use crate::{
BalanceOf, BlockTree, Config, ConsensusBlockInfo, DomainBlockDescendants, DomainBlocks,
ExecutionInbox, ExecutionReceiptOf, HeadReceiptNumber, InboxedBundle,
ExecutionInbox, ExecutionReceiptOf, HeadReceiptNumber, InboxedBundleAuthor,
};
use codec::{Decode, Encode};
use frame_support::{ensure, PalletError};
Expand Down Expand Up @@ -123,7 +123,7 @@ pub(crate) fn verify_execution_receipt<T: Config>(
consensus_block_number,
consensus_block_hash,
domain_block_number,
block_extrinsics_roots,
inboxed_bundles,
parent_domain_block_receipt_hash,
execution_trace,
execution_trace_root,
Expand All @@ -138,15 +138,15 @@ pub(crate) fn verify_execution_receipt<T: Config>(
Error::BadGenesisReceipt
);
} else {
let bundles_extrinsics_roots: Vec<_> =
inboxed_bundles.iter().map(|b| b.extrinsics_root).collect();
let execution_inbox =
ExecutionInbox::<T>::get((domain_id, domain_block_number, consensus_block_number));
let expected_extrinsics_roots: Vec<_> = execution_inbox
.into_iter()
.map(|b| b.extrinsics_root)
.collect();
let expected_extrinsics_roots: Vec<_> =
execution_inbox.iter().map(|b| b.extrinsics_root).collect();
ensure!(
!block_extrinsics_roots.is_empty()
&& *block_extrinsics_roots == expected_extrinsics_roots,
!bundles_extrinsics_roots.is_empty()
&& bundles_extrinsics_roots == expected_extrinsics_roots,
Error::InvalidExtrinsicsRoots
);

Expand Down Expand Up @@ -297,12 +297,10 @@ pub(crate) fn process_execution_receipt<T: Config>(
execution_receipt.consensus_block_number,
));
for (index, bd) in bundle_digests.into_iter().enumerate() {
if let Some(bundle_author) = InboxedBundle::<T>::take(bd.header_hash) {
if execution_receipt
.invalid_bundles
.iter()
.any(|ib| ib.bundle_index == index as u32)
{
if let Some(bundle_author) = InboxedBundleAuthor::<T>::take(bd.header_hash) {
// It is okay to index `ER::bundles` here since `verify_execution_receipt` have checked
// the `ER::bundles` have the same length of `ExecutionInbox`
if execution_receipt.inboxed_bundles[index].is_invalid() {
invalid_bundle_authors.push(bundle_author);
}
}
Expand Down Expand Up @@ -409,7 +407,7 @@ mod tests {
use frame_support::dispatch::RawOrigin;
use frame_support::{assert_err, assert_ok};
use sp_core::H256;
use sp_domains::{BundleDigest, InvalidBundle, InvalidBundleType};
use sp_domains::{BundleDigest, InboxedBundle, InvalidBundleType};
use sp_runtime::traits::BlockNumberProvider;

#[test]
Expand Down Expand Up @@ -506,7 +504,9 @@ mod tests {
extrinsics_root: bundle_extrinsics_root,
}]
);
assert!(InboxedBundle::<Test>::contains_key(bundle_header_hash));
assert!(InboxedBundleAuthor::<Test>::contains_key(
bundle_header_hash
));

// Head receipt number should be updated
let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
Expand Down Expand Up @@ -545,7 +545,7 @@ mod tests {
let pruned_bundle = bundle_header_hash_of_block_1.unwrap();
assert!(BlockTree::<Test>::get(domain_id, 1).is_empty());
assert!(ExecutionInbox::<Test>::get((domain_id, 1, 1)).is_empty());
assert!(!InboxedBundle::<Test>::contains_key(pruned_bundle));
assert!(!InboxedBundleAuthor::<Test>::contains_key(pruned_bundle));
assert_eq!(
execution_receipt_type::<Test>(domain_id, &pruned_receipt),
ReceiptType::Rejected(RejectedReceiptType::Pruned)
Expand Down Expand Up @@ -741,12 +741,12 @@ mod tests {
future_receipt.consensus_block_number,
),
future_receipt
.block_extrinsics_roots
.inboxed_bundles
.clone()
.into_iter()
.map(|er| BundleDigest {
.map(|b| BundleDigest {
header_hash: H256::random(),
extrinsics_root: er,
extrinsics_root: b.extrinsics_root,
})
.collect::<Vec<_>>(),
);
Expand Down Expand Up @@ -774,7 +774,8 @@ mod tests {

// Receipt with unknown extrinsics roots
let mut unknown_extrinsics_roots_receipt = current_head_receipt.clone();
unknown_extrinsics_roots_receipt.block_extrinsics_roots = vec![H256::random()];
unknown_extrinsics_roots_receipt.inboxed_bundles =
vec![InboxedBundle::valid(H256::random(), H256::random())];
assert_err!(
verify_execution_receipt::<Test>(domain_id, &unknown_extrinsics_roots_receipt),
Error::InvalidExtrinsicsRoots
Expand Down Expand Up @@ -881,36 +882,38 @@ mod tests {
let head_node = get_block_tree_node_at::<Test>(domain_id, head_receipt_number).unwrap();
assert_eq!(head_node.operator_ids, operator_set);

// Prepare the inbainvalid bundles and bundle authors
let invalid_bundle_authors: Vec<_> = operator_set
.clone()
.into_iter()
.filter(|i| i % 2 == 0)
.collect();
let invalid_bundles = invalid_bundle_authors
.iter()
.map(|i| InvalidBundle {
// operator id start at 1 while bundle_index start at 0, thus minus 1 here
bundle_index: *i as u32 - 1,
invalid_bundle_type: InvalidBundleType::OutOfRangeTx,
})
.collect();

// Get the `bunde_extrinsics_roots` that contains all the submitted bundles
// Get the `bundles_extrinsics_roots` that contains all the submitted bundles
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,
));
let bunde_extrinsics_roots: Vec<_> = execution_inbox
let bundles_extrinsics_roots: Vec<_> = execution_inbox
.into_iter()
.map(|b| b.extrinsics_root)
.collect();
assert_eq!(bunde_extrinsics_roots.len(), operator_set.len());
assert_eq!(bundles_extrinsics_roots.len(), operator_set.len());

target_receipt.invalid_bundles = invalid_bundles;
target_receipt.block_extrinsics_roots = bunde_extrinsics_roots;
// Prepare the invalid bundles and invalid bundle authors
let mut bundles = vec![];
let mut invalid_bundle_authors = vec![];
for (i, (operator, extrinsics_root)) in operator_set
.iter()
.zip(bundles_extrinsics_roots)
.enumerate()
{
if i % 2 == 0 {
invalid_bundle_authors.push(*operator);
bundles.push(InboxedBundle::invalid(
InvalidBundleType::OutOfRangeTx(0),
extrinsics_root,
));
} else {
bundles.push(InboxedBundle::valid(H256::random(), extrinsics_root));
}
}
target_receipt.inboxed_bundles = bundles;

// Run into next block
run_to_block::<Test>(
Expand Down
8 changes: 4 additions & 4 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ mod pallet {
/// these bundles will be used to construct the domain block and `ExecutionInbox` is used to:
///
/// 1. Ensure subsequent ERs of that domain block include all pre-validated extrinsic bundles
/// 2. Index the `InboxedBundle` and pruned its value when the corresponding `ExecutionInbox` is pruned
/// 2. Index the `InboxedBundleAuthor` and pruned its value when the corresponding `ExecutionInbox` is pruned
#[pallet::storage]
pub type ExecutionInbox<T: Config> = StorageNMap<
_,
Expand All @@ -535,7 +535,7 @@ mod pallet {
/// the last `BlockTreePruningDepth` domain blocks. Used to verify the invalid bundle fraud proof and
/// slash malicious operator who have submitted invalid bundle.
#[pallet::storage]
pub(super) type InboxedBundle<T: Config> =
pub(super) type InboxedBundleAuthor<T: Config> =
StorageMap<_, Identity, H256, OperatorId, OptionQuery>;

/// The block number of the best domain block, increase by one when the first bundle of the domain is
Expand Down Expand Up @@ -843,7 +843,7 @@ mod pallet {
},
);

InboxedBundle::<T>::insert(bundle_header_hash, operator_id);
InboxedBundleAuthor::<T>::insert(bundle_header_hash, operator_id);

SuccessfulBundles::<T>::append(domain_id, bundle_hash);

Expand Down Expand Up @@ -1443,7 +1443,7 @@ impl<T: Config> Pallet<T> {
// and sign an existing bundle thus creating a duplicated bundle and pass the check.
let bundle_header_hash = opaque_bundle.sealed_header.pre_hash();
ensure!(
!InboxedBundle::<T>::contains_key(bundle_header_hash),
!InboxedBundleAuthor::<T>::contains_key(bundle_header_hash),
BundleError::DuplicatedBundle
);
Ok(())
Expand Down
40 changes: 27 additions & 13 deletions crates/pallet-domains/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ use sp_domains::fraud_proof::{
use sp_domains::merkle_tree::MerkleTree;
use sp_domains::storage::RawGenesis;
use sp_domains::{
BundleHeader, DomainId, DomainsHoldIdentifier, ExecutionReceipt, OpaqueBundle, OperatorId,
OperatorPair, ProofOfElection, ReceiptHash, RuntimeType, SealedBundleHeader,
BundleHeader, DomainId, DomainsHoldIdentifier, ExecutionReceipt, InboxedBundle, OpaqueBundle,
OperatorId, OperatorPair, ProofOfElection, ReceiptHash, RuntimeType, SealedBundleHeader,
StakingHoldIdentifier,
};
use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash as HashT, IdentityLookup, Zero};
Expand Down Expand Up @@ -300,16 +300,18 @@ pub(crate) fn create_dummy_receipt(
.into();
(execution_trace, execution_trace_root)
};
let inboxed_bundles = block_extrinsics_roots
.into_iter()
.map(InboxedBundle::dummy)
.collect();
ExecutionReceipt {
domain_block_number: block_number,
domain_block_hash: H256::random(),
domain_block_extrinsic_root: Default::default(),
parent_domain_block_receipt_hash,
consensus_block_number: block_number,
consensus_block_hash,
valid_bundles: Vec::new(),
invalid_bundles: Vec::new(),
block_extrinsics_roots,
inboxed_bundles,
final_state_root: Default::default(),
execution_trace,
execution_trace_root,
Expand Down Expand Up @@ -856,18 +858,33 @@ fn test_invalid_domain_extrinsic_root_proof() {
);

let bad_receipt_at = 8;
let domain_block = get_block_tree_node_at::<Test>(domain_id, bad_receipt_at).unwrap();
let valid_bundle_digests = vec![ValidBundleDigest {
bundle_index: 0,
bundle_digest: vec![(Some(vec![1, 2, 3]), ExtrinsicDigest::Data(vec![4, 5, 6]))],
}];
let mut domain_block = get_block_tree_node_at::<Test>(domain_id, bad_receipt_at).unwrap();
let bad_receipt = &mut domain_block.execution_receipt;
bad_receipt.inboxed_bundles = {
valid_bundle_digests
.iter()
.map(|vbd| {
InboxedBundle::valid(BlakeTwo256::hash_of(&vbd.bundle_digest), H256::random())
})
.collect()
};
bad_receipt.domain_block_extrinsic_root = H256::random();

let bad_receipt_hash = domain_block.execution_receipt.hash();
let bad_receipt_hash = bad_receipt.hash();
let (fraud_proof, root) = generate_invalid_domain_extrinsic_root_fraud_proof::<Test>(
domain_id,
bad_receipt_hash,
valid_bundle_digests,
Randomness::from([1u8; 32]),
1000,
);
let (consensus_block_number, consensus_block_hash) = (
domain_block.execution_receipt.consensus_block_number,
domain_block.execution_receipt.consensus_block_hash,
bad_receipt.consensus_block_number,
bad_receipt.consensus_block_hash,
);
ConsensusBlockInfo::<Test>::insert(
domain_id,
Expand All @@ -882,6 +899,7 @@ fn test_invalid_domain_extrinsic_root_proof() {
fn generate_invalid_domain_extrinsic_root_fraud_proof<T: Config + pallet_timestamp::Config>(
domain_id: DomainId,
bad_receipt_hash: ReceiptHash,
valid_bundle_digests: Vec<ValidBundleDigest>,
randomness: Randomness,
moment: Moment,
) -> (FraudProof<BlockNumberFor<T>, T::Hash>, T::Hash) {
Expand All @@ -905,10 +923,6 @@ fn generate_invalid_domain_extrinsic_root_fraud_proof<T: Config + pallet_timesta
storage_proof_for_key::<T, _>(backend.clone(), StorageKey(randomness_storage_key));
let (root, timestamp_storage_proof) =
storage_proof_for_key::<T, _>(backend, StorageKey(timestamp_storage_key));
let valid_bundle_digests = vec![ValidBundleDigest {
bundle_index: 0,
bundle_digest: vec![(Some(vec![1, 2, 3]), ExtrinsicDigest::Data(vec![4, 5, 6]))],
}];
(
FraudProof::InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof {
domain_id,
Expand Down
Loading

0 comments on commit 7728b93

Please sign in to comment.