Skip to content

Commit

Permalink
Merge pull request #2933 from subspace/fix-er-gap
Browse files Browse the repository at this point in the history
Introduce `submit_receipt` to fill up the unbounded gap between `HeadDomainNumber` and `HeadReceiptNumber`
  • Loading branch information
NingLin-P committed Jul 24, 2024
2 parents ee46fa7 + cda9e51 commit 87a5ff3
Show file tree
Hide file tree
Showing 16 changed files with 882 additions and 207 deletions.
36 changes: 35 additions & 1 deletion crates/pallet-domains/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ use sp_core::crypto::{Ss58Codec, UncheckedFrom};
use sp_core::ByteArray;
use sp_domains::{
dummy_opaque_bundle, BlockFees, DomainId, ExecutionReceipt, OperatorAllowList, OperatorId,
OperatorPublicKey, OperatorSignature, PermissionedActionAllowedBy, RuntimeType, Transfers,
OperatorPublicKey, OperatorSignature, PermissionedActionAllowedBy, ProofOfElection,
RuntimeType, SealedSingletonReceipt, SingletonReceipt, Transfers,
};
use sp_domains_fraud_proof::fraud_proof::FraudProof;
use sp_runtime::traits::{CheckedAdd, One, Zero};
Expand Down Expand Up @@ -877,6 +878,36 @@ mod benchmarks {
);
}

#[benchmark]
fn submit_receipt() {
let domain_id = register_domain::<T>();
let (_, operator_id) =
register_helper_operator::<T>(domain_id, T::MinNominatorStake::get());

assert_eq!(Domains::<T>::head_receipt_number(domain_id), 0u32.into());

let receipt = {
let mut er = BlockTree::<T>::get::<_, DomainBlockNumberFor<T>>(domain_id, Zero::zero())
.and_then(BlockTreeNodes::<T>::get)
.expect("genesis receipt must exist")
.execution_receipt;
er.domain_block_number = One::one();
er
};
let sealed_singleton_receipt = SealedSingletonReceipt {
singleton_receipt: SingletonReceipt {
proof_of_election: ProofOfElection::dummy(domain_id, operator_id),
receipt,
},
signature: OperatorSignature::unchecked_from([0u8; 64]),
};

#[extrinsic_call]
submit_receipt(RawOrigin::None, sealed_singleton_receipt);

assert_eq!(Domains::<T>::head_receipt_number(domain_id), 1u32.into());
}

