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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
/*!
   Builder construct that spawn new chains with some common parameters.
*/

use eyre::eyre;
use std::str::FromStr;

use alloc::sync::Arc;
use ibc_relayer::config::compat_mode::CompatMode;
use tokio::runtime::Runtime;

use crate::chain::driver::ChainDriver;
use crate::error::Error;
use crate::types::config::TestConfig;
use crate::util::random::random_unused_tcp_port;

use super::chain_type::ChainType;

/**
   Used for holding common configuration needed to create new `ChainDriver`s.

   Currently this is hardcoded to support only a single version of Gaia chain.
   We may want to turn this into a trait in the future to support different
   chain implementations.
*/
#[derive(Debug)]
pub struct ChainBuilder {
    /**
       The CLI executable used for the chain commands. Defaults to `gaiad`.

       TODO: Have a mutable list of command paths so that the `ChainBuilder`
       may return [`ChainDriver`]s bound to different chain commands
       for testing with multiple chain implementations.
    */
    pub command_paths: Vec<String>,

    /**
       The filesystem path to store the data files used by the chain.
    */
    pub base_store_dir: String,

    pub account_prefixes: Vec<String>,

    pub native_tokens: Vec<String>,

    pub compat_modes: Option<Vec<String>>,

    pub runtime: Arc<Runtime>,
}

impl ChainBuilder {
    /**
       Create a new `ChainBuilder`.
    */
    pub fn new(
        command_paths: Vec<String>,
        base_store_dir: &str,
        account_prefixes: Vec<String>,
        native_tokens: Vec<String>,
        compat_modes: Option<Vec<String>>,
        runtime: Arc<Runtime>,
    ) -> Self {
        Self {
            command_paths,
            base_store_dir: base_store_dir.to_string(),
            account_prefixes,
            native_tokens,
            compat_modes,
            runtime,
        }
    }

    /**
       Create a `ChainBuilder` based on the provided [`TestConfig`].
    */
    pub fn new_with_config(config: &TestConfig, runtime: Arc<Runtime>) -> Self {
        Self::new(
            config.chain_command_paths.clone(),
            &format!("{}", config.chain_store_dir.display()),
            config.account_prefixes.clone(),
            config.native_tokens.clone(),
            config.compat_modes.clone(),
            runtime,
        )
    }

    /**
       Create a new [`ChainDriver`] with the chain ID containing the
       given prefix.

       Note that this only configures the [`ChainDriver`] without
       the actual chain being intitialized or spawned.

       The `ChainBuilder` will configure the [`ChainDriver`] with random
       unused ports, and add a random suffix to the chain ID.

       For example, calling this with a prefix `"alpha"` will return
       a [`ChainDriver`] configured with a chain ID  like
       `"ibc-alpha-f5a2a988"`.
    */
    pub fn new_chain(
        &self,
        prefix: &str,
        use_random_id: bool,
        chain_number: usize,
    ) -> Result<ChainDriver, Error> {
        // If there are more spawned chains than given chain binaries, take the N-th position modulo
        // the number of chain binaries given. Same for account prefix.
        let chain_number = chain_number % self.command_paths.len();
        let account_number = chain_number % self.account_prefixes.len();
        let native_token_number = chain_number % self.native_tokens.len();
        let compat_mode = if let Some(modes) = &self.compat_modes {
            let mode_str = &modes[chain_number % modes.len()];
            Some(CompatMode::from_str(mode_str).map_err(|e| {
                Error::generic(eyre!(
                    "Invalid CompatMode environment variable `{mode_str}`: {e}"
                ))
            })?)
        } else {
            None
        };

        let chain_type = ChainType::from_str(&self.command_paths[chain_number])?;

        let chain_id = chain_type.chain_id(prefix, use_random_id);

        let rpc_port = random_unused_tcp_port();
        let grpc_port = random_unused_tcp_port();
        let grpc_web_port = random_unused_tcp_port();
        let p2p_port = random_unused_tcp_port();
        let pprof_port = random_unused_tcp_port();

        let home_path = format!("{}/{}", self.base_store_dir, chain_id);

        let driver = ChainDriver::create(
            chain_type,
            self.command_paths[chain_number].clone(),
            chain_id,
            home_path,
            self.account_prefixes[account_number].clone(),
            rpc_port,
            grpc_port,
            grpc_web_port,
            p2p_port,
            pprof_port,
            self.runtime.clone(),
            self.native_tokens[native_token_number].clone(),
            compat_mode,
        )?;

        Ok(driver)
    }
}