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

Storage Proofs: BlockDigest -> DomainRuntimeUpgrades & merge AllowList #3329

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 131 additions & 52 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ use sp_consensus_subspace::WrappedPotOutput;
use sp_core::H256;
use sp_domains::bundle_producer_election::BundleProducerElectionParams;
use sp_domains::{
DomainBundleLimit, DomainId, DomainInstanceData, ExecutionReceipt, OpaqueBundle, OperatorId,
OperatorPublicKey, OperatorRewardSource, OperatorSignature, ProofOfElection, RuntimeId,
SealedSingletonReceipt, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT, EMPTY_EXTRINSIC_ROOT,
DomainAllowlistUpdates, DomainBundleLimit, DomainId, DomainInstanceData, ExecutionReceipt,
OpaqueBundle, OperatorId, OperatorPublicKey, OperatorRewardSource, OperatorSignature,
ProofOfElection, RuntimeId, SealedSingletonReceipt, DOMAIN_EXTRINSICS_SHUFFLING_SEED_SUBJECT,
EMPTY_EXTRINSIC_ROOT,
};
use sp_domains_fraud_proof::fraud_proof::{
DomainRuntimeCodeAt, FraudProof, FraudProofVariant, InvalidBlockFeesProof,
Expand Down Expand Up @@ -208,9 +209,10 @@ mod pallet {
#[cfg(not(feature = "runtime-benchmarks"))]
use crate::MAX_NOMINATORS_TO_SLASH;
use crate::{
BalanceOf, BlockSlot, BlockTreeNodeFor, DomainBlockNumberFor, ElectionVerificationParams,
ExecutionReceiptOf, FraudProofFor, HoldIdentifier, NominatorId, OpaqueBundleOf,
ReceiptHashFor, SingletonReceiptOf, StateRootOf, MAX_BUNDLE_PER_BLOCK, STORAGE_VERSION,
BalanceOf, BlockSlot, BlockTreeNodeFor, DomainAllowlistUpdatesProvider,
DomainBlockNumberFor, ElectionVerificationParams, ExecutionReceiptOf, FraudProofFor,
HoldIdentifier, NominatorId, OpaqueBundleOf, ReceiptHashFor, SingletonReceiptOf,
StateRootOf, MAX_BUNDLE_PER_BLOCK, STORAGE_VERSION,
};
#[cfg(not(feature = "std"))]
use alloc::string::String;
Expand Down Expand Up @@ -238,8 +240,8 @@ mod pallet {
use sp_domains_fraud_proof::storage_proof::{self, FraudProofStorageKeyProvider};
use sp_domains_fraud_proof::{InvalidTransactionCode, StatelessDomainRuntimeCall};
use sp_runtime::traits::{
AtLeast32BitUnsigned, BlockNumberProvider, CheckEqual, CheckedAdd, Header as HeaderT,
MaybeDisplay, One, SimpleBitOps, Zero,
AtLeast32BitUnsigned, BlockNumberProvider, CheckEqual, CheckedAdd, CheckedSub,
Header as HeaderT, MaybeDisplay, One, SimpleBitOps, Zero,
};
use sp_runtime::Saturating;
use sp_std::boxed::Box;
Expand Down Expand Up @@ -415,6 +417,9 @@ mod pallet {
/// A hook to call after a domain is instantiated
type OnDomainInstantiated: OnDomainInstantiated;

/// Domain allow list update source
type DomainAllowlistUpdates: DomainAllowlistUpdatesProvider;

/// Hash type of MMR
type MmrHash: Parameter + Member + Default + Clone;

Expand Down Expand Up @@ -699,18 +704,18 @@ mod pallet {
#[pallet::storage]
pub(super) type AccumulatedTreasuryFunds<T> = StorageValue<_, BalanceOf<T>, ValueQuery>;

/// Storage used to keep track of which consensus block the domain runtime upgrade happen.
/// Storage used to keep track of which consensus block each domain runtime upgrade happens in.
#[pallet::storage]
pub(super) type DomainRuntimeUpgradeRecords<T: Config> = StorageMap<
pub type DomainRuntimeUpgradeRecords<T: Config> = StorageMap<
_,
Identity,
RuntimeId,
BTreeMap<BlockNumberFor<T>, DomainRuntimeUpgradeEntry<T::Hash>>,
ValueQuery,
>;

/// Temporary storage keep track of domain runtime upgrade happen in the current block, cleared
/// in the next block initialization.
/// Temporary storage to keep track of domain runtime upgrades which happened in the parent
/// block. Cleared in the current block's initialization.
#[pallet::storage]
pub type DomainRuntimeUpgrades<T> = StorageValue<_, Vec<RuntimeId>, ValueQuery>;

Expand Down Expand Up @@ -1856,44 +1861,51 @@ mod pallet {

/// Combined fraud proof data for the InvalidInherentExtrinsic fraud proof
#[pallet::storage]
pub type BlockInvalidInherentExtrinsicData<T> = StorageValue<_, InvalidInherentExtrinsicData>;
pub type BlockInvalidInherentExtrinsicData<T> =
StorageMap<_, Identity, DomainId, InvalidInherentExtrinsicData, ValueQuery>;

#[pallet::hooks]
// TODO: proper benchmark
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_initialize(block_number: BlockNumberFor<T>) -> Weight {
let parent_number = block_number - One::one();
let parent_hash = frame_system::Pallet::<T>::block_hash(parent_number);

// Record any previous domain runtime upgrade in `DomainRuntimeUpgradeRecords` and then do the
// domain runtime upgrade scheduled in the current block
for runtime_id in DomainRuntimeUpgrades::<T>::take() {
let reference_count = RuntimeRegistry::<T>::get(runtime_id)
.expect("Runtime object must be present since domain is insantiated; qed")
.instance_count;
if !reference_count.is_zero() {
DomainRuntimeUpgradeRecords::<T>::mutate(runtime_id, |upgrade_record| {
upgrade_record.insert(
parent_number,
DomainRuntimeUpgradeEntry {
at_hash: parent_hash,
reference_count,
},
)
// At genesis, avoid block number underflow, and use the default zero genesis parent hash.
let parent_number = block_number.checked_sub(&One::one());
Comment on lines +1871 to +1872
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC there should be no on_initialize/on_finalize for the genesis block, the genesis block is built from the chain spec with genesis_build, also see https://github.com/paritytech/polkadot-sdk/blob/ca7817922148c1e6f6856138998f7135f42f3f4f/substrate/frame/system/src/lib.rs#L1812.

cc @vedhavyas @nazar-pc in case I'm wrong.

let parent_hash = parent_number
.map(|parent_number| frame_system::Pallet::<T>::block_hash(parent_number))
.unwrap_or_default();

// By definition, the genesis block doesn't have any previous domain runtime upgrades.
if let Some(parent_number) = parent_number {
// Record any previous domain runtime upgrade in `DomainRuntimeUpgradeRecords` and then do the
// domain runtime upgrade scheduled in the current block.
for runtime_id in DomainRuntimeUpgrades::<T>::take() {
let reference_count = RuntimeRegistry::<T>::get(runtime_id)
.expect("Runtime object must be present since domain is insantiated; qed")
.instance_count;
if !reference_count.is_zero() {
DomainRuntimeUpgradeRecords::<T>::mutate(runtime_id, |upgrade_record| {
upgrade_record.insert(
parent_number,
DomainRuntimeUpgradeEntry {
at_hash: parent_hash,
reference_count,
},
)
});
}
}
do_upgrade_runtimes::<T>(block_number);

// Store the hash of the parent consensus block for domains that have bundles submitted
// in that consensus block
for (domain_id, _) in SuccessfulBundles::<T>::drain() {
ConsensusBlockHash::<T>::insert(domain_id, parent_number, parent_hash);
T::DomainBundleSubmitted::domain_bundle_submitted(domain_id);
DomainSudoCalls::<T>::mutate(domain_id, |sudo_call| {
sudo_call.clear();
});
}
}
do_upgrade_runtimes::<T>(block_number);

// Store the hash of the parent consensus block for domain that have bundles submitted
// in that consensus block
for (domain_id, _) in SuccessfulBundles::<T>::drain() {
ConsensusBlockHash::<T>::insert(domain_id, parent_number, parent_hash);
T::DomainBundleSubmitted::domain_bundle_submitted(domain_id);
DomainSudoCalls::<T>::mutate(domain_id, |sudo_call| {
sudo_call.clear();
});
}

for (operator_id, slot_set) in OperatorBundleSlot::<T>::drain() {
// NOTE: `OperatorBundleSlot` use `BTreeSet` so `last` will return the maximum value in the set
Expand All @@ -1902,15 +1914,27 @@ mod pallet {
}
}

BlockInvalidInherentExtrinsicData::<T>::kill();
let _ = BlockInvalidInherentExtrinsicData::<T>::clear(u32::MAX, None);

Weight::zero()
}

fn on_finalize(_: BlockNumberFor<T>) {
fn on_finalize(block_number: BlockNumberFor<T>) {
let parent_number = block_number.checked_sub(&One::one());
let domain_runtime_upgrade_ids = if let Some(parent_number) = parent_number {
DomainRuntimeUpgradeRecords::<T>::iter()
.flat_map(|(runtime_id, upgrade_map)| {
upgrade_map.get(&parent_number).map(|_entry| runtime_id)
})
.collect::<Vec<RuntimeId>>()
} else {
// By definition, the genesis block doesn't have any parent block or any runtime upgrades
Vec::new()
};

// If this consensus block will derive any domain block, gather the necessary storage for potential fraud proof usage
if SuccessfulBundles::<T>::iter_keys().count() > 0
|| DomainRuntimeUpgrades::<T>::exists()
|| !domain_runtime_upgrade_ids.is_empty()
{
let extrinsics_shuffling_seed = Randomness::from(
Into::<H256>::into(Self::extrinsics_shuffling_seed()).to_fixed_bytes(),
Expand All @@ -1931,13 +1955,54 @@ mod pallet {
let consensus_transaction_byte_fee =
sp_domains::DOMAIN_STORAGE_FEE_MULTIPLIER * transaction_byte_fee;

let invalid_inherent_extrinsic_data = InvalidInherentExtrinsicData {
extrinsics_shuffling_seed,
timestamp,
consensus_transaction_byte_fee,
};
// Record a storage proof for each domain with a successful bundle
for domain_id in SuccessfulBundles::<T>::iter_keys() {
let domain_chain_allowlist =
T::DomainAllowlistUpdates::domain_allowlist_updates(domain_id)
.unwrap_or_default();
Comment on lines +1960 to +1962
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it because of the allowlist update we need to store the InvalidInherentExtrinsicData for each domain separately?

While most of the data are the same for all the domain I think it makes more sense to store them once and shared by all the domains, for the allowlist update we can store Map<domain_id, AllowlistUpdate> which may bring redundant data in the FP but it should be fine as FP and the allowlist update are rare.


let invalid_inherent_extrinsic_data = InvalidInherentExtrinsicData {
extrinsics_shuffling_seed,
timestamp,
consensus_transaction_byte_fee,
domain_chain_allowlist,
};

BlockInvalidInherentExtrinsicData::<T>::insert(
domain_id,
invalid_inherent_extrinsic_data,
);
}

BlockInvalidInherentExtrinsicData::<T>::set(Some(invalid_inherent_extrinsic_data));
// Record a storage proof for each domain with a runtime upgrade
for runtime_id in domain_runtime_upgrade_ids {
let Some(domain_id) = Self::domain_id(runtime_id) else {
// If the genesis block isn't present for the runtime_id, there haven't
// been any upgrades for it
continue;
};

if BlockInvalidInherentExtrinsicData::<T>::contains_key(domain_id) {
// Skip domains that already have a record due to a successful bundle
continue;
}

let domain_chain_allowlist =
T::DomainAllowlistUpdates::domain_allowlist_updates(domain_id)
.unwrap_or_default();

let invalid_inherent_extrinsic_data = InvalidInherentExtrinsicData {
extrinsics_shuffling_seed,
timestamp,
consensus_transaction_byte_fee,
domain_chain_allowlist,
};

BlockInvalidInherentExtrinsicData::<T>::insert(
domain_id,
invalid_inherent_extrinsic_data,
);
}
}

let _ = LastEpochStakingDistribution::<T>::clear(u32::MAX, None);
Expand Down Expand Up @@ -2101,6 +2166,15 @@ impl<T: Config> Pallet<T> {
.map(|domain_object| domain_object.domain_config.runtime_id)
}

pub fn domain_id(runtime_id: RuntimeId) -> Option<DomainId> {
RuntimeRegistry::<T>::get(runtime_id).and_then(|runtime_object| {
runtime_object
.raw_genesis
.domain_id()
.expect("DomainId must be valid since domain is insantiated; qed")
})
}

pub fn domain_instance_data(
domain_id: DomainId,
) -> Option<(DomainInstanceData, BlockNumberFor<T>)> {
Expand Down Expand Up @@ -2910,7 +2984,7 @@ impl<T: Config> Pallet<T> {
}

// Get the domain runtime code that used to derive `receipt`, if the runtime code still present in
// the state then get it from the state otherwise from the `maybe_domain_runtime_code_at` prood.
// the state then get it from the state otherwise from the `maybe_domain_runtime_code_at` proof.
pub fn get_domain_runtime_code_for_receipt(
domain_id: DomainId,
receipt: &ExecutionReceiptOf<T>,
Expand Down Expand Up @@ -3120,3 +3194,8 @@ pub fn calculate_tx_range(
};
new_tx_range.clamp(lower_bound, upper_bound)
}

/// An abstraction that gets domain allow list updates from pallet-messenger.
pub trait DomainAllowlistUpdatesProvider {
fn domain_allowlist_updates(domain_id: DomainId) -> Option<DomainAllowlistUpdates>;
}
1 change: 1 addition & 0 deletions crates/pallet-domains/src/runtime_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ impl Default for DomainRuntimeInfo {
}
}

// TODO: unify this with fraud proofs DomainRuntimeUpgradeEntry
#[derive(TypeInfo, Debug, Encode, Decode, Clone, PartialEq, Eq)]
pub struct DomainRuntimeUpgradeEntry<Hash> {
// The consensus block hash at which the upgrade happened
Expand Down
16 changes: 12 additions & 4 deletions crates/pallet-domains/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ use crate::runtime_registry::ScheduledRuntimeUpgrade;
use crate::staking::Operator;
use crate::{
self as pallet_domains, BalanceOf, BlockSlot, BlockTree, BlockTreeNodes, BundleError, Config,
ConsensusBlockHash, DomainBlockNumberFor, DomainHashingFor, DomainRegistry,
DomainRuntimeUpgradeRecords, DomainRuntimeUpgrades, ExecutionInbox, ExecutionReceiptOf,
FraudProofError, FungibleHoldId, HeadDomainNumber, HeadReceiptNumber, NextDomainId, Operators,
RuntimeRegistry, ScheduledRuntimeUpgrades,
ConsensusBlockHash, DomainAllowlistUpdates, DomainAllowlistUpdatesProvider,
DomainBlockNumberFor, DomainHashingFor, DomainRegistry, DomainRuntimeUpgradeRecords,
DomainRuntimeUpgrades, ExecutionInbox, ExecutionReceiptOf, FraudProofError, FungibleHoldId,
HeadDomainNumber, HeadReceiptNumber, NextDomainId, Operators, RuntimeRegistry,
ScheduledRuntimeUpgrades,
};
use codec::{Decode, Encode, MaxEncodedLen};
use core::mem;
Expand Down Expand Up @@ -237,6 +238,12 @@ impl sp_domains::DomainsTransfersTracker<Balance> for MockDomainsTransfersTracke
}
}

impl DomainAllowlistUpdatesProvider for () {
fn domain_allowlist_updates(_domain_id: DomainId) -> Option<DomainAllowlistUpdates> {
None
}
}

impl pallet_domains::Config for Test {
type RuntimeEvent = RuntimeEvent;
type DomainHash = sp_core::H256;
Expand Down Expand Up @@ -272,6 +279,7 @@ impl pallet_domains::Config for Test {
type ConsensusSlotProbability = SlotProbability;
type DomainBundleSubmitted = ();
type OnDomainInstantiated = ();
type DomainAllowlistUpdates = ();
type Balance = Balance;
type MmrHash = H256;
type MmrProofVerifier = ();
Expand Down
5 changes: 0 additions & 5 deletions crates/pallet-transaction-fees/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,6 @@

pub mod weights;

#[cfg(not(feature = "std"))]
extern crate alloc;

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use codec::{Codec, Decode, Encode};
use frame_support::sp_runtime::traits::Zero;
use frame_support::sp_runtime::SaturatedConversion;
Expand Down
5 changes: 2 additions & 3 deletions crates/sp-domains-fraud-proof/src/fraud_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,9 +497,8 @@ pub struct InvalidExtrinsicsRootProof {
/// The combined storage proofs used during verification
pub invalid_inherent_extrinsic_proofs: InvalidInherentExtrinsicDataProof,

/// The individual storage proofs used during verification
// TODO: combine these proofs into `InvalidInherentExtrinsicDataProof`
pub invalid_inherent_extrinsic_proof: InvalidInherentExtrinsicProof,
/// Optional domain runtime code upgrade storage proof
pub maybe_domain_runtime_upgrade_proof: Option<DomainRuntimeUpgradedProof>,

/// Optional sudo extrinsic call storage proof
pub domain_sudo_call_proof: DomainSudoCallStorageProof,
Expand Down
Loading
Loading