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

Add runtime_upgrades() runtime API for DomainRuntimeUpgrades #3337

Merged
merged 6 commits into from
Jan 8, 2025
Merged
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
6 changes: 6 additions & 0 deletions crates/pallet-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2102,11 +2102,17 @@ impl<T: Config> Pallet<T> {
Ok(HeadDomainNumber::<T>::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<RuntimeId> {
DomainRegistry::<T>::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<RuntimeId> {
DomainRuntimeUpgrades::<T>::get()
}

pub fn domain_instance_data(
domain_id: DomainId,
) -> Option<(DomainInstanceData, BlockNumberFor<T>)> {
Expand Down
4 changes: 2 additions & 2 deletions crates/sp-domains-fraud-proof/src/fraud_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
12 changes: 8 additions & 4 deletions crates/sp-domains-fraud-proof/src/storage_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<DomainRuntimeCodeProof>,
}

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")]
Expand Down Expand Up @@ -324,7 +328,7 @@ impl DomainRuntimeUpgradedProof {
} else {
None
};
Ok(DomainRuntimeUpgradedProof {
Ok(MaybeDomainRuntimeUpgradedProof {
domain_runtime_upgrades,
new_domain_runtime_code,
})
Expand Down
4 changes: 2 additions & 2 deletions crates/sp-domains-fraud-proof/src/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -79,7 +79,7 @@ where
)?;

let maybe_domain_runtime_upgrade =
domain_runtime_upgraded_proof.verify::<CBlock, SKP>(runtime_id, &state_root)?;
maybe_domain_runtime_upgraded_proof.verify::<CBlock, SKP>(runtime_id, &state_root)?;

let domain_chain_allowlist = <DomainChainsAllowlistUpdateStorageProof as BasicStorageProof<
CBlock,
Expand Down
55 changes: 27 additions & 28 deletions crates/sp-domains/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1448,14 +1448,6 @@ pub fn system_digest_final_key() -> Vec<u8> {
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<u8> {
frame_support::storage::storage_prefix("BlockFees".as_ref(), "CollectedBlockFees".as_ref())
.to_vec()
}

/// Hook to handle chain rewards.
pub trait OnChainRewards<Balance> {
fn on_chain_rewards(chain_id: ChainId, reward: Balance);
Expand All @@ -1476,15 +1468,18 @@ pub enum OperatorRewardSource<Number> {
}

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<DomainHeader: HeaderT> {
/// Submits the transaction bundle via an unsigned extrinsic.
fn submit_bundle_unsigned(opaque_bundle: OpaqueBundle<NumberFor<Block>, 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<NumberFor<Block>, 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<Block::Extrinsic>,
Expand All @@ -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<Vec<u8>>;

/// Returns the runtime id for given `domain_id`.
/// Returns the runtime id for the given `domain_id`.
fn runtime_id(domain_id: DomainId) -> Option<RuntimeId>;

/// 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<RuntimeId>;

/// Returns the domain instance data for the given `domain_id`.
fn domain_instance_data(domain_id: DomainId) -> Option<(DomainInstanceData, NumberFor<Block>)>;

/// 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<H256>;

/// Returns the best execution chain number.
Expand All @@ -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<HeaderNumberFor<DomainHeader>>;

/// Returns the execution receipt
/// Returns the execution receipt with the given hash.
fn execution_receipt(receipt_hash: HeaderHashFor<DomainHeader>) -> Option<ExecutionReceiptFor<DomainHeader, Block, Balance>>;

/// 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<OperatorId, Balance>, Vec<OperatorId>)>;

/// 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<DomainHeader>) -> Option<HeaderHashFor<DomainHeader>>;

/// 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<DomainHeader>, HeaderHashFor<DomainHeader>)>;

/// 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<DomainHeader>) -> 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<Block>) -> Option<bool>;

/// Return domain sudo call.
/// Returns the domain sudo calls for the given domain, if any.
fn domain_sudo_call(domain_id: DomainId) -> Option<Vec<u8>>;

/// 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<ExecutionReceiptFor<DomainHeader, Block, Balance>>;
}

Expand Down
4 changes: 4 additions & 0 deletions crates/subspace-fake-runtime-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,10 @@ sp_api::impl_runtime_apis! {
unreachable!()
}

fn runtime_upgrades() -> Vec<sp_domains::RuntimeId> {
unreachable!()
}

fn domain_instance_data(_domain_id: DomainId) -> Option<(DomainInstanceData, NumberFor<Block>)> {
unreachable!()
}
Expand Down
4 changes: 4 additions & 0 deletions crates/subspace-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1270,6 +1270,10 @@ impl_runtime_apis! {
Domains::runtime_id(domain_id)
}

fn runtime_upgrades() -> Vec<sp_domains::RuntimeId> {
Domains::runtime_upgrades()
}

fn domain_instance_data(domain_id: DomainId) -> Option<(DomainInstanceData, NumberFor<Block>)> {
Domains::domain_instance_data(domain_id)
}
Expand Down
65 changes: 31 additions & 34 deletions domains/client/block-preprocessor/src/inherents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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::<dyn DomainsApi<CBlock, CBlock::Header>>(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)
};
Comment on lines +80 to +103
Copy link
Member Author

@teor2345 teor2345 Jan 8, 2025

Choose a reason for hiding this comment

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

I think we'll need this compatibility code for inherents, so that runtime upgrades on Taurus will work. I avoided repeating the compatibility code by calling this method instead.

I think we'll be able to remove this code (edit: the compatibility code) after the runtime on Taurus is upgraded, and most nodes on Taurus and Mainnet are upgraded?

Copy link
Member

Choose a reason for hiding this comment

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

I didn't read into this PR to understand the context, but as a general rule inherents can't be removed without making it impossible to import older blocks. They are kind of like host functions for runtime environment that once added can't be removed.

Copy link
Member Author

@teor2345 teor2345 Jan 8, 2025

Choose a reason for hiding this comment

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

I didn't read into this PR to understand the context, but as a general rule inherents can't be removed without making it impossible to import older blocks. They are kind of like host functions for runtime environment that once added can't be removed.

No inherents are added or removed in this PR, it just changes how we process them to avoid some duplicate work.

This PR changes the runtime code the domain client calls when it processes a domain runtime upgrade inherent. Previously, we searched through the block header to determine if the runtime was upgraded. Now we ask the runtime for a list of upgraded runtime IDs (which the domains pallet has already determined by doing a similar search.)

Copy link
Member

Choose a reason for hiding this comment

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

I think we'll be able to remove this code (edit: the compatibility code) after the runtime on Taurus is upgraded, and most nodes on Taurus and Mainnet are upgraded.

Nazar is correct, we can't remove the compatibility code even after runtime/node upgraded. Because the runtime code is on-chain state, it applies to block that after the upgrade and before the next upgrade, older block will still use the old runtime (which doesn't have the new runtime API). Image a node (running on the latest release that doesn't contain the compatibility code) just joined the network and sync from genesis, it will fail to call the new API until the block that upgrade the runtime.


Ok(is_upgraded)
}

/// Returns new upgraded runtime if upgraded did happen in the provided consensus block.
Expand All @@ -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!(
Expand Down
26 changes: 9 additions & 17 deletions domains/client/domain-operator/src/fraud_proof.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
}),
Expand All @@ -432,27 +431,20 @@ where
domain_id: DomainId,
at: CBlock::Hash,
) -> Result<Option<RuntimeId>, 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()
.runtime_id(at, domain_id)?
.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))
}
Expand Down
4 changes: 4 additions & 0 deletions test/subspace-test-runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1318,6 +1318,10 @@ impl_runtime_apis! {
Domains::runtime_id(domain_id)
}

fn runtime_upgrades() -> Vec<sp_domains::RuntimeId> {
Domains::runtime_upgrades()
}

fn domain_instance_data(domain_id: DomainId) -> Option<(DomainInstanceData, NumberFor<Block>)> {
Domains::domain_instance_data(domain_id)
}
Expand Down
Loading