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};
7use sp_runtime::traits::{Block as BlockT, 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) = loop {
94        let Some(block_imported) = imported_block_notification_stream.next().await else {
95            return Err("Imported block notification stream end unexpectedly"
96                .to_string()
97                .into());
98        };
99        let Some(data) = consensus_client
100            .runtime_api()
101            .domain_instance_data(block_imported.hash, self_domain_id)?
102        else {
103            continue;
104        };
105        break data;
106    };
107
108    Ok(BootstrapResult {
109        domain_instance_data,
110        domain_created_at,
111        imported_block_notification_stream,
112    })
113}