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 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
/*!
Implementation of [`ChainDriver`].
*/
use core::time::Duration;
use alloc::sync::Arc;
use eyre::eyre;
use ibc_relayer::config::compat_mode::CompatMode;
use tokio::runtime::Runtime;
use ibc_relayer::chain::cosmos::types::config::TxConfig;
use ibc_relayer_types::applications::transfer::amount::Amount;
use ibc_relayer_types::core::ics24_host::identifier::ChainId;
use crate::chain::chain_type::ChainType;
use crate::chain::cli::query::query_balance;
use crate::error::Error;
use crate::ibc::denom::Denom;
use crate::ibc::token::Token;
use crate::relayer::tx::new_tx_config_for_test;
use crate::types::env::{EnvWriter, ExportEnv};
use crate::types::wallet::WalletAddress;
use crate::util::retry::assert_eventually_succeed;
/**
Number of times (seconds) to try and query a wallet to reach the
target amount, as used by [`assert_eventual_wallet_amount`].
We set this to around 60 seconds to make sure that the tests still
pass in slower environments like the CI.
If you encounter retry error, try increasing this constant. If the
test is taking much longer to reach eventual consistency, it might
be indication of some underlying performance issues.
*/
const WAIT_WALLET_AMOUNT_ATTEMPTS: u16 = 90;
/**
A driver for interacting with a chain full nodes through command line.
The name `ChainDriver` is inspired by
[WebDriver](https://developer.mozilla.org/en-US/docs/Web/WebDriver),
which is the term used to describe programs that control spawning of the
web browsers. In our case, the ChainDriver is used to spawn and manage
chain full nodes.
Currently the `ChainDriver` is hardcoded to support only a single version
of Gaia chain. In the future, we will want to turn this into one or more
`ChainDriver` traits so that they can be used to spawn multiple chain
implementations other than a single version of Gaia.
*/
#[derive(Debug, Clone)]
pub struct ChainDriver {
pub chain_type: ChainType,
/**
The filesystem path to the Gaia CLI. Defaults to `gaiad`.
*/
pub command_path: String,
/**
The ID of the chain.
*/
pub chain_id: ChainId,
/**
The home directory for the full node to store data files.
*/
pub home_path: String,
pub account_prefix: String,
/**
The port used for RPC.
*/
pub rpc_port: u16,
/**
The port used for GRPC.
*/
pub grpc_port: u16,
pub grpc_web_port: u16,
/**
The port used for P2P. (Currently unused other than for setup)
*/
pub p2p_port: u16,
/**
The port used for pprof. (Currently unused other than for setup)
*/
pub pprof_port: u16,
pub tx_config: TxConfig,
pub runtime: Arc<Runtime>,
pub compat_mode: Option<CompatMode>,
}
impl ExportEnv for ChainDriver {
fn export_env(&self, writer: &mut impl EnvWriter) {
writer.write_env("CMD", &self.command_path);
writer.write_env("HOME", &self.home_path);
writer.write_env("RPC_ADDR", &self.rpc_address());
writer.write_env("GRPC_ADDR", &self.grpc_address());
}
}
impl ChainDriver {
/// Create a new [`ChainDriver`]
pub fn create(
chain_type: ChainType,
command_path: String,
chain_id: ChainId,
home_path: String,
account_prefix: String,
rpc_port: u16,
grpc_port: u16,
grpc_web_port: u16,
p2p_port: u16,
pprof_port: u16,
runtime: Arc<Runtime>,
native_token: String,
compat_mode: Option<CompatMode>,
) -> Result<Self, Error> {
let tx_config = new_tx_config_for_test(
chain_id.clone(),
format!("http://localhost:{rpc_port}"),
format!("http://localhost:{grpc_port}"),
chain_type.address_type(),
native_token,
)?;
Ok(Self {
chain_type,
command_path,
chain_id,
home_path,
account_prefix,
rpc_port,
grpc_port,
grpc_web_port,
p2p_port,
pprof_port,
tx_config,
runtime,
compat_mode,
})
}
/// Returns the full URL for the RPC address.
pub fn rpc_address(&self) -> String {
format!("http://localhost:{}", self.rpc_port)
}
/// Returns the full URL for the WebSocket address.
pub fn websocket_address(&self) -> String {
format!("ws://localhost:{}/websocket", self.rpc_port)
}
/// Returns the full URL for the GRPC address.
pub fn grpc_address(&self) -> String {
format!("http://localhost:{}", self.grpc_port)
}
/**
Returns the full URL for the RPC address to listen to when starting
the full node.
This is somehow different from [`rpc_address`](ChainDriver::rpc_address)
as it requires the `"tcp://"` scheme.
*/
pub fn rpc_listen_address(&self) -> String {
format!("tcp://localhost:{}", self.rpc_port)
}
/**
Returns the full URL for the GRPC address to listen to when starting
the full node.
This is somehow different from [`grpc_address`](ChainDriver::grpc_address)
as it requires no scheme to be specified.
*/
pub fn grpc_listen_address(&self) -> String {
format!("localhost:{}", self.grpc_port)
}
/**
Query for the balances for a given wallet address and denomination
*/
pub fn query_balance(&self, wallet_id: &WalletAddress, denom: &Denom) -> Result<Amount, Error> {
query_balance(
self.chain_id.as_str(),
&self.command_path,
&self.rpc_listen_address(),
&wallet_id.0,
&denom.to_string(),
)
}
/**
Assert that a wallet should eventually have the expected amount in the
given denomination.
*/
pub fn assert_eventual_wallet_amount(
&self,
wallet: &WalletAddress,
token: &Token,
) -> Result<(), Error> {
assert_eventually_succeed(
&format!("wallet reach {wallet} amount {token}"),
WAIT_WALLET_AMOUNT_ATTEMPTS,
Duration::from_secs(1),
|| {
let amount: Amount = self.query_balance(wallet, &token.denom)?;
if amount == token.amount {
Ok(())
} else {
Err(Error::generic(eyre!(
"current balance of account {} with amount {} does not match the target amount {}",
wallet,
amount,
token
)))
}
},
)?;
Ok(())
}
}