diff --git a/crates/pallet-domains/src/lib.rs b/crates/pallet-domains/src/lib.rs index a4d48a7e29..dc1c5565ce 100644 --- a/crates/pallet-domains/src/lib.rs +++ b/crates/pallet-domains/src/lib.rs @@ -2102,11 +2102,17 @@ impl Pallet { Ok(HeadDomainNumber::::get(domain_id) + missed_upgrade.into()) } + /// Returns the runtime ID for the supplied `domain_id`, if that domain exists. pub fn runtime_id(domain_id: DomainId) -> Option { DomainRegistry::::get(domain_id) .map(|domain_object| domain_object.domain_config.runtime_id) } + /// Returns the list of runtime upgrades in the current block. + pub fn runtime_upgrades() -> Vec { + DomainRuntimeUpgrades::::get() + } + pub fn domain_instance_data( domain_id: DomainId, ) -> Option<(DomainInstanceData, BlockNumberFor)> { diff --git a/crates/sp-domains-fraud-proof/src/fraud_proof.rs b/crates/sp-domains-fraud-proof/src/fraud_proof.rs index 57ca6b6ada..3fc6521f62 100644 --- a/crates/sp-domains-fraud-proof/src/fraud_proof.rs +++ b/crates/sp-domains-fraud-proof/src/fraud_proof.rs @@ -497,8 +497,8 @@ pub struct InvalidExtrinsicsRootProof { /// The combined storage proofs used during verification pub invalid_inherent_extrinsic_proofs: InvalidInherentExtrinsicDataProof, - /// Domain runtime code upgraded (or "not upgraded") storage proof - pub domain_runtime_upgraded_proof: DomainRuntimeUpgradedProof, + /// A single domain runtime code upgrade (or "not upgraded") storage proof + pub maybe_domain_runtime_upgraded_proof: MaybeDomainRuntimeUpgradedProof, /// Storage proof for a change to the chains that are allowed to open a channel with each domain pub domain_chain_allowlist_proof: DomainChainsAllowlistUpdateStorageProof, diff --git a/crates/sp-domains-fraud-proof/src/storage_proof.rs b/crates/sp-domains-fraud-proof/src/storage_proof.rs index ff70b23bd0..d1fb32d88d 100644 --- a/crates/sp-domains-fraud-proof/src/storage_proof.rs +++ b/crates/sp-domains-fraud-proof/src/storage_proof.rs @@ -286,14 +286,18 @@ where } } +/// A proof of a single domain runtime upgrade (or that there wasn't an upgrade). #[derive(Clone, Debug, Decode, Encode, Eq, PartialEq, TypeInfo)] -pub struct DomainRuntimeUpgradedProof { +pub struct MaybeDomainRuntimeUpgradedProof { + /// A list of domain runtime upgrades for a block. pub domain_runtime_upgrades: DomainRuntimeUpgradesProof, + + /// The new domain runtime code, if the domain runtime was upgraded. pub new_domain_runtime_code: Option, } -impl DomainRuntimeUpgradedProof { - /// Generate the `DomainRuntimeUpgradedProof`. +impl MaybeDomainRuntimeUpgradedProof { + /// Generate the `MaybeDomainRuntimeUpgradedProof`. /// It is the caller's responsibility to check if the domain runtime is upgraded at /// `block_hash`. If it is, the `maybe_runtime_id` should be `Some`. #[cfg(feature = "std")] @@ -324,7 +328,7 @@ impl DomainRuntimeUpgradedProof { } else { None }; - Ok(DomainRuntimeUpgradedProof { + Ok(MaybeDomainRuntimeUpgradedProof { domain_runtime_upgrades, new_domain_runtime_code, }) diff --git a/crates/sp-domains-fraud-proof/src/verification.rs b/crates/sp-domains-fraud-proof/src/verification.rs index d4497adbe2..7fa90fdead 100644 --- a/crates/sp-domains-fraud-proof/src/verification.rs +++ b/crates/sp-domains-fraud-proof/src/verification.rs @@ -66,7 +66,7 @@ where let InvalidExtrinsicsRootProof { valid_bundle_digests, invalid_inherent_extrinsic_proofs, - domain_runtime_upgraded_proof, + maybe_domain_runtime_upgraded_proof, domain_chain_allowlist_proof, domain_sudo_call_proof, } = fraud_proof; @@ -79,7 +79,7 @@ where )?; let maybe_domain_runtime_upgrade = - domain_runtime_upgraded_proof.verify::(runtime_id, &state_root)?; + maybe_domain_runtime_upgraded_proof.verify::(runtime_id, &state_root)?; let domain_chain_allowlist = Vec { frame_support::storage::storage_prefix("System".as_ref(), "Digest".as_ref()).to_vec() } -// TODO: This is used to keep compatible with gemini-3h, remove before next network -/// This is a representation of actual Block Fees storage in pallet-block-fees. -/// Any change in key or value there should be changed here accordingly. -pub fn operator_block_fees_final_key() -> Vec { - frame_support::storage::storage_prefix("BlockFees".as_ref(), "CollectedBlockFees".as_ref()) - .to_vec() -} - /// Hook to handle chain rewards. pub trait OnChainRewards { fn on_chain_rewards(chain_id: ChainId, reward: Balance); @@ -1476,15 +1468,18 @@ pub enum OperatorRewardSource { } sp_api::decl_runtime_apis! { - /// API necessary for domains pallet. + /// APIs used to access the domains pallet. + // When updating this version, document new APIs with "Only present in API versions" comments. + // TODO: when removing this version, also remove "Only present in API versions" comments. + #[api_version(2)] pub trait DomainsApi { /// Submits the transaction bundle via an unsigned extrinsic. fn submit_bundle_unsigned(opaque_bundle: OpaqueBundle, Block::Hash, DomainHeader, Balance>); - // Submit singleton receipt via an unsigned extrinsic. + // Submits a singleton receipt via an unsigned extrinsic. fn submit_receipt_unsigned(singleton_receipt: SealedSingletonReceipt, Block::Hash, DomainHeader, Balance>); - /// Extract the bundles stored successfully from the given extrinsics. + /// Extracts the bundles successfully stored from the given extrinsics. fn extract_successful_bundles( domain_id: DomainId, extrinsics: Vec, @@ -1493,22 +1488,26 @@ sp_api::decl_runtime_apis! { /// Generates a randomness seed for extrinsics shuffling. fn extrinsics_shuffling_seed() -> Randomness; - /// Returns the WASM bundle for given `domain_id`. + /// Returns the current WASM bundle for the given `domain_id`. fn domain_runtime_code(domain_id: DomainId) -> Option>; - /// Returns the runtime id for given `domain_id`. + /// Returns the runtime id for the given `domain_id`. fn runtime_id(domain_id: DomainId) -> Option; - /// Returns the domain instance data for given `domain_id`. + /// Returns the list of runtime upgrades in the current block. + /// Only present in API versions 2 and later. + fn runtime_upgrades() -> Vec; + + /// Returns the domain instance data for the given `domain_id`. fn domain_instance_data(domain_id: DomainId) -> Option<(DomainInstanceData, NumberFor)>; - /// Returns the current timestamp at given height. + /// Returns the current timestamp at the current height. fn timestamp() -> Moment; /// Returns the current Tx range for the given domain Id. fn domain_tx_range(domain_id: DomainId) -> U256; - /// Return the genesis state root if not pruned + /// Returns the genesis state root if not pruned. fn genesis_state_root(domain_id: DomainId) -> Option; /// Returns the best execution chain number. @@ -1523,38 +1522,38 @@ sp_api::decl_runtime_apis! { /// Returns true if there are any ERs in the challenge period with non empty extrinsics. fn non_empty_er_exists(domain_id: DomainId) -> bool; - /// Returns the current best number of the domain. + /// Returns the current best block number for the domain. fn domain_best_number(domain_id: DomainId) -> Option>; - /// Returns the execution receipt + /// Returns the execution receipt with the given hash. fn execution_receipt(receipt_hash: HeaderHashFor) -> Option>; - /// Returns the current epoch and the next epoch operators of the given domain + /// Returns the current epoch and the next epoch operators of the given domain. fn domain_operators(domain_id: DomainId) -> Option<(BTreeMap, Vec)>; - /// Returns the execution receipt hash of the given domain and domain block number + /// Returns the execution receipt hash of the given domain and domain block number. fn receipt_hash(domain_id: DomainId, domain_number: HeaderNumberFor) -> Option>; - /// Return the consensus chain byte fee that will used to charge the domain transaction for consensus - /// chain storage fee + /// Returns the consensus chain byte fee that will used to charge the domain transaction for consensus + /// chain storage fees. fn consensus_chain_byte_fee() -> Balance; - /// Returns the latest confirmed domain block number and hash + /// Returns the latest confirmed domain block number and hash. fn latest_confirmed_domain_block(domain_id: DomainId) -> Option<(HeaderNumberFor, HeaderHashFor)>; - /// Return if the receipt is exist and pending to prune + /// Returns if the receipt is exist and pending to prune fn is_bad_er_pending_to_prune(domain_id: DomainId, receipt_hash: HeaderHashFor) -> bool; - /// Return the balance of the storage fund account + /// Returns the balance of the storage fund account. fn storage_fund_account_balance(operator_id: OperatorId) -> Balance; - /// Return if the domain runtime code is upgraded since `at` + /// Returns true if the given domain's runtime code has been upgraded since `at`. fn is_domain_runtime_upgraded_since(domain_id: DomainId, at: NumberFor) -> Option; - /// Return domain sudo call. + /// Returns the domain sudo calls for the given domain, if any. fn domain_sudo_call(domain_id: DomainId) -> Option>; - /// Return last confirmed domain block execution receipt. + /// Returns the last confirmed domain block execution receipt. fn last_confirmed_domain_block_receipt(domain_id: DomainId) ->Option>; } diff --git a/crates/subspace-fake-runtime-api/src/lib.rs b/crates/subspace-fake-runtime-api/src/lib.rs index 30a2b1bad9..5c3f63b8b5 100644 --- a/crates/subspace-fake-runtime-api/src/lib.rs +++ b/crates/subspace-fake-runtime-api/src/lib.rs @@ -210,6 +210,10 @@ sp_api::impl_runtime_apis! { unreachable!() } + fn runtime_upgrades() -> Vec { + unreachable!() + } + fn domain_instance_data(_domain_id: DomainId) -> Option<(DomainInstanceData, NumberFor)> { unreachable!() } diff --git a/crates/subspace-runtime/src/lib.rs b/crates/subspace-runtime/src/lib.rs index 0f68d7776b..4a1789167a 100644 --- a/crates/subspace-runtime/src/lib.rs +++ b/crates/subspace-runtime/src/lib.rs @@ -1270,6 +1270,10 @@ impl_runtime_apis! { Domains::runtime_id(domain_id) } + fn runtime_upgrades() -> Vec { + Domains::runtime_upgrades() + } + fn domain_instance_data(domain_id: DomainId) -> Option<(DomainInstanceData, NumberFor)> { Domains::domain_instance_data(domain_id) } diff --git a/domains/client/block-preprocessor/src/inherents.rs b/domains/client/block-preprocessor/src/inherents.rs index fdf0c88543..28b8c7bce9 100644 --- a/domains/client/block-preprocessor/src/inherents.rs +++ b/domains/client/block-preprocessor/src/inherents.rs @@ -13,7 +13,7 @@ //! Deriving these extrinsics during fraud proof verification should be possible since //! verification environment will have access to consensus chain. -use sp_api::ProvideRuntimeApi; +use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_domains::{DomainId, DomainsApi, DomainsDigestItem}; use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider}; @@ -69,25 +69,40 @@ where CBlock: BlockT, Block: BlockT, { - let header = consensus_client.header(consensus_block_hash)?.ok_or( - sp_blockchain::Error::MissingHeader(format!( - "No header found for {consensus_block_hash:?}" - )), - )?; - let runtime_api = consensus_client.runtime_api(); + let runtime_id = runtime_api .runtime_id(consensus_block_hash, domain_id)? .ok_or(sp_blockchain::Error::Application(Box::from(format!( "No RuntimeId found for {domain_id:?}" ))))?; - Ok(header - .digest() - .logs - .iter() - .filter_map(|log| log.as_domain_runtime_upgrade()) - .any(|upgraded_runtime_id| upgraded_runtime_id == runtime_id)) + // The runtime_upgrades() API is only present in API versions 2 and later. On earlier versions, + // we need to call legacy code. + // TODO: remove version check before next network + let domains_api_version = runtime_api + .api_version::>(consensus_block_hash)? + // It is safe to return a default version of 1, since there will always be version 1. + .unwrap_or(1); + + let is_upgraded = if domains_api_version >= 2 { + let runtime_upgrades = runtime_api.runtime_upgrades(consensus_block_hash)?; + runtime_upgrades.contains(&runtime_id) + } else { + let header = consensus_client.header(consensus_block_hash)?.ok_or( + sp_blockchain::Error::MissingHeader(format!( + "No header found for {consensus_block_hash:?}" + )), + )?; + header + .digest() + .logs + .iter() + .filter_map(|log| log.as_domain_runtime_upgrade()) + .any(|upgraded_runtime_id| upgraded_runtime_id == runtime_id) + }; + + Ok(is_upgraded) } /// Returns new upgraded runtime if upgraded did happen in the provided consensus block. @@ -102,27 +117,9 @@ where CBlock: BlockT, Block: BlockT, { - let header = consensus_client.header(consensus_block_hash)?.ok_or( - sp_blockchain::Error::MissingHeader(format!( - "No header found for {consensus_block_hash:?}" - )), - )?; - - let runtime_api = consensus_client.runtime_api(); - let runtime_id = runtime_api - .runtime_id(consensus_block_hash, domain_id)? - .ok_or(sp_blockchain::Error::Application(Box::from(format!( - "No RuntimeId found for {domain_id:?}" - ))))?; - - if header - .digest() - .logs - .iter() - .filter_map(|log| log.as_domain_runtime_upgrade()) - .any(|upgraded_runtime_id| upgraded_runtime_id == runtime_id) - { - let new_domain_runtime = runtime_api + if is_runtime_upgraded::<_, _, Block>(consensus_client, consensus_block_hash, domain_id)? { + let new_domain_runtime = consensus_client + .runtime_api() .domain_runtime_code(consensus_block_hash, domain_id)? .ok_or_else(|| { sp_blockchain::Error::Application(Box::from(format!( diff --git a/domains/client/domain-operator/src/fraud_proof.rs b/domains/client/domain-operator/src/fraud_proof.rs index f354c04773..a15d79d486 100644 --- a/domains/client/domain-operator/src/fraud_proof.rs +++ b/domains/client/domain-operator/src/fraud_proof.rs @@ -14,8 +14,7 @@ use sp_domain_digests::AsPredigest; use sp_domains::core_api::DomainCoreApi; use sp_domains::proof_provider_and_verifier::StorageProofProvider; use sp_domains::{ - DomainId, DomainsApi, DomainsDigestItem, ExtrinsicDigest, HeaderHashingFor, InvalidBundleType, - RuntimeId, + DomainId, DomainsApi, ExtrinsicDigest, HeaderHashingFor, InvalidBundleType, RuntimeId, }; use sp_domains_fraud_proof::execution_prover::ExecutionProver; use sp_domains_fraud_proof::fraud_proof::{ @@ -389,7 +388,7 @@ where &self.storage_key_provider, )?; - let domain_runtime_upgraded_proof = DomainRuntimeUpgradedProof::generate( + let maybe_domain_runtime_upgraded_proof = MaybeDomainRuntimeUpgradedProof::generate( &self.storage_key_provider, self.consensus_client.as_ref(), consensus_block_hash, @@ -418,7 +417,7 @@ where proof: FraudProofVariant::InvalidExtrinsicsRoot(InvalidExtrinsicsRootProof { valid_bundle_digests, invalid_inherent_extrinsic_proofs, - domain_runtime_upgraded_proof, + maybe_domain_runtime_upgraded_proof, domain_chain_allowlist_proof, domain_sudo_call_proof, }), @@ -432,13 +431,6 @@ where domain_id: DomainId, at: CBlock::Hash, ) -> Result, FraudProofError> { - let header = - self.consensus_client - .header(at)? - .ok_or(sp_blockchain::Error::MissingHeader(format!( - "No header found for {at:?}" - )))?; - let runtime_id = self .consensus_client .runtime_api() @@ -446,13 +438,13 @@ where .ok_or(sp_blockchain::Error::Application(Box::from(format!( "No RuntimeId found for {domain_id:?}" ))))?; + // This API is only present in API versions 2 and later, but it is safe to call + // unconditionally, because: + // - on Mainnet, there are no domains yet, and + // - on Taurus, there are no invalid execution receipts yet. + let runtime_upgrades = self.consensus_client.runtime_api().runtime_upgrades(at)?; - let is_runtime_upgraded = header - .digest() - .logs - .iter() - .filter_map(|log| log.as_domain_runtime_upgrade()) - .any(|upgraded_runtime_id| upgraded_runtime_id == runtime_id); + let is_runtime_upgraded = runtime_upgrades.contains(&runtime_id); Ok(is_runtime_upgraded.then_some(runtime_id)) } diff --git a/test/subspace-test-runtime/src/lib.rs b/test/subspace-test-runtime/src/lib.rs index 6de8225fdf..b3f7333727 100644 --- a/test/subspace-test-runtime/src/lib.rs +++ b/test/subspace-test-runtime/src/lib.rs @@ -1318,6 +1318,10 @@ impl_runtime_apis! { Domains::runtime_id(domain_id) } + fn runtime_upgrades() -> Vec { + Domains::runtime_upgrades() + } + fn domain_instance_data(domain_id: DomainId) -> Option<(DomainInstanceData, NumberFor)> { Domains::domain_instance_data(domain_id) }