domain_client_operator/
fetch_domain_bootstrap_info.rs

1use futures::StreamExt;
2use sc_client_api::backend::Backend;
3use sc_client_api::{BlockchainEvents, ImportNotifications};
4use sp_api::ProvideRuntimeApi;
5use sp_blockchain::HeaderBackend;
6use sp_domains::{DomainId, DomainInstanceData, DomainsApi, DomainsDigestItem};
7use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero};
8
9#[derive(Debug)]
10pub struct BootstrapResult<CBlock: BlockT> {
11    // The [`DomainInstanceData`] used by the domain instance starter to
12    // construct `RuntimeGenesisConfig` of the domain instance
13    pub domain_instance_data: DomainInstanceData,
14    /// The consensus chain block number when the domain first instantiated.
15    pub domain_created_at: NumberFor<CBlock>,
16    // The `imported_block_notification_stream` used by the bootstrapper
17    //
18    // NOTE: the domain instance starter must reuse this stream instead of
19    // create a new one from the consensus client to avoid missing imported
20    // block notification.
21    pub imported_block_notification_stream: ImportNotifications<CBlock>,
22}
23
24pub async fn fetch_domain_bootstrap_info<Block, CBlock, CClient, DomainBackend>(
25    consensus_client: &CClient,
26    domain_backend: &DomainBackend,
27    self_domain_id: DomainId,
28) -> Result<BootstrapResult<CBlock>, Box<dyn std::error::Error>>
29where
30    Block: BlockT,
31    CBlock: BlockT,
32    CClient: HeaderBackend<CBlock> + ProvideRuntimeApi<CBlock> + BlockchainEvents<CBlock>,
33    CClient::Api: DomainsApi<CBlock, Block::Header>,
34    DomainBackend: Backend<Block>,
35{
36    // The genesis block is finalized when the chain is initialized, if `finalized_state`
37    // is non-empty meaning the domain chain is already started in the last run.
38    let is_domain_started = domain_backend.blockchain().info().finalized_state.is_some();
39
40    let mut imported_block_notification_stream =
41        consensus_client.every_import_notification_stream();
42
43    // Check if the domain instance data already exist in the consensus chain's state
44    let best_hash = consensus_client.info().best_hash;
45    if let Some((domain_instance_data, domain_created_at)) = consensus_client
46        .runtime_api()
47        .domain_instance_data(best_hash, self_domain_id)?
48    {
49        let domain_best_number = consensus_client
50            .runtime_api()
51            .domain_best_number(best_hash, self_domain_id)?
52            .unwrap_or_default();
53
54        // The `domain_best_number` is the expected best domain block after the operator has
55        // processed the consensus block at `best_hash`. If `domain_best_number` is not zero
56        // and the domain chain is not started, the domain block `0..domain_best_number` are
57        // missed, we can not preceed running as a domain node because:
58        //
59        // - The consensus block and state that derive the domain block `0..domain_best_number`
60        //    may not available anymore
61        //
62        // - There may be domain runtime upgrade in `0..domain_best_number` which will result
63        //    in inconsistent `raw_genesis`
64        if !is_domain_started && !domain_best_number.is_zero() {
65            return Err(
66                "An existing consensus node can't be restarted as a domain node, in order to
67                proceed please wipe the `db` and `domains` folders"
68                    .to_string()
69                    .into(),
70            );
71        }
72
73        return Ok(BootstrapResult {
74            domain_instance_data,
75            domain_created_at,
76            imported_block_notification_stream,
77        });
78    }
79
80    // The domain instance data is not found in the consensus chain while the domain chain
81    // is already started meaning the domain chain is running ahead of the consensus chain,
82    // which is unexpected as the domain chain is derived from the consensus chain.
83    if is_domain_started {
84        return Err(
85            "The domain chain is ahead of the consensus chain, inconsistent `db` and `domains`
86            folders from the last run"
87                .to_string()
88                .into(),
89        );
90    }
91
92    // Check each imported consensus block to get the domain instance data
93    let (domain_instance_data, domain_created_at) = 'outer: loop {
94        if let Some(block_imported) = imported_block_notification_stream.next().await {
95            let header = block_imported.header;
96            for item in header.digest().logs.iter() {
97                if let Some(domain_id) = item.as_domain_instantiation() {
98                    if domain_id == self_domain_id {
99                        let res = consensus_client
100                            .runtime_api()
101                            .domain_instance_data(header.hash(), domain_id)?;
102
103                        match res {
104                            Some(data) => break 'outer data,
105                            None => {
106                                return Err(format!(
107                                    "Failed to get domain instance data for domain {domain_id:?}"
108                                )
109                                .into())
110                            }
111                        }
112                    }
113                }
114            }
115        } else {
116            return Err("Imported block notification stream end unexpectedly"
117                .to_string()
118                .into());
119        }
120    };
121
122    Ok(BootstrapResult {
123        domain_instance_data,
124        domain_created_at,
125        imported_block_notification_stream,
126    })
127}