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

Introduce submit_receipt to fill up the unbounded gap between HeadDomainNumber and HeadReceiptNumber #2933

Merged
merged 10 commits into from
Jul 24, 2024
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
Loading