domain_block_preprocessor/
inherents.rs

1//! Provides functionality of adding inherent extrinsics to the Domain.
2//!
3//! Unlike Primary chain where inherent data is first derived the block author
4//! and the data is verified by the on primary runtime, domains inherents
5//! short circuit the derivation and verification of inherent data
6//! as the inherent data is directly taken from the primary block from which
7//! domain block is being built.
8//!
9//! One of the first use case for this is passing Timestamp data. Before building a
10//! domain block using a primary block, we take the current time from the primary runtime
11//! and then create an unsigned extrinsic that is put on top the bundle extrinsics.
12//!
13//! Deriving these extrinsics during fraud proof verification should be possible since
14//! verification environment will have access to consensus chain.
15
16use sp_api::{ApiExt, ProvideRuntimeApi};
17use sp_blockchain::HeaderBackend;
18use sp_domains::{DomainId, DomainsApi, DomainsDigestItem};
19use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider};
20use sp_messenger::MessengerApi;
21use sp_runtime::traits::{Block as BlockT, Header, NumberFor};
22use sp_timestamp::InherentType;
23use std::error::Error;
24use std::sync::Arc;
25
26pub async fn get_inherent_data<CClient, CBlock, Block>(
27    consensus_client: Arc<CClient>,
28    consensus_block_hash: CBlock::Hash,
29    parent_hash: Block::Hash,
30    domain_id: DomainId,
31) -> Result<InherentData, sp_blockchain::Error>
32where
33    CBlock: BlockT,
34    Block: BlockT,
35    CClient: ProvideRuntimeApi<CBlock> + HeaderBackend<CBlock>,
36    CClient::Api:
37        DomainsApi<CBlock, Block::Header> + MessengerApi<CBlock, NumberFor<CBlock>, CBlock::Hash>,
38{
39    let create_inherent_data_providers =
40        CreateInherentDataProvider::new(consensus_client, Some(consensus_block_hash), domain_id);
41    let inherent_data_providers = <CreateInherentDataProvider<_, _> as CreateInherentDataProviders<
42        Block,
43        (),
44    >>::create_inherent_data_providers(
45        &create_inherent_data_providers, parent_hash, ()
46    )
47        .await?;
48    let mut inherent_data = InherentData::new();
49    inherent_data_providers
50        .provide_inherent_data(&mut inherent_data)
51        .await
52        .map_err(|err| {
53            sp_blockchain::Error::Application(Box::from(format!(
54                "failed to provide inherent data: {err:?}"
55            )))
56        })?;
57
58    Ok(inherent_data)
59}
60
61pub(crate) fn is_runtime_upgraded<CClient, CBlock, Block>(
62    consensus_client: &Arc<CClient>,
63    consensus_block_hash: CBlock::Hash,
64    domain_id: DomainId,
65) -> Result<bool, sp_blockchain::Error>
66where
67    CClient: ProvideRuntimeApi<CBlock> + HeaderBackend<CBlock>,
68    CClient::Api: DomainsApi<CBlock, Block::Header>,
69    CBlock: BlockT,
70    Block: BlockT,
71{
72    let runtime_api = consensus_client.runtime_api();
73
74    let runtime_id = runtime_api
75        .runtime_id(consensus_block_hash, domain_id)?
76        .ok_or(sp_blockchain::Error::Application(Box::from(format!(
77            "No RuntimeId found for {domain_id:?}"
78        ))))?;
79
80    // The runtime_upgrades() API is only present in API versions 2 and later. On earlier versions,
81    // we need to call legacy code.
82    // TODO: remove version check before next network
83    let domains_api_version = runtime_api
84        .api_version::<dyn DomainsApi<CBlock, CBlock::Header>>(consensus_block_hash)?
85        // It is safe to return a default version of 1, since there will always be version 1.
86        .unwrap_or(1);
87
88    let is_upgraded = if domains_api_version >= 2 {
89        let runtime_upgrades = runtime_api.runtime_upgrades(consensus_block_hash)?;
90        runtime_upgrades.contains(&runtime_id)
91    } else {
92        let header = consensus_client.header(consensus_block_hash)?.ok_or(
93            sp_blockchain::Error::MissingHeader(format!(
94                "No header found for {consensus_block_hash:?}"
95            )),
96        )?;
97        header
98            .digest()
99            .logs
100            .iter()
101            .filter_map(|log| log.as_domain_runtime_upgrade())
102            .any(|upgraded_runtime_id| upgraded_runtime_id == runtime_id)
103    };
104
105    Ok(is_upgraded)
106}
107
108/// Returns new upgraded runtime if upgraded did happen in the provided consensus block.
109pub fn extract_domain_runtime_upgrade_code<CClient, CBlock, Block>(
110    consensus_client: &Arc<CClient>,
111    consensus_block_hash: CBlock::Hash,
112    domain_id: DomainId,
113) -> Result<Option<Vec<u8>>, sp_blockchain::Error>
114where
115    CClient: ProvideRuntimeApi<CBlock> + HeaderBackend<CBlock>,
116    CClient::Api: DomainsApi<CBlock, Block::Header>,
117    CBlock: BlockT,
118    Block: BlockT,
119{
120    if is_runtime_upgraded::<_, _, Block>(consensus_client, consensus_block_hash, domain_id)? {
121        let new_domain_runtime = consensus_client
122            .runtime_api()
123            .domain_runtime_code(consensus_block_hash, domain_id)?
124            .ok_or_else(|| {
125                sp_blockchain::Error::Application(Box::from(format!(
126                    "No new runtime code for {domain_id:?}"
127                )))
128            })?;
129
130        Ok(Some(new_domain_runtime))
131    } else {
132        Ok(None)
133    }
134}
135
136#[derive(Debug)]
137pub struct CreateInherentDataProvider<CClient, CBlock: BlockT> {
138    consensus_client: Arc<CClient>,
139    maybe_consensus_block_hash: Option<CBlock::Hash>,
140    domain_id: DomainId,
141}
142
143impl<CClient, CBlock: BlockT + Clone> Clone for CreateInherentDataProvider<CClient, CBlock> {
144    fn clone(&self) -> Self {
145        Self {
146            consensus_client: self.consensus_client.clone(),
147            maybe_consensus_block_hash: self.maybe_consensus_block_hash,
148            domain_id: self.domain_id,
149        }
150    }
151}
152
153impl<CClient, CBlock: BlockT> CreateInherentDataProvider<CClient, CBlock> {
154    pub fn new(
155        consensus_client: Arc<CClient>,
156        maybe_consensus_block_hash: Option<CBlock::Hash>,
157        domain_id: DomainId,
158    ) -> Self {
159        Self {
160            consensus_client,
161            maybe_consensus_block_hash,
162            domain_id,
163        }
164    }
165}
166
167#[async_trait::async_trait]
168impl<CClient, CBlock, Block> CreateInherentDataProviders<Block, ()>
169    for CreateInherentDataProvider<CClient, CBlock>
170where
171    Block: BlockT,
172    CBlock: BlockT,
173    CClient: ProvideRuntimeApi<CBlock> + HeaderBackend<CBlock>,
174    CClient::Api:
175        DomainsApi<CBlock, Block::Header> + MessengerApi<CBlock, NumberFor<CBlock>, CBlock::Hash>,
176{
177    type InherentDataProviders = (
178        sp_timestamp::InherentDataProvider,
179        sp_block_fees::InherentDataProvider,
180        sp_executive::InherentDataProvider,
181        sp_messenger::InherentDataProvider,
182        sp_domain_sudo::InherentDataProvider,
183        sp_evm_tracker::InherentDataProvider,
184    );
185
186    async fn create_inherent_data_providers(
187        &self,
188        _parent: Block::Hash,
189        _extra_args: (),
190    ) -> Result<Self::InherentDataProviders, Box<dyn Error + Send + Sync>> {
191        // always prefer the consensus block hash that was given but eth_rpc
192        // uses this inherent provider to while fetching pending state
193        // https://github.com/paritytech/frontier/blob/master/client/rpc/src/eth/pending.rs#L70
194        // This is a non mutable call used by web3 api and using the best consensus block hash
195        // here is completely ok.
196        let consensus_block_hash = self
197            .maybe_consensus_block_hash
198            .unwrap_or(self.consensus_client.info().best_hash);
199
200        let runtime_api = self.consensus_client.runtime_api();
201        // Some APIs are only present in API versions 3 and later. On earlier versions, we need to
202        // call legacy code.
203        // TODO: remove version check before next network
204        let domains_api_version = runtime_api
205            .api_version::<dyn DomainsApi<CBlock, CBlock::Header>>(consensus_block_hash)?
206            // It is safe to return a default version of 1, since there will always be version 1.
207            .unwrap_or(1);
208
209        let timestamp = if domains_api_version >= 3 {
210            runtime_api.domain_timestamp(consensus_block_hash)?
211        } else {
212            #[allow(deprecated)]
213            runtime_api.timestamp(consensus_block_hash)?
214        };
215        let timestamp_provider =
216            sp_timestamp::InherentDataProvider::new(InherentType::new(timestamp));
217
218        let maybe_runtime_upgrade_code = extract_domain_runtime_upgrade_code::<_, _, Block>(
219            &self.consensus_client,
220            consensus_block_hash,
221            self.domain_id,
222        )?;
223        let runtime_upgrade_provider =
224            sp_executive::InherentDataProvider::new(maybe_runtime_upgrade_code);
225
226        let consensus_chain_byte_fee = if domains_api_version >= 3 {
227            runtime_api.consensus_transaction_byte_fee(consensus_block_hash)?
228        } else {
229            #[allow(deprecated)]
230            runtime_api.consensus_chain_byte_fee(consensus_block_hash)?
231        };
232        let storage_price_provider =
233            sp_block_fees::InherentDataProvider::new(consensus_chain_byte_fee);
234
235        let domain_chains_allowlist_update =
236            runtime_api.domain_chains_allowlist_update(consensus_block_hash, self.domain_id)?;
237        let messenger_inherent_provider =
238            sp_messenger::InherentDataProvider::new(sp_messenger::InherentType {
239                maybe_updates: domain_chains_allowlist_update,
240            });
241
242        let maybe_domain_sudo_call =
243            runtime_api.domain_sudo_call(consensus_block_hash, self.domain_id)?;
244        let domain_sudo_call_inherent_provider =
245            sp_domain_sudo::InherentDataProvider::new(maybe_domain_sudo_call);
246
247        let maybe_evm_domain_contract_creation_allowed_by_call = if domains_api_version >= 4 {
248            runtime_api.evm_domain_contract_creation_allowed_by_call(
249                consensus_block_hash,
250                self.domain_id,
251            )?
252        } else {
253            None
254        };
255        let evm_domain_contract_creation_allowed_by_call_inherent_provider =
256            sp_evm_tracker::InherentDataProvider::new(
257                maybe_evm_domain_contract_creation_allowed_by_call,
258            );
259
260        Ok((
261            timestamp_provider,
262            storage_price_provider,
263            runtime_upgrade_provider,
264            messenger_inherent_provider,
265            domain_sudo_call_inherent_provider,
266            evm_domain_contract_creation_allowed_by_call_inherent_provider,
267        ))
268    }
269}