fn register_runtime<T: Config>() -> RuntimeId {
let genesis_storage = include_bytes!("../res/evm-domain-genesis-storage").to_vec();
let runtime_id = NextRuntimeId::<T>::get();
Expand Down Expand Up @@ -978,6 +1009,9 @@ mod benchmarks {
}

fn run_to_block<T: Config>(block_number: BlockNumberFor<T>, parent_hash: T::Hash) {
if let Some(parent_block_number) = block_number.checked_sub(&One::one()) {
<Domains<T> as Hooks<BlockNumberFor<T>>>::on_finalize(parent_block_number);
}
System::<T>::set_block_number(block_number);
System::<T>::initialize(&block_number, &parent_hash, &Default::default());
<Domains<T> as Hooks<BlockNumberFor<T>>>::on_initialize(block_number);
Expand Down
38 changes: 28 additions & 10 deletions crates/pallet-domains/src/block_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ extern crate alloc;
use crate::{
BalanceOf, BlockTree, BlockTreeNodeFor, BlockTreeNodes, Config, ConsensusBlockHash,
DomainBlockNumberFor, DomainHashingFor, DomainRuntimeUpgradeRecords, ExecutionInbox,
ExecutionReceiptOf, HeadReceiptExtended, HeadReceiptNumber, InboxedBundleAuthor,
LatestConfirmedDomainExecutionReceipt, LatestSubmittedER, Pallet, ReceiptHashFor,
ExecutionReceiptOf, HeadDomainNumber, HeadReceiptNumber, InboxedBundleAuthor,
LatestConfirmedDomainExecutionReceipt, LatestSubmittedER, NewAddedHeadReceipt, Pallet,
ReceiptHashFor,
};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
Expand Down Expand Up @@ -49,6 +50,8 @@ pub enum Error {
OverwritingER,
RuntimeNotFound,
LastBlockNotFound,
UnmatchedNewHeadReceipt,
UnexpectedConfirmedDomainBlock,
}

#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
Expand Down Expand Up @@ -121,7 +124,7 @@ pub(crate) fn execution_receipt_type<T: Config>(
) -> ReceiptType {
let receipt_number = execution_receipt.domain_block_number;
let head_receipt_number = HeadReceiptNumber::<T>::get(domain_id);
let head_receipt_extended = HeadReceiptExtended::<T>::get(domain_id);
let head_receipt_extended = NewAddedHeadReceipt::<T>::get(domain_id).is_some();
let next_receipt_number = head_receipt_number.saturating_add(One::one());
let latest_confirmed_domain_block_number =
Pallet::<T>::latest_confirmed_domain_block_number(domain_id);
Expand Down Expand Up @@ -157,9 +160,11 @@ pub(crate) fn execution_receipt_type<T: Config>(
}

// Add confirm to the head receipt that added in the current block or it is
// the genesis receipt
// the first genesis receipt
let is_first_genesis_receipt =
receipt_number.is_zero() && HeadDomainNumber::<T>::get(domain_id).is_zero();
if receipt_number == head_receipt_number
&& (head_receipt_extended || receipt_number.is_zero())
&& (head_receipt_extended || is_first_genesis_receipt)
{
return ReceiptType::Accepted(AcceptedReceiptType::CurrentHead);
}
Expand Down Expand Up @@ -194,6 +199,19 @@ pub(crate) fn verify_execution_receipt<T: Config>(
return Err(rejected_receipt_type.into());
}

// If there is new head receipt added in the current block, as long as the incoming
// receipt is the same as the new head receipt we can safely skip the following checks,
// if they are not the same, we just reject the incoming receipt and expecting a fraud
// proof will be submit if the new head receipt is fraudulent and then the incoming
// receipt will be re-submit.
if let Some(new_added_head_receipt) = NewAddedHeadReceipt::<T>::get(domain_id) {
ensure!(
new_added_head_receipt == execution_receipt.hash::<DomainHashingFor<T>>(),
Error::UnmatchedNewHeadReceipt,
);
return Ok(());
}

// The genesis receipt is generated and added to the block tree by the runtime upon domain
// instantiation thus it is unchallengeable, we can safely skip other checks as long as we
// can ensure it is always be the same.
Expand Down Expand Up @@ -321,14 +339,15 @@ pub(crate) fn process_execution_receipt<T: Config>(
execution_receipt: ExecutionReceiptOf<T>,
receipt_type: AcceptedReceiptType,
) -> ProcessExecutionReceiptResult<T> {
let er_hash = execution_receipt.hash::<DomainHashingFor<T>>();
let receipt_block_number = execution_receipt.domain_block_number;
match receipt_type {
AcceptedReceiptType::NewHead => {
add_new_receipt_to_block_tree::<T>(domain_id, submitter, execution_receipt)?;

// Update the head receipt number
HeadReceiptNumber::<T>::insert(domain_id, receipt_block_number);
HeadReceiptExtended::<T>::insert(domain_id, true);
NewAddedHeadReceipt::<T>::insert(domain_id, er_hash);

// Prune expired domain block
if let Some(to_prune) =
Expand Down Expand Up @@ -422,7 +441,6 @@ pub(crate) fn process_execution_receipt<T: Config>(
}
AcceptedReceiptType::CurrentHead => {
// Add confirmation to the current head receipt
let er_hash = execution_receipt.hash::<DomainHashingFor<T>>();
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"
Expand Down Expand Up @@ -880,7 +898,7 @@ mod tests {
extend_block_tree_from_zero(domain_id, operator_id1, 3);

// No new receipt submitted in current block
assert!(!HeadReceiptExtended::<Test>::get(domain_id));
assert!(NewAddedHeadReceipt::<Test>::get(domain_id).is_none());

// Receipt that confirm a head receipt of the previous block is stale receipt
let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);
Expand Down Expand Up @@ -1090,8 +1108,8 @@ mod tests {
let head_receipt_number = HeadReceiptNumber::<Test>::get(domain_id);

// reject extending receipt if the HeadReceiptNumber is already extended
assert!(!HeadReceiptExtended::<Test>::get(domain_id));
HeadReceiptExtended::<Test>::set(domain_id, true);
assert!(NewAddedHeadReceipt::<Test>::get(domain_id).is_none());
NewAddedHeadReceipt::<Test>::set(domain_id, Some(H256::random()));

// Construct a future receipt
let mut future_receipt = next_receipt.clone();
Expand Down
12 changes: 9 additions & 3 deletions crates/pallet-domains/src/bundle_storage_fund.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,20 @@ pub fn charge_bundle_storage_fee<T: Config>(
let storage_fund_acc = storage_fund_account::<T>(operator_id);
let storage_fee = T::StorageFee::transaction_byte_fee() * bundle_size.into();

T::Currency::burn_from(
if let Err(err) = T::Currency::burn_from(
&storage_fund_acc,
storage_fee,
Preservation::Expendable,
Precision::Exact,
Fortitude::Polite,
)
.map_err(|_| Error::BundleStorageFeePayment)?;
) {
let total_balance = total_balance::<T>(operator_id);
log::debug!(
target: "runtime::domains",
"Operator {operator_id:?} unable to pay for the bundle storage fee {storage_fee:?}, storage fund total balance {total_balance:?}, err {err:?}",
);
return Err(Error::BundleStorageFeePayment);
}

// Note the storage fee, it will go to the consensus block author
T::StorageFee::note_storage_fees(storage_fee);
Expand Down
Loading

0 comments on commit 87a5ff3

Please sign in to comment.