1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
use futures::StreamExt;
use sc_client_api::{BlockchainEvents, ImportNotifications};
use sp_api::ProvideRuntimeApi;
use sp_blockchain::HeaderBackend;
use sp_domains::{DomainId, DomainInstanceData, DomainsApi, DomainsDigestItem};
use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};

#[derive(Debug)]
pub struct BootstrapResult<CBlock: BlockT> {
    // The [`DomainInstanceData`] used by the domain instance starter to
    // construct `RuntimeGenesisConfig` of the domain instance
    pub domain_instance_data: DomainInstanceData,
    /// The consensus chain block number when the domain first instantiated.
    pub domain_created_at: NumberFor<CBlock>,
    // The `imported_block_notification_stream` used by the bootstrapper
    //
    // NOTE: the domain instance starter must reuse this stream instead of
    // create a new one from the consensus client to avoid missing imported
    // block notification.
    pub imported_block_notification_stream: ImportNotifications<CBlock>,
}

pub async fn fetch_domain_bootstrap_info<Block, CBlock, CClient>(
    consensus_client: &CClient,
    self_domain_id: DomainId,
) -> Result<BootstrapResult<CBlock>, Box<dyn std::error::Error>>
where
    Block: BlockT,
    CBlock: BlockT,
    CClient: HeaderBackend<CBlock> + ProvideRuntimeApi<CBlock> + BlockchainEvents<CBlock>,
    CClient::Api: DomainsApi<CBlock, Block::Header>,
{
    let mut imported_block_notification_stream =
        consensus_client.every_import_notification_stream();

    // Check if the domain instance data already exist in the consensus chain's state
    let best_hash = consensus_client.info().best_hash;
    if let Some((domain_instance_data, domain_created_at)) = consensus_client
        .runtime_api()
        .domain_instance_data(best_hash, self_domain_id)?
    {
        return Ok(BootstrapResult {
            domain_instance_data,
            domain_created_at,
            imported_block_notification_stream,
        });
    }

    // Check each imported consensus block to get the domain instance data
    let (domain_instance_data, domain_created_at) = 'outer: loop {
        if let Some(block_imported) = imported_block_notification_stream.next().await {
            let header = block_imported.header;
            for item in header.digest().logs.iter() {
                if let Some(domain_id) = item.as_domain_instantiation() {
                    if domain_id == self_domain_id {
                        let res = consensus_client
                            .runtime_api()
                            .domain_instance_data(header.hash(), domain_id)?;

                        match res {
                            Some(data) => break 'outer data,
                            None => {
                                return Err(format!(
                                    "Failed to get domain instance data for domain {domain_id:?}"
                                )
                                .into())
                            }
                        }
                    }
                }
            }
        } else {
            return Err("Imported block notification stream end unexpectedly"
                .to_string()
                .into());
        }
    };

    Ok(BootstrapResult {
        domain_instance_data,
        domain_created_at,
        imported_block_notification_stream,
    })
}