From d30116d39e552113b9981e08668901f214decf02 Mon Sep 17 00:00:00 2001 From: Luca Joss Date: Thu, 3 Oct 2024 13:13:17 +0200 Subject: [PATCH 01/11] Update bootstrapping of consumer chain --- flake.lock | 76 +++--- flake.nix | 3 +- .../test-framework/src/bootstrap/consumer.rs | 2 +- .../test-framework/src/chain/cli/provider.rs | 237 +++++++++++++++++- .../test-framework/src/chain/ext/bootstrap.rs | 214 +++++++++++++--- .../src/framework/binary/ics.rs | 23 +- 6 files changed, 489 insertions(+), 66 deletions(-) diff --git a/flake.lock b/flake.lock index ae1418f0a4..232ea75686 100644 --- a/flake.lock +++ b/flake.lock @@ -158,6 +158,7 @@ "gaia17-src": "gaia17-src", "gaia18-src": "gaia18-src", "gaia19-src": "gaia19-src", + "gaia20-src": "gaia20-src", "gaia5-src": "gaia5-src", "gaia6-ordered-src": "gaia6-ordered-src", "gaia6-src": "gaia6-src", @@ -225,15 +226,16 @@ "wasmvm_2_1_2-src": "wasmvm_2_1_2-src" }, "locked": { - "lastModified": 1725007328, - "narHash": "sha256-DMRDFFpXIJyKRNhiVhXaIoOJkmVyKbt6DO6M5F8CCec=", + "lastModified": 1727710776, + "narHash": "sha256-hnlolMIm2DObRP2SstNhZaDkv1+ycxuR27jhPsCCdOU=", "owner": "informalsystems", "repo": "cosmos.nix", - "rev": "d87f011075da3ee1afbbe3443ca25461af7d49fd", + "rev": "af8d2cbb5b68779eb39c90117461ec70b2cfc0b5", "type": "github" }, "original": { "owner": "informalsystems", + "ref": "luca_joss/add-gaia-v20-alpha", "repo": "cosmos.nix", "type": "github" } @@ -497,11 +499,11 @@ "systems": "systems_6" }, "locked": { - "lastModified": 1710146030, - "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=", + "lastModified": 1726560853, + "narHash": "sha256-X6rJYSESBVr3hBoH0WbKE5KvhPU5bloyZ2L4K60/fPQ=", "owner": "numtide", "repo": "flake-utils", - "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a", + "rev": "c1dfcf08411b08f6b8615f7d8971a2bfa81d5e8a", "type": "github" }, "original": { @@ -673,6 +675,23 @@ "type": "github" } }, + "gaia20-src": { + "flake": false, + "locked": { + "lastModified": 1726853009, + "narHash": "sha256-N7x3k56AtPbIbbJjqKmlEJIytKElALJwj14lZ2pewZg=", + "owner": "cosmos", + "repo": "gaia", + "rev": "2dba9d471ef73b0a99e844bf55a44ddae700ea06", + "type": "github" + }, + "original": { + "owner": "cosmos", + "ref": "v20.0.0", + "repo": "gaia", + "type": "github" + } + }, "gaia5-src": { "flake": false, "locked": { @@ -964,16 +983,16 @@ "ibc-go-v6-src": { "flake": false, "locked": { - "lastModified": 1712318519, - "narHash": "sha256-roRXZEOJIFJiXEQ+a71QdMmqoVJKVk2wvPgHJ9r/mQ8=", + "lastModified": 1713970631, + "narHash": "sha256-MpBZ/V8agG3/GFJXEY3kSp+FnUw8rX5C613l5D8HXs4=", "owner": "cosmos", "repo": "ibc-go", - "rev": "8e31269c692d87ac65bfe70cf609925975a57203", + "rev": "8cd96f4169ebee21d50ef69417203b21cf4238ab", "type": "github" }, "original": { "owner": "cosmos", - "ref": "v6.3.0", + "ref": "v6.3.1", "repo": "ibc-go", "type": "github" } @@ -981,16 +1000,16 @@ "ibc-go-v7-src": { "flake": false, "locked": { - "lastModified": 1712318559, - "narHash": "sha256-uYiUNXLD48v3vRGK6/aQ7z7Ed5hY8VnEBGG/3Uv87Nc=", + "lastModified": 1725009574, + "narHash": "sha256-6Wpxu4mQaSrQKOLSb3kUpzRrr0aIHVMVEHVwpGJw3sM=", "owner": "cosmos", "repo": "ibc-go", - "rev": "802ca265dba74a293747e1ccb8b7999aa985af19", + "rev": "a5dde80a4ba1c4601aa055a311bf46779104627f", "type": "github" }, "original": { "owner": "cosmos", - "ref": "v7.4.0", + "ref": "v7.8.0", "repo": "ibc-go", "type": "github" } @@ -1015,16 +1034,16 @@ "ibc-go-v8-src": { "flake": false, "locked": { - "lastModified": 1716359952, - "narHash": "sha256-KTjyHwmXA/jgppDKRe85XfRmh8O7AHFKhDyyOb9VROU=", + "lastModified": 1726232417, + "narHash": "sha256-oIfVmXIOkRqDF4NGmHsh5BELCIzPydAqiz+7urnZ7A4=", "owner": "cosmos", "repo": "ibc-go", - "rev": "9b6567bf818198ded336490d5f2d89c9d42fd87b", + "rev": "6b2554360c0e3f0bbaa59da5b16b29fc05675c57", "type": "github" }, "original": { "owner": "cosmos", - "ref": "v8.3.1", + "ref": "v8.5.1", "repo": "ibc-go", "type": "github" } @@ -1049,16 +1068,16 @@ "ibc-go-v9-src": { "flake": false, "locked": { - "lastModified": 1723037346, - "narHash": "sha256-ba8gbJ0l4l8ZRT9XVN3hTcnxZSb5Fn20p1xiEG4/54c=", + "lastModified": 1725262239, + "narHash": "sha256-F2p/lIs2/ropKdm0Pebz1kjhRlgwYK0BmDGe/sYec3Y=", "owner": "cosmos", "repo": "ibc-go", - "rev": "66ebf864d7bfe2193a96c972a9e74196b2ddf104", + "rev": "8983f91e519fb1c43d9c9481ba60f11e4ae2b2b0", "type": "github" }, "original": { "owner": "cosmos", - "ref": "v9.0.0-beta.1", + "ref": "v9.0.0-rc.0", "repo": "ibc-go", "type": "github" } @@ -1131,15 +1150,16 @@ "interchain-security-src": { "flake": false, "locked": { - "narHash": "sha256-adBzn51PKoRsCL9gIzC5Tcqmu7u3GjxTcDj2jpZ/da8=", + "lastModified": 1726849313, + "narHash": "sha256-1WEvV3LoXfGvZC9fXOb8mBLKVGCVBiXZcwUewSPit+8=", "owner": "cosmos", "repo": "interchain-security", - "rev": "03aada4af3243dbf739a12adfacc7b37232df694", + "rev": "1e60637f9d8f3505208282416abfbb87fabc4795", "type": "github" }, "original": { "owner": "cosmos", - "ref": "feat/ics-misbehaviour-handling", + "ref": "v6.1.0", "repo": "interchain-security", "type": "github" } @@ -1444,11 +1464,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1725194671, - "narHash": "sha256-tLGCFEFTB5TaOKkpfw3iYT9dnk4awTP/q4w+ROpMfuw=", + "lastModified": 1727648392, + "narHash": "sha256-VTlVv1nSxImFxY6RPQpNZxvEOQ0u5s1wBFDgixySNDo=", "owner": "nixos", "repo": "nixpkgs", - "rev": "b833ff01a0d694b910daca6e2ff4a3f26dee478c", + "rev": "4e0c36e4dd53f35d5a6385bdae88895ec5832f70", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index 1a9ae4229a..c004502da0 100644 --- a/flake.nix +++ b/flake.nix @@ -4,7 +4,7 @@ inputs = { nixpkgs.url = github:nixos/nixpkgs/nixpkgs-unstable; flake-utils.url = github:numtide/flake-utils; - cosmos-nix.url = github:informalsystems/cosmos.nix; + cosmos-nix.url = github:informalsystems/cosmos.nix/luca_joss/add-gaia-v20-alpha; }; outputs = inputs: let @@ -33,6 +33,7 @@ evmos gaia6-ordered gaia18 + gaia20 ibc-go-v2-simapp ibc-go-v3-simapp ibc-go-v4-simapp diff --git a/tools/test-framework/src/bootstrap/consumer.rs b/tools/test-framework/src/bootstrap/consumer.rs index 6c85595331..3899d46f5b 100644 --- a/tools/test-framework/src/bootstrap/consumer.rs +++ b/tools/test-framework/src/bootstrap/consumer.rs @@ -57,7 +57,7 @@ pub fn bootstrap_consumer_node( node_a .chain_driver - .query_consumer_genesis(&chain_driver, chain_driver.chain_id.as_str())?; + .query_consumer_genesis(&chain_driver, prefix)?; chain_driver.replace_genesis_state()?; diff --git a/tools/test-framework/src/chain/cli/provider.rs b/tools/test-framework/src/chain/cli/provider.rs index 2c5dc6f8cd..31a8a7f0c4 100644 --- a/tools/test-framework/src/chain/cli/provider.rs +++ b/tools/test-framework/src/chain/cli/provider.rs @@ -1,6 +1,6 @@ use eyre::eyre; use std::collections::HashMap; -use std::str; +use std::{str, thread}; use crate::chain::exec::{simple_exec, ExecOutput}; use crate::error::{handle_generic_error, Error}; @@ -12,7 +12,8 @@ pub fn submit_consumer_chain_proposal( rpc_listen_address: &str, fees: &str, ) -> Result<(), Error> { - let proposal_file = format!("{}/consumer_proposal.json", home_path); + let proposal_file = format!("{}/consumer_proposal_topn.json", home_path); + thread::sleep(std::time::Duration::from_secs(3)); // The submission might fail silently if there is not enough gas let raw_output = simple_exec( @@ -21,8 +22,236 @@ pub fn submit_consumer_chain_proposal( &[ "tx", "gov", - "submit-legacy-proposal", - "consumer-addition", + "submit-proposal", + &proposal_file, + "--chain-id", + chain_id, + "--from", + "validator", + "--home", + home_path, + "--node", + rpc_listen_address, + "--keyring-backend", + "test", + "--gas", + "2000000", + "--fees", + fees, + "--output", + "json", + "--yes", + ], + )?; + + let output: serde_json::Value = + serde_json::from_str(&raw_output.stdout).map_err(handle_generic_error)?; + + let output_code = output.get("code").and_then(|code| code.as_u64()).ok_or_else(|| Error::generic(eyre!("failed to extract 'code' from 'tx gov submit-legacy-proposal consumer-addition' command")))?; + + // Proposal submission might fail due to account sequence error. + // Wait a bit and resubmit if the first submission fails + if output_code != 0 { + thread::sleep(std::time::Duration::from_secs(3)); + simple_exec( + chain_id, + command_path, + &[ + "tx", + "gov", + "submit-proposal", + &proposal_file, + "--chain-id", + chain_id, + "--from", + "validator", + "--home", + home_path, + "--node", + rpc_listen_address, + "--keyring-backend", + "test", + "--gas", + "2000000", + "--fees", + fees, + "--output", + "json", + "--yes", + ], + )?; + } + + Ok(()) +} + +pub fn create_consumer( + chain_id: &str, + command_path: &str, + home_path: &str, + rpc_listen_address: &str, + fees: &str, +) -> Result { + let proposal_file = format!("{}/consumer_proposal.json", home_path); + + // The submission might fail silently if there is not enough gas + let raw_output = simple_exec( + chain_id, + command_path, + &[ + "tx", + "provider", + "create-consumer", + &proposal_file, + "--chain-id", + chain_id, + "--from", + "validator", + "--home", + home_path, + "--node", + rpc_listen_address, + "--keyring-backend", + "test", + "--gas", + "2000000", + "--fees", + fees, + "--output", + "json", + "--yes", + ], + )?; + + let output: serde_json::Value = + serde_json::from_str(&raw_output.stdout).map_err(handle_generic_error)?; + + let hash = output.get("txhash").and_then(|code| code.as_str()).unwrap(); + + thread::sleep(std::time::Duration::from_secs(3)); + + let hash_output = simple_exec( + chain_id, + command_path, + &[ + "query", + "tx", + hash, + "--chain-id", + chain_id, + "--home", + home_path, + "--node", + rpc_listen_address, + "--output", + "json", + ], + )?; + + let hash_output_json: serde_json::Value = + serde_json::from_str(&hash_output.stdout).map_err(handle_generic_error)?; + + let output_code = output.get("code").and_then(|code| code.as_u64()).ok_or_else(|| Error::generic(eyre!("failed to extract 'code' from 'tx gov submit-legacy-proposal consumer-addition' command")))?; + + if output_code != 0 { + let output_logs = output.get("raw_log").and_then(|code| code.as_str()).ok_or_else(|| Error::generic(eyre!("failed to extract 'raw_logs' from 'tx gov submit-legacy-proposal consumer-addition' command")))?; + return Err(Error::generic(eyre!("output code for commande 'tx provider create-consumer' should be 0, but is instead '{output_code}'. Detail: {output_logs}", ))); + } + + let events = hash_output_json + .get("events") + .and_then(|code| code.as_array()) + .ok_or_else(|| { + Error::generic(eyre!( + "failed to extract 'events' from the output of `create-consumer` CLI" + )) + })?; + + let create_consumer_event = events + .iter() + .find(|v| v.get("type").and_then(|v| v.as_str()) == Some("create_consumer")) + .ok_or_else(|| Error::generic(eyre!("failed to extract create_consumer event")))?; + + let attributes = create_consumer_event + .get("attributes") + .and_then(|v| v.as_array()) + .ok_or_else(|| { + Error::generic(eyre!( + "failed to extract attributes from create_consumer event" + )) + })?; + + let consumer_id_attribute = attributes + .iter() + .find(|v| v.get("key").and_then(|v| v.as_str()) == Some("consumer_id")) + .ok_or_else(|| Error::generic(eyre!("failed to extract consumer_id attribute")))?; + + let consumer_id = consumer_id_attribute + .get("value") + .and_then(|v| v.as_str()) + .ok_or_else(|| Error::generic(eyre!("failed to extract consumer_id")))?; + + Ok(consumer_id.to_owned()) +} + +pub fn validator_opt_in( + chain_id: &str, + command_path: &str, + home_path: &str, + rpc_listen_address: &str, + fees: &str, + consumer_id: &str, + pubkey: &str, +) -> Result<(), Error> { + simple_exec( + chain_id, + command_path, + &[ + "tx", + "provider", + "opt-in", + consumer_id, + &format!("'{pubkey}'"), + "--chain-id", + chain_id, + "--from", + "validator", + "--home", + home_path, + "--node", + rpc_listen_address, + "--keyring-backend", + "test", + "--gas", + "2000000", + "--fees", + fees, + "--output", + "json", + "--yes", + ], + )?; + + Ok(()) +} + +pub fn update_consumer( + chain_id: &str, + command_path: &str, + home_path: &str, + rpc_listen_address: &str, + fees: &str, +) -> Result<(), Error> { + let proposal_file = format!("{}/consumer_update_proposal.json", home_path); + + // The submission might fail silently if there is not enough gas + let raw_output = simple_exec( + chain_id, + command_path, + &[ + "tx", + "provider", + "update-consumer", &proposal_file, "--chain-id", chain_id, diff --git a/tools/test-framework/src/chain/ext/bootstrap.rs b/tools/test-framework/src/chain/ext/bootstrap.rs index 4fef50083d..76b90dd186 100644 --- a/tools/test-framework/src/chain/ext/bootstrap.rs +++ b/tools/test-framework/src/chain/ext/bootstrap.rs @@ -15,9 +15,10 @@ use crate::chain::cli::bootstrap::{ start_chain, }; use crate::chain::cli::provider::{ - copy_validator_key_pair, query_consumer_genesis, query_gov_proposal, replace_genesis_state, - submit_consumer_chain_proposal, + copy_validator_key_pair, create_consumer, query_consumer_genesis, query_gov_proposal, + replace_genesis_state, submit_consumer_chain_proposal, update_consumer, validator_opt_in, }; +use crate::chain::cli::query::query_auth_module; use crate::chain::driver::ChainDriver; use crate::chain::exec::simple_exec; use crate::error::{handle_generic_error, Error}; @@ -107,7 +108,21 @@ pub trait ChainBootstrapMethodsExt { &self, consumer_chain_id: &str, fees: &str, - spawn_time: &str, + ) -> Result<(), Error>; + + fn create_permisionless_consumer( + &self, + consumer_chain_id: &str, + fees: &str, + ) -> Result; + + fn validator_opt_in(&self, consumer_chain_id: &str, fees: &str) -> Result<(), Error>; + + fn update_consumer( + &self, + consumer_chain_id: &str, + fees: &str, + validator: &str, ) -> Result<(), Error>; /** @@ -278,8 +293,14 @@ impl ChainBootstrapMethodsExt for ChainDriver { &self, consumer_chain_id: &str, fees: &str, - _spawn_time: &str, ) -> Result<(), Error> { + let gov_address = query_auth_module( + self.chain_id.as_str(), + &self.command_path, + &self.home_path, + &self.rpc_listen_address(), + "gov", + )?; let res = simple_exec( self.chain_id.as_str(), "jq", @@ -294,35 +315,46 @@ impl ChainBootstrapMethodsExt for ChainDriver { spawn_time.pop(); let raw_proposal = r#" { - "title": "Create consumer chain", - "summary": "First consumer chain", - "chain_id": "{consumer_chain_id}", - "initial_height": { - "revision_number": 1, - "revision_height": 1 + "@type": "/interchain_security.ccv.provider.v1.MsgUpdateConsumer", + "consumer_id": "{consumer_chain_id}", + "owner": "{gov_address}", + "metadata": "{\"title\":\"update consumer 0 to top N\",\"authors\":[\"\"],\"summary\":\"update consumer 0 to top N\",\"details\":\"\",\"proposal_forum_url\":\"\",\"vote_option_context\":\"\"}", + "initialization_parameters": { + "initial_height": { + "revision_number": 0, + "revision_height": 1 + }, + "genesis_hash": "2D5C2110941DA54BE07CBB9FACD7E4A2E3253E79BE7BE3E5A1A7BDA518BAA4BE", + "binary_hash": "2D5C2110941DA54BE07CBB9FACD7E4A2E3253E79BE7BE3E5A1A7BDA518BAA4BE", + "spawn_time": "2023-03-11T09:02:14.718477-08:00", + "ccv_timeout_period": "2419200s", + "unbonding_period": "2419200s", + "transfer_timeout_period": "3600s", + "consumer_redistribution_fraction": "0.75", + "blocks_per_distribution_transmission": 1500, + "historical_entries": 1000, + "distribution_transmission_channel": "" + }, + "power_shaping_parameters": { + "top_N": 100, + "validators_power_cap": 0, + "validator_set_cap": 50, + "allowlist": [], + "denylist": [], + "min_stake": 1000, + "allow_inactive_vals": true }, - "genesis_hash": "Z2VuX2hhc2g=", - "binary_hash": "YmluX2hhc2g=", - "spawn_time": "{spawn_time}", - "blocks_per_distribution_transmission": 10, - "consumer_redistribution_fraction": "0.75", - "distribution_transmission_channel": "", - "historical_entries": 10000, - "transfer_timeout_period": 100000000000, - "ccv_timeout_period": 100000000000, - "unbonding_period": 100000000000, - "deposit": "10000001stake", - "top_N": 95, - "validators_power_cap": 0, - "validator_set_cap": 0, - "allowlist": [], - "denylist": [] + "metadata": "ipfs://CID", + "deposit": "10000000stake", + "title": "update consumer 0 to top N", + "summary": "update consumer 0 to top N", + "expedited": false }"#; let proposal = raw_proposal.replace("{consumer_chain_id}", consumer_chain_id); - let proposal = proposal.replace("{spawn_time}", &spawn_time); + let proposal = proposal.replace("{gov_address}", &gov_address); - self.write_file("consumer_proposal.json", &proposal)?; + self.write_file("consumer_proposal_topn.json", &proposal)?; submit_consumer_chain_proposal( self.chain_id.as_str(), @@ -333,6 +365,132 @@ impl ChainBootstrapMethodsExt for ChainDriver { ) } + fn create_permisionless_consumer( + &self, + consumer_chain_id: &str, + fees: &str, + ) -> Result { + let raw_proposal = r#" + { + "chain_id": "{consumer_chain_id}", + "metadata": { + "name": "consumer-1-metadata-name", + "description":"consumer-1-metadata-description", + "metadata": "consumer-1-metadata-metadata" + } + }"#; + + let proposal = raw_proposal.replace("{consumer_chain_id}", consumer_chain_id); + + self.write_file("consumer_proposal.json", &proposal)?; + + create_consumer( + self.chain_id.as_str(), + &self.command_path, + &self.home_path, + &self.rpc_listen_address(), + fees, + ) + } + + fn validator_opt_in(&self, consumer_chain_id: &str, fees: &str) -> Result<(), Error> { + let validator_seed = self.read_file("user1-seed.json")?; + let parsed: serde_json::Value = + serde_json::from_str(&validator_seed).expect("Invalid JSON"); + + let pubkey = if let Some(pubkey) = parsed.get("pubkey") { + pubkey.as_str().unwrap() + } else { + "" + }; + validator_opt_in( + self.chain_id.as_str(), + &self.command_path, + &self.home_path, + &self.rpc_listen_address(), + fees, + consumer_chain_id, + pubkey, + ) + } + + fn update_consumer( + &self, + consumer_chain_id: &str, + fees: &str, + validator: &str, + ) -> Result<(), Error> { + let gov_address = query_auth_module( + self.chain_id.as_str(), + &self.command_path, + &self.home_path, + &self.rpc_listen_address(), + "gov", + )?; + let res = simple_exec( + self.chain_id.as_str(), + "jq", + &[ + "-r", + ".genesis_time", + &format!("{}/config/genesis.json", self.home_path), + ], + )?; + let mut spawn_time = res.stdout; + // Remove newline character + spawn_time.pop(); + let raw_proposal = r#" + { + "consumer_id": "{consumer_chain_id}", + "owner_address": "{validator}", + "new_owner_address": "{gov_address}", + "metadata": { + "name": "consumer-1-metadata-name", + "description":"consumer-1-metadata-description", + "metadata": "consumer-1-metadata-metadata" + }, + "initialization_parameters": { + "initial_height": { + "revision_number": 0, + "revision_height": 1 + }, + "genesis_hash": "2D5C2110941DA54BE07CBB9FACD7E4A2E3253E79BE7BE3E5A1A7BDA518BAA4BE", + "binary_hash": "2D5C2110941DA54BE07CBB9FACD7E4A2E3253E79BE7BE3E5A1A7BDA518BAA4BE", + "spawn_time": "2023-03-11T09:02:14.718477-08:00", + "ccv_timeout_period": 2419200, + "unbonding_period": 2419200, + "transfer_timeout_period": 3600, + "consumer_redistribution_fraction": "0.75", + "blocks_per_distribution_transmission": 1500, + "historical_entries": 1000, + "distribution_transmission_channel": "" + }, + "power_shaping_parameters":{ + "top_N":50, + "validators_power_cap":50, + "validator_set_cap":50, + "allowlist":[], + "denylist":[], + "min_stake": 1000, + "allow_inactive_vals":true + } + }"#; + + let proposal = raw_proposal.replace("{consumer_chain_id}", consumer_chain_id); + let proposal = proposal.replace("{gov_address}", &gov_address); + let proposal = proposal.replace("{validator}", validator); + + self.write_file("consumer_update_proposal.json", &proposal)?; + + update_consumer( + self.chain_id.as_str(), + &self.command_path, + &self.home_path, + &self.rpc_listen_address(), + fees, + ) + } + fn assert_proposal_status( &self, chain_id: &str, diff --git a/tools/test-framework/src/framework/binary/ics.rs b/tools/test-framework/src/framework/binary/ics.rs index 24086eff6c..ea5fe6e15f 100644 --- a/tools/test-framework/src/framework/binary/ics.rs +++ b/tools/test-framework/src/framework/binary/ics.rs @@ -1,4 +1,5 @@ use std::str::FromStr; +use std::thread; use crate::bootstrap::consumer::bootstrap_consumer_node; use crate::bootstrap::single::bootstrap_single_node; @@ -71,12 +72,24 @@ where let chain_type = ChainType::from_str(&builder.command_paths[1])?; let chain_id = chain_type.chain_id("consumer", false); - node_a.chain_driver.submit_consumer_chain_proposal( - chain_id.as_str(), + let consumer_id = node_a + .chain_driver + .create_permisionless_consumer(chain_id.as_str(), &provider_fee)?; + + thread::sleep(std::time::Duration::from_secs(3)); + + node_a.chain_driver.update_consumer( + &consumer_id, &provider_fee, - "2023-05-31T12:09:47.048227Z", + node_a.wallets.validator.address.as_str(), )?; + thread::sleep(std::time::Duration::from_secs(3)); + + node_a + .chain_driver + .submit_consumer_chain_proposal(&consumer_id, &provider_fee)?; + node_a.chain_driver.assert_proposal_status( node_a.chain_driver.chain_id.as_str(), &node_a.chain_driver.command_path, @@ -104,9 +117,11 @@ where "1", )?; + thread::sleep(std::time::Duration::from_secs(3)); + let node_b = bootstrap_consumer_node( builder, - "consumer", + &consumer_id, &node_a, |config| self.test.get_overrides().modify_node_config(config), |genesis| self.test.get_overrides().modify_genesis_file(genesis), From 42693fbe79d40c90e18400dc8fa8c760d00f7d3c Mon Sep 17 00:00:00 2001 From: Luca Joss Date: Thu, 3 Oct 2024 13:20:54 +0200 Subject: [PATCH 02/11] Add Gaia v20 to CI and update Gaia used in CI tests --- .github/workflows/integration.yaml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 59f90f2caf..533546ab46 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -50,6 +50,11 @@ jobs: account_prefix: cosmos native_token: stake features: forward-packet,clean-workers,ica,ics29-fee + - package: gaia20 + command: gaiad + account_prefix: cosmos + native_token: stake + features: forward-packet,clean-workers,ica,ics29-fee - package: ibc-go-v6-simapp command: simd account_prefix: cosmos @@ -178,7 +183,7 @@ jobs: fail-fast: false matrix: chain: - - package: .#gaia18 .#stride + - package: .#gaia20 .#stride command: gaiad,strided account_prefix: cosmos,stride - package: .#gaia18 .#neutron @@ -223,7 +228,7 @@ jobs: fail-fast: false matrix: chain: - - package: .#gaia18 .#stride-no-admin + - package: .#gaia20 .#stride-no-admin command: gaiad,strided account_prefix: cosmos,stride steps: @@ -264,7 +269,7 @@ jobs: fail-fast: false matrix: chain: - - package: .#celestia .#gaia18 + - package: .#celestia .#gaia20 command: celestia-appd,gaiad account_prefix: celestia,cosmos native_token: utia,stake From 525f21b2c1b1e0679148be2791963d704c2f4b6f Mon Sep 17 00:00:00 2001 From: Luca Joss Date: Fri, 4 Oct 2024 15:49:09 +0200 Subject: [PATCH 03/11] Correctly create consumer chain and opt-in validator --- Cargo.lock | 62 +++++++ tools/test-framework/Cargo.toml | 2 + .../test-framework/src/bootstrap/consumer.rs | 23 +++ .../test-framework/src/chain/cli/provider.rs | 2 +- .../test-framework/src/chain/ext/bootstrap.rs | 152 ++++-------------- .../src/framework/binary/ics.rs | 36 +---- 6 files changed, 120 insertions(+), 157 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index bc73115080..983064f339 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,21 @@ dependencies = [ "memchr", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.15" @@ -506,6 +521,20 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "wasm-bindgen", + "windows-targets 0.52.6", +] + [[package]] name = "clap" version = "3.2.25" @@ -1508,6 +1537,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "ibc-chain-registry" version = "0.29.3" @@ -1744,6 +1796,7 @@ dependencies = [ name = "ibc-test-framework" version = "0.29.3" dependencies = [ + "chrono", "color-eyre", "crossbeam-channel", "eyre", @@ -4216,6 +4269,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.48.0" diff --git a/tools/test-framework/Cargo.toml b/tools/test-framework/Cargo.toml index 34f5a08182..5c71770833 100644 --- a/tools/test-framework/Cargo.toml +++ b/tools/test-framework/Cargo.toml @@ -42,3 +42,5 @@ toml = { workspace = true } tonic = { workspace = true, features = ["tls", "tls-roots"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } + +chrono = "0.4.38" diff --git a/tools/test-framework/src/bootstrap/consumer.rs b/tools/test-framework/src/bootstrap/consumer.rs index 3899d46f5b..72591b992d 100644 --- a/tools/test-framework/src/bootstrap/consumer.rs +++ b/tools/test-framework/src/bootstrap/consumer.rs @@ -8,7 +8,9 @@ use std::time::Duration; use tracing::info; use crate::chain::builder::ChainBuilder; +use crate::chain::cli::provider::validator_opt_in; use crate::chain::config; +use crate::chain::exec::simple_exec; use crate::chain::ext::bootstrap::ChainBootstrapMethodsExt; use crate::error::Error; use crate::prelude::{ChainDriver, Denom, FullNode, TestWallets, Token}; @@ -22,6 +24,7 @@ pub fn bootstrap_consumer_node( genesis_modifier: impl FnOnce(&mut serde_json::Value) -> Result<(), Error>, chain_number: usize, provider_chain_driver: &ChainDriver, + provider_fee: &String, ) -> Result { let stake_denom = Denom::base("stake"); @@ -53,6 +56,25 @@ pub fn bootstrap_consumer_node( chain_driver.add_genesis_account(&user2.address, &[&initial_stake, &initial_coin])?; // Wait for the consumer chain to be initialized before querying the genesis + thread::sleep(Duration::from_secs(5)); + + let show_validator_output = simple_exec( + "test", + &chain_driver.command_path, + &["comet", "show-validator", "--home", &chain_driver.home_path], + )?; + + validator_opt_in( + provider_chain_driver.chain_id.as_str(), + &provider_chain_driver.command_path, + &provider_chain_driver.home_path, + &provider_chain_driver.rpc_listen_address(), + provider_fee, + prefix, + &show_validator_output.stdout, + )?; + + // Wait enough time so that the spawn_time passed thread::sleep(Duration::from_secs(30)); node_a @@ -139,6 +161,7 @@ pub fn bootstrap_consumer_node( wallets, process: Arc::new(RwLock::new(process)), }; + thread::sleep(Duration::from_secs(180)); Ok(node) } diff --git a/tools/test-framework/src/chain/cli/provider.rs b/tools/test-framework/src/chain/cli/provider.rs index 31a8a7f0c4..66138967a3 100644 --- a/tools/test-framework/src/chain/cli/provider.rs +++ b/tools/test-framework/src/chain/cli/provider.rs @@ -211,7 +211,7 @@ pub fn validator_opt_in( "provider", "opt-in", consumer_id, - &format!("'{pubkey}'"), + pubkey, "--chain-id", chain_id, "--from", diff --git a/tools/test-framework/src/chain/ext/bootstrap.rs b/tools/test-framework/src/chain/ext/bootstrap.rs index 76b90dd186..b814a4bc06 100644 --- a/tools/test-framework/src/chain/ext/bootstrap.rs +++ b/tools/test-framework/src/chain/ext/bootstrap.rs @@ -1,3 +1,6 @@ +use chrono::DateTime; +use chrono::Duration as ChronoDuration; +use chrono::Utc; use core::str::FromStr; use eyre::eyre; use hdpath::StandardHDPath; @@ -16,7 +19,7 @@ use crate::chain::cli::bootstrap::{ }; use crate::chain::cli::provider::{ copy_validator_key_pair, create_consumer, query_consumer_genesis, query_gov_proposal, - replace_genesis_state, submit_consumer_chain_proposal, update_consumer, validator_opt_in, + replace_genesis_state, update_consumer, validator_opt_in, }; use crate::chain::cli::query::query_auth_module; use crate::chain::driver::ChainDriver; @@ -101,15 +104,6 @@ pub trait ChainBootstrapMethodsExt { */ fn start(&self) -> Result; - /** - Submit a consumer chain proposal. - */ - fn submit_consumer_chain_proposal( - &self, - consumer_chain_id: &str, - fees: &str, - ) -> Result<(), Error>; - fn create_permisionless_consumer( &self, consumer_chain_id: &str, @@ -289,82 +283,6 @@ impl ChainBootstrapMethodsExt for ChainDriver { ) } - fn submit_consumer_chain_proposal( - &self, - consumer_chain_id: &str, - fees: &str, - ) -> Result<(), Error> { - let gov_address = query_auth_module( - self.chain_id.as_str(), - &self.command_path, - &self.home_path, - &self.rpc_listen_address(), - "gov", - )?; - let res = simple_exec( - self.chain_id.as_str(), - "jq", - &[ - "-r", - ".genesis_time", - &format!("{}/config/genesis.json", self.home_path), - ], - )?; - let mut spawn_time = res.stdout; - // Remove newline character - spawn_time.pop(); - let raw_proposal = r#" - { - "@type": "/interchain_security.ccv.provider.v1.MsgUpdateConsumer", - "consumer_id": "{consumer_chain_id}", - "owner": "{gov_address}", - "metadata": "{\"title\":\"update consumer 0 to top N\",\"authors\":[\"\"],\"summary\":\"update consumer 0 to top N\",\"details\":\"\",\"proposal_forum_url\":\"\",\"vote_option_context\":\"\"}", - "initialization_parameters": { - "initial_height": { - "revision_number": 0, - "revision_height": 1 - }, - "genesis_hash": "2D5C2110941DA54BE07CBB9FACD7E4A2E3253E79BE7BE3E5A1A7BDA518BAA4BE", - "binary_hash": "2D5C2110941DA54BE07CBB9FACD7E4A2E3253E79BE7BE3E5A1A7BDA518BAA4BE", - "spawn_time": "2023-03-11T09:02:14.718477-08:00", - "ccv_timeout_period": "2419200s", - "unbonding_period": "2419200s", - "transfer_timeout_period": "3600s", - "consumer_redistribution_fraction": "0.75", - "blocks_per_distribution_transmission": 1500, - "historical_entries": 1000, - "distribution_transmission_channel": "" - }, - "power_shaping_parameters": { - "top_N": 100, - "validators_power_cap": 0, - "validator_set_cap": 50, - "allowlist": [], - "denylist": [], - "min_stake": 1000, - "allow_inactive_vals": true - }, - "metadata": "ipfs://CID", - "deposit": "10000000stake", - "title": "update consumer 0 to top N", - "summary": "update consumer 0 to top N", - "expedited": false - }"#; - - let proposal = raw_proposal.replace("{consumer_chain_id}", consumer_chain_id); - let proposal = proposal.replace("{gov_address}", &gov_address); - - self.write_file("consumer_proposal_topn.json", &proposal)?; - - submit_consumer_chain_proposal( - self.chain_id.as_str(), - &self.command_path, - &self.home_path, - &self.rpc_listen_address(), - fees, - ) - } - fn create_permisionless_consumer( &self, consumer_chain_id: &str, @@ -377,10 +295,30 @@ impl ChainBootstrapMethodsExt for ChainDriver { "name": "consumer-1-metadata-name", "description":"consumer-1-metadata-description", "metadata": "consumer-1-metadata-metadata" + }, + "initialization_parameters": { + "initial_height": { + "revision_number": 0, + "revision_height": 1 + }, + "genesis_hash": "Z2VuX2hhc2g=", + "binary_hash": "YmluX2hhc2g=", + "spawn_time": "{spawn_time}", + "unbonding_period": 1728000000000000, + "ccv_timeout_period": 2419200000000000, + "transfer_timeout_period": 1800000000000, + "consumer_redistribution_fraction": "0.75", + "blocks_per_distribution_transmission": 1000, + "historical_entries": 10000, + "distribution_transmission_channel": "" } }"#; + let current_time: DateTime = Utc::now(); + let future_time = current_time + ChronoDuration::seconds(30); + let spawn_time = future_time.to_rfc3339(); let proposal = raw_proposal.replace("{consumer_chain_id}", consumer_chain_id); + let proposal = proposal.replace("{spawn_time}", &spawn_time); self.write_file("consumer_proposal.json", &proposal)?; @@ -394,15 +332,12 @@ impl ChainBootstrapMethodsExt for ChainDriver { } fn validator_opt_in(&self, consumer_chain_id: &str, fees: &str) -> Result<(), Error> { - let validator_seed = self.read_file("user1-seed.json")?; - let parsed: serde_json::Value = - serde_json::from_str(&validator_seed).expect("Invalid JSON"); - - let pubkey = if let Some(pubkey) = parsed.get("pubkey") { - pubkey.as_str().unwrap() - } else { - "" - }; + let show_validator_output = simple_exec( + "test", + &self.command_path, + &["comet", "show-validator", "--home", &self.home_path], + )?; + validator_opt_in( self.chain_id.as_str(), &self.command_path, @@ -410,7 +345,7 @@ impl ChainBootstrapMethodsExt for ChainDriver { &self.rpc_listen_address(), fees, consumer_chain_id, - pubkey, + &show_validator_output.stdout, ) } @@ -448,31 +383,6 @@ impl ChainBootstrapMethodsExt for ChainDriver { "name": "consumer-1-metadata-name", "description":"consumer-1-metadata-description", "metadata": "consumer-1-metadata-metadata" - }, - "initialization_parameters": { - "initial_height": { - "revision_number": 0, - "revision_height": 1 - }, - "genesis_hash": "2D5C2110941DA54BE07CBB9FACD7E4A2E3253E79BE7BE3E5A1A7BDA518BAA4BE", - "binary_hash": "2D5C2110941DA54BE07CBB9FACD7E4A2E3253E79BE7BE3E5A1A7BDA518BAA4BE", - "spawn_time": "2023-03-11T09:02:14.718477-08:00", - "ccv_timeout_period": 2419200, - "unbonding_period": 2419200, - "transfer_timeout_period": 3600, - "consumer_redistribution_fraction": "0.75", - "blocks_per_distribution_transmission": 1500, - "historical_entries": 1000, - "distribution_transmission_channel": "" - }, - "power_shaping_parameters":{ - "top_N":50, - "validators_power_cap":50, - "validator_set_cap":50, - "allowlist":[], - "denylist":[], - "min_stake": 1000, - "allow_inactive_vals":true } }"#; diff --git a/tools/test-framework/src/framework/binary/ics.rs b/tools/test-framework/src/framework/binary/ics.rs index ea5fe6e15f..e620ccfedc 100644 --- a/tools/test-framework/src/framework/binary/ics.rs +++ b/tools/test-framework/src/framework/binary/ics.rs @@ -5,14 +5,12 @@ use crate::bootstrap::consumer::bootstrap_consumer_node; use crate::bootstrap::single::bootstrap_single_node; use crate::chain::builder::ChainBuilder; use crate::chain::chain_type::ChainType; -use crate::chain::cli::upgrade::vote_proposal; use crate::chain::ext::bootstrap::ChainBootstrapMethodsExt; use crate::error::Error; use crate::framework::base::{run_basic_test, BasicTest, HasOverrides, TestConfigOverride}; use crate::framework::binary::node::{NodeConfigOverride, NodeGenesisOverride}; use crate::prelude::FullNode; use crate::types::config::TestConfig; -use crate::util::proposal_status::ProposalStatus; /** Runs a test case that implements [`InterchainSecurityChainTest`]. @@ -86,39 +84,6 @@ where thread::sleep(std::time::Duration::from_secs(3)); - node_a - .chain_driver - .submit_consumer_chain_proposal(&consumer_id, &provider_fee)?; - - node_a.chain_driver.assert_proposal_status( - node_a.chain_driver.chain_id.as_str(), - &node_a.chain_driver.command_path, - &node_a.chain_driver.home_path, - &node_a.chain_driver.rpc_listen_address(), - ProposalStatus::VotingPeriod, - "1", - )?; - - vote_proposal( - node_a.chain_driver.chain_id.as_str(), - &node_a.chain_driver.command_path, - &node_a.chain_driver.home_path, - &node_a.chain_driver.rpc_listen_address(), - &provider_fee, - "1", - )?; - - node_a.chain_driver.assert_proposal_status( - node_a.chain_driver.chain_id.as_str(), - &node_a.chain_driver.command_path, - &node_a.chain_driver.home_path, - &node_a.chain_driver.rpc_listen_address(), - ProposalStatus::Passed, - "1", - )?; - - thread::sleep(std::time::Duration::from_secs(3)); - let node_b = bootstrap_consumer_node( builder, &consumer_id, @@ -127,6 +92,7 @@ where |genesis| self.test.get_overrides().modify_genesis_file(genesis), 1, &node_a.chain_driver, + &provider_fee, )?; let _node_process_a = node_a.process.clone(); From c7a4e7e454d8e316ff14fb2d21257823c8d47e8c Mon Sep 17 00:00:00 2001 From: Luca Joss Date: Tue, 15 Oct 2024 16:14:04 +0200 Subject: [PATCH 04/11] WIP: Fix CCQs between Stride and Gaia --- .../test-framework/src/bootstrap/consumer.rs | 11 +-- tools/test-framework/src/bootstrap/single.rs | 1 + .../test-framework/src/chain/cli/bootstrap.rs | 8 ++ .../test-framework/src/chain/cli/provider.rs | 25 +++++- tools/test-framework/src/chain/config.rs | 37 ++++++++ .../test-framework/src/chain/ext/bootstrap.rs | 84 ++++++++++++++++--- 6 files changed, 143 insertions(+), 23 deletions(-) diff --git a/tools/test-framework/src/bootstrap/consumer.rs b/tools/test-framework/src/bootstrap/consumer.rs index 72591b992d..c473ab6c28 100644 --- a/tools/test-framework/src/bootstrap/consumer.rs +++ b/tools/test-framework/src/bootstrap/consumer.rs @@ -10,7 +10,6 @@ use tracing::info; use crate::chain::builder::ChainBuilder; use crate::chain::cli::provider::validator_opt_in; use crate::chain::config; -use crate::chain::exec::simple_exec; use crate::chain::ext::bootstrap::ChainBootstrapMethodsExt; use crate::error::Error; use crate::prelude::{ChainDriver, Denom, FullNode, TestWallets, Token}; @@ -41,7 +40,7 @@ pub fn bootstrap_consumer_node( )))?; let initial_coin = Token::new(denom.clone(), initial_amount); - let chain_driver = builder.new_chain(prefix, false, chain_number)?; + let chain_driver = builder.new_chain("consumer", false, chain_number)?; chain_driver.initialize()?; @@ -58,12 +57,6 @@ pub fn bootstrap_consumer_node( // Wait for the consumer chain to be initialized before querying the genesis thread::sleep(Duration::from_secs(5)); - let show_validator_output = simple_exec( - "test", - &chain_driver.command_path, - &["comet", "show-validator", "--home", &chain_driver.home_path], - )?; - validator_opt_in( provider_chain_driver.chain_id.as_str(), &provider_chain_driver.command_path, @@ -71,7 +64,6 @@ pub fn bootstrap_consumer_node( &provider_chain_driver.rpc_listen_address(), provider_fee, prefix, - &show_validator_output.stdout, )?; // Wait enough time so that the spawn_time passed @@ -161,7 +153,6 @@ pub fn bootstrap_consumer_node( wallets, process: Arc::new(RwLock::new(process)), }; - thread::sleep(Duration::from_secs(180)); Ok(node) } diff --git a/tools/test-framework/src/bootstrap/single.rs b/tools/test-framework/src/bootstrap/single.rs index 508d0b28ba..ff018a7021 100644 --- a/tools/test-framework/src/bootstrap/single.rs +++ b/tools/test-framework/src/bootstrap/single.rs @@ -100,6 +100,7 @@ pub fn bootstrap_single_node( config::set_rpc_port(config, chain_driver.rpc_port)?; config::set_p2p_port(config, chain_driver.p2p_port)?; config::set_pprof_port(config, chain_driver.pprof_port)?; + config::set_block_sync(config, true)?; config::set_timeout_commit(config, Duration::from_secs(1))?; config::set_timeout_propose(config, Duration::from_secs(1))?; config::set_mode(config, "validator")?; diff --git a/tools/test-framework/src/chain/cli/bootstrap.rs b/tools/test-framework/src/chain/cli/bootstrap.rs index fbe530e2ff..e53ffd1d20 100644 --- a/tools/test-framework/src/chain/cli/bootstrap.rs +++ b/tools/test-framework/src/chain/cli/bootstrap.rs @@ -23,6 +23,14 @@ pub fn initialize(chain_id: &str, command_path: &str, home_path: &str) -> Result chain_id, ], )?; + simple_exec( + chain_id, + command_path, + &[ + "version", + "--long", + ], + )?; Ok(()) } diff --git a/tools/test-framework/src/chain/cli/provider.rs b/tools/test-framework/src/chain/cli/provider.rs index 66138967a3..8a323954f5 100644 --- a/tools/test-framework/src/chain/cli/provider.rs +++ b/tools/test-framework/src/chain/cli/provider.rs @@ -47,6 +47,8 @@ pub fn submit_consumer_chain_proposal( let output: serde_json::Value = serde_json::from_str(&raw_output.stdout).map_err(handle_generic_error)?; + let txhash = output.get("txhash").and_then(|code| code.as_str()).ok_or_else(|| Error::generic(eyre!("failed to extract 'code' from 'tx gov submit-legacy-proposal consumer-addition' command")))?; + let output_code = output.get("code").and_then(|code| code.as_u64()).ok_or_else(|| Error::generic(eyre!("failed to extract 'code' from 'tx gov submit-legacy-proposal consumer-addition' command")))?; // Proposal submission might fail due to account sequence error. @@ -82,6 +84,27 @@ pub fn submit_consumer_chain_proposal( )?; } + thread::sleep(core::time::Duration::from_secs(3)); + + let query_txhash_output = simple_exec( + chain_id, + command_path, + &[ + "--home", + home_path, + "--node", + rpc_listen_address, + "query", + "tx", + "--type=hash", + txhash, + "--output", + "json", + ], + )?; + + tracing::warn!("{}", query_txhash_output.stdout); + Ok(()) } @@ -201,7 +224,6 @@ pub fn validator_opt_in( rpc_listen_address: &str, fees: &str, consumer_id: &str, - pubkey: &str, ) -> Result<(), Error> { simple_exec( chain_id, @@ -211,7 +233,6 @@ pub fn validator_opt_in( "provider", "opt-in", consumer_id, - pubkey, "--chain-id", chain_id, "--from", diff --git a/tools/test-framework/src/chain/config.rs b/tools/test-framework/src/chain/config.rs index 9d98f9702d..1c31d02d18 100644 --- a/tools/test-framework/src/chain/config.rs +++ b/tools/test-framework/src/chain/config.rs @@ -92,6 +92,19 @@ pub fn set_pprof_port(config: &mut Value, port: u16) -> Result<(), Error> { Ok(()) } +/// Set the `pprof_laddr` field in the full node config. +pub fn set_block_sync(config: &mut Value, value: bool) -> Result<(), Error> { + config + .as_table_mut() + .ok_or_else(|| eyre!("expect object"))? + .insert( + "block_sync".to_string(), + value.into(), + ); + + Ok(()) +} + pub fn set_mempool_version(config: &mut Value, version: &str) -> Result<(), Error> { config .get_mut("mempool") @@ -213,6 +226,30 @@ pub fn add_allow_message_interchainaccounts( Ok(()) } +pub fn add_pub_key_types(genesis: &mut serde_json::Value, pub_key_type: &str) -> Result<(), Error> { + let pub_key_types = if let Some(consensus) = genesis.get_mut("consensus") { + consensus.get_mut("params") + .and_then(|params| params.get_mut("validator")) + .and_then(|validator| validator.get_mut("pub_key_types")) + .and_then(|pub_key_types| pub_key_types.as_array_mut()) + .ok_or_else(|| { + eyre!("failed to retrieve pub_key_types as a vector, in the genesis file under `consensus`") + })? + } else { + genesis.get_mut("consensus_params") + .and_then(|consensus_params| consensus_params.get_mut("validator")) + .and_then(|validator| validator.get_mut("pub_key_types")) + .and_then(|pub_key_types| pub_key_types.as_array_mut()) + .ok_or_else(|| { + eyre!("failed to retrieve pub_key_types as a vector, in the genesis file under `consensus_params`") + })? + }; + + pub_key_types.push(serde_json::Value::String(pub_key_type.to_string())); + + Ok(()) +} + pub fn add_allow_message_interchainquery( genesis: &mut serde_json::Value, message: &str, diff --git a/tools/test-framework/src/chain/ext/bootstrap.rs b/tools/test-framework/src/chain/ext/bootstrap.rs index b814a4bc06..ed212bbb90 100644 --- a/tools/test-framework/src/chain/ext/bootstrap.rs +++ b/tools/test-framework/src/chain/ext/bootstrap.rs @@ -17,6 +17,7 @@ use crate::chain::cli::bootstrap::{ add_genesis_account, add_genesis_validator, add_wallet, collect_gen_txs, initialize, start_chain, }; +use crate::chain::cli::provider::submit_consumer_chain_proposal; use crate::chain::cli::provider::{ copy_validator_key_pair, create_consumer, query_consumer_genesis, query_gov_proposal, replace_genesis_state, update_consumer, validator_opt_in, @@ -104,6 +105,15 @@ pub trait ChainBootstrapMethodsExt { */ fn start(&self) -> Result; + /** + Submit a consumer chain proposal. + */ + fn submit_consumer_chain_proposal( + &self, + consumer_chain_id: &str, + fees: &str, + ) -> Result<(), Error>; + fn create_permisionless_consumer( &self, consumer_chain_id: &str, @@ -283,6 +293,65 @@ impl ChainBootstrapMethodsExt for ChainDriver { ) } + fn submit_consumer_chain_proposal( + &self, + consumer_chain_id: &str, + fees: &str, + ) -> Result<(), Error> { + + let raw_proposal = r#" + { + "chain_id": "{consumer_chain_id}", + "initialization_parameters": { + "initial_height": { + "revision_number": 0, + "revision_height": 1 + }, + "genesis_hash": "Z2VuX2hhc2g=", + "binary_hash": "YmluX2hhc2g=", + "spawn_time": "{spawn_time}", + "unbonding_period": 100000000000, + "ccv_timeout_period": 100000000000, + "transfer_timeout_period": 100000000000, + "consumer_redistribution_fraction": "0.75", + "blocks_per_distribution_transmission": 10, + "historical_entries": 10000, + "distribution_transmission_channel": "" + }, + "power_shaping_parameters": { + "top_N": 100, + "validators_power_cap": 0, + "validator_set_cap": 0, + "allowlist": [], + "denylist": [], + "min_stake": 0, + "allow_inactive_vals": false + }, + "metadata": "ipfs://CID", + "deposit": "10000000stake", + "title": "\"update consumer 0 to top N\"", + "summary": "\"update consumer 0 to top N\"", + "expedited": false + }"#; + + let current_time: DateTime = Utc::now(); + let future_time = current_time + ChronoDuration::seconds(30); + let spawn_time = future_time.to_rfc3339(); + + let proposal = raw_proposal.replace("{consumer_chain_id}", consumer_chain_id); + let proposal = proposal.replace("{spawn_time}", &spawn_time); + + self.write_file("consumer_proposal_topn.json", &proposal)?; + + submit_consumer_chain_proposal( + self.chain_id.as_str(), + &self.command_path, + &self.home_path, + &self.rpc_listen_address(), + fees, + ) + } + fn create_permisionless_consumer( &self, consumer_chain_id: &str, @@ -304,11 +373,11 @@ impl ChainBootstrapMethodsExt for ChainDriver { "genesis_hash": "Z2VuX2hhc2g=", "binary_hash": "YmluX2hhc2g=", "spawn_time": "{spawn_time}", - "unbonding_period": 1728000000000000, - "ccv_timeout_period": 2419200000000000, - "transfer_timeout_period": 1800000000000, + "unbonding_period": 100000000000, + "ccv_timeout_period": 100000000000, + "transfer_timeout_period": 100000000000, "consumer_redistribution_fraction": "0.75", - "blocks_per_distribution_transmission": 1000, + "blocks_per_distribution_transmission": 10, "historical_entries": 10000, "distribution_transmission_channel": "" } @@ -332,12 +401,6 @@ impl ChainBootstrapMethodsExt for ChainDriver { } fn validator_opt_in(&self, consumer_chain_id: &str, fees: &str) -> Result<(), Error> { - let show_validator_output = simple_exec( - "test", - &self.command_path, - &["comet", "show-validator", "--home", &self.home_path], - )?; - validator_opt_in( self.chain_id.as_str(), &self.command_path, @@ -345,7 +408,6 @@ impl ChainBootstrapMethodsExt for ChainDriver { &self.rpc_listen_address(), fees, consumer_chain_id, - &show_validator_output.stdout, ) } From 50a7c028c31e3108f238aabe2299b754d00cb4c0 Mon Sep 17 00:00:00 2001 From: Luca Joss Date: Tue, 15 Oct 2024 17:12:02 +0200 Subject: [PATCH 05/11] Add metrics for ICS31 cross chain queries --- .../relayer/src/worker/cross_chain_query.rs | 19 +++++- crates/telemetry/src/state.rs | 59 +++++++++++++++++++ 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/crates/relayer/src/worker/cross_chain_query.rs b/crates/relayer/src/worker/cross_chain_query.rs index 811bb14b36..d6293ebefc 100644 --- a/crates/relayer/src/worker/cross_chain_query.rs +++ b/crates/relayer/src/worker/cross_chain_query.rs @@ -8,6 +8,7 @@ use crate::error::Error; use crate::event::IbcEventWithHeight; use crate::foreign_client::ForeignClient; use crate::object::CrossChainQuery; +use crate::telemetry; use crate::util::task::{spawn_background_task, Next, TaskError, TaskHandle}; use crate::worker::WorkerCmd; @@ -74,6 +75,12 @@ fn handle_cross_chain_query( // Handle of queried chain has to query data from it's RPC info!("request: {}", cross_chain_query.short_name()); + telemetry!( + cross_chain_queries, + &cross_chain_query.src_chain_id, + &cross_chain_query.dst_chain_id, + queries.len() + ); let response = chain_b_handle.cross_chain_query(queries); if let Ok(cross_chain_query_responses) = response { // Run only when cross chain query response is not empty @@ -124,12 +131,22 @@ fn handle_cross_chain_query( ); } - chain_a_handle + let _ccq_responses = chain_a_handle .send_messages_and_wait_check_tx(TrackedMsgs::new_uuid( chain_a_msgs, Uuid::new_v4(), )) .map_err(|_| TaskError::Ignore(RunError::query()))?; + + telemetry!( + cross_chain_query_responses, + &cross_chain_query.dst_chain_id, + &cross_chain_query.src_chain_id, + _ccq_responses + .iter() + .map(|ccq_response| ccq_response.code) + .collect() + ); } } } diff --git a/crates/telemetry/src/state.rs b/crates/telemetry/src/state.rs index e5a9c9cf96..136b414077 100644 --- a/crates/telemetry/src/state.rs +++ b/crates/telemetry/src/state.rs @@ -215,6 +215,15 @@ pub struct TelemetryState { /// Number of ICS-20 packets filtered because the memo and/or the receiver fields were exceeding the configured limits filtered_packets: Counter, + + /// Observed ICS31 CrossChainQueries + cross_chain_queries: Counter, + + /// Observed ICS31 CrossChainQuery successful Responses + cross_chain_query_responses: Counter, + + /// Observed ICS31 CrossChainQuery error Responses + cross_chain_query_error_responses: Counter, } impl TelemetryState { @@ -423,6 +432,21 @@ impl TelemetryState { .u64_counter("filtered_packets") .with_description("Number of ICS-20 packets filtered because the memo and/or the receiver fields were exceeding the configured limits") .init(), + + cross_chain_queries: meter + .u64_counter("cross_chain_queries") + .with_description("Number of ICS-31 queries received") + .init(), + + cross_chain_query_responses: meter + .u64_counter("cross_chain_query_responses") + .with_description("Number of ICS-31 successful query responses") + .init(), + + cross_chain_query_error_responses: meter + .u64_counter("cross_chain_query_error_responses") + .with_description("Number of ICS-31 error query responses") + .init(), } } @@ -1236,6 +1260,41 @@ impl TelemetryState { self.filtered_packets.add(&cx, count, labels); } } + + pub fn cross_chain_queries(&self, src_chain: &ChainId, dst_chain: &ChainId, count: usize) { + let cx = Context::current(); + + if count > 0 { + let labels = &[ + KeyValue::new("src_chain", src_chain.to_string()), + KeyValue::new("dst_chain", dst_chain.to_string()), + ]; + + self.cross_chain_queries.add(&cx, count as u64, labels); + } + } + + pub fn cross_chain_query_responses( + &self, + src_chain: &ChainId, + dst_chain: &ChainId, + ccq_responses_codes: Vec, + ) { + let cx = Context::current(); + + let labels = &[ + KeyValue::new("src_chain", src_chain.to_string()), + KeyValue::new("dst_chain", dst_chain.to_string()), + ]; + + for code in ccq_responses_codes.iter() { + if code.is_ok() { + self.cross_chain_query_responses.add(&cx, 1, labels); + } else { + self.cross_chain_query_error_responses.add(&cx, 1, labels); + } + } + } } use std::sync::Arc; From dff8d3ec83b08d7e1d3e722e4bc9e7d71c64df0e Mon Sep 17 00:00:00 2001 From: Luca Joss Date: Tue, 15 Oct 2024 17:12:14 +0200 Subject: [PATCH 06/11] Remove unnecessary code --- .../test-framework/src/chain/cli/bootstrap.rs | 8 ----- .../test-framework/src/chain/cli/provider.rs | 21 -------------- tools/test-framework/src/chain/config.rs | 29 +------------------ .../test-framework/src/chain/ext/bootstrap.rs | 1 - 4 files changed, 1 insertion(+), 58 deletions(-) diff --git a/tools/test-framework/src/chain/cli/bootstrap.rs b/tools/test-framework/src/chain/cli/bootstrap.rs index e53ffd1d20..fbe530e2ff 100644 --- a/tools/test-framework/src/chain/cli/bootstrap.rs +++ b/tools/test-framework/src/chain/cli/bootstrap.rs @@ -23,14 +23,6 @@ pub fn initialize(chain_id: &str, command_path: &str, home_path: &str) -> Result chain_id, ], )?; - simple_exec( - chain_id, - command_path, - &[ - "version", - "--long", - ], - )?; Ok(()) } diff --git a/tools/test-framework/src/chain/cli/provider.rs b/tools/test-framework/src/chain/cli/provider.rs index 8a323954f5..ed5169f91f 100644 --- a/tools/test-framework/src/chain/cli/provider.rs +++ b/tools/test-framework/src/chain/cli/provider.rs @@ -84,27 +84,6 @@ pub fn submit_consumer_chain_proposal( )?; } - thread::sleep(core::time::Duration::from_secs(3)); - - let query_txhash_output = simple_exec( - chain_id, - command_path, - &[ - "--home", - home_path, - "--node", - rpc_listen_address, - "query", - "tx", - "--type=hash", - txhash, - "--output", - "json", - ], - )?; - - tracing::warn!("{}", query_txhash_output.stdout); - Ok(()) } diff --git a/tools/test-framework/src/chain/config.rs b/tools/test-framework/src/chain/config.rs index 1c31d02d18..c81d904480 100644 --- a/tools/test-framework/src/chain/config.rs +++ b/tools/test-framework/src/chain/config.rs @@ -97,10 +97,7 @@ pub fn set_block_sync(config: &mut Value, value: bool) -> Result<(), Error> { config .as_table_mut() .ok_or_else(|| eyre!("expect object"))? - .insert( - "block_sync".to_string(), - value.into(), - ); + .insert("block_sync".to_string(), value.into()); Ok(()) } @@ -226,30 +223,6 @@ pub fn add_allow_message_interchainaccounts( Ok(()) } -pub fn add_pub_key_types(genesis: &mut serde_json::Value, pub_key_type: &str) -> Result<(), Error> { - let pub_key_types = if let Some(consensus) = genesis.get_mut("consensus") { - consensus.get_mut("params") - .and_then(|params| params.get_mut("validator")) - .and_then(|validator| validator.get_mut("pub_key_types")) - .and_then(|pub_key_types| pub_key_types.as_array_mut()) - .ok_or_else(|| { - eyre!("failed to retrieve pub_key_types as a vector, in the genesis file under `consensus`") - })? - } else { - genesis.get_mut("consensus_params") - .and_then(|consensus_params| consensus_params.get_mut("validator")) - .and_then(|validator| validator.get_mut("pub_key_types")) - .and_then(|pub_key_types| pub_key_types.as_array_mut()) - .ok_or_else(|| { - eyre!("failed to retrieve pub_key_types as a vector, in the genesis file under `consensus_params`") - })? - }; - - pub_key_types.push(serde_json::Value::String(pub_key_type.to_string())); - - Ok(()) -} - pub fn add_allow_message_interchainquery( genesis: &mut serde_json::Value, message: &str, diff --git a/tools/test-framework/src/chain/ext/bootstrap.rs b/tools/test-framework/src/chain/ext/bootstrap.rs index ed212bbb90..07103fcd05 100644 --- a/tools/test-framework/src/chain/ext/bootstrap.rs +++ b/tools/test-framework/src/chain/ext/bootstrap.rs @@ -298,7 +298,6 @@ impl ChainBootstrapMethodsExt for ChainDriver { consumer_chain_id: &str, fees: &str, ) -> Result<(), Error> { - let raw_proposal = r#" { "chain_id": "{consumer_chain_id}", From 82f1448d9f048f87fbd15f597d6abed42272b11e Mon Sep 17 00:00:00 2001 From: Luca Joss Date: Tue, 15 Oct 2024 17:15:00 +0200 Subject: [PATCH 07/11] Fix clippy --- tools/test-framework/src/chain/cli/provider.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/test-framework/src/chain/cli/provider.rs b/tools/test-framework/src/chain/cli/provider.rs index ed5169f91f..76310a2a6d 100644 --- a/tools/test-framework/src/chain/cli/provider.rs +++ b/tools/test-framework/src/chain/cli/provider.rs @@ -47,8 +47,6 @@ pub fn submit_consumer_chain_proposal( let output: serde_json::Value = serde_json::from_str(&raw_output.stdout).map_err(handle_generic_error)?; - let txhash = output.get("txhash").and_then(|code| code.as_str()).ok_or_else(|| Error::generic(eyre!("failed to extract 'code' from 'tx gov submit-legacy-proposal consumer-addition' command")))?; - let output_code = output.get("code").and_then(|code| code.as_u64()).ok_or_else(|| Error::generic(eyre!("failed to extract 'code' from 'tx gov submit-legacy-proposal consumer-addition' command")))?; // Proposal submission might fail due to account sequence error. From b268dc192e5f1890f2543db70fa3540081906010 Mon Sep 17 00:00:00 2001 From: Luca Joss Date: Wed, 16 Oct 2024 11:00:50 +0200 Subject: [PATCH 08/11] Disable CCQ test --- .github/workflows/integration.yaml | 8 ++------ .github/workflows/multi-chains.yaml | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 533546ab46..979436bd2e 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -45,11 +45,6 @@ jobs: fail-fast: false matrix: chain: - - package: gaia18 - command: gaiad - account_prefix: cosmos - native_token: stake - features: forward-packet,clean-workers,ica,ics29-fee - package: gaia20 command: gaiad account_prefix: cosmos @@ -186,7 +181,7 @@ jobs: - package: .#gaia20 .#stride command: gaiad,strided account_prefix: cosmos,stride - - package: .#gaia18 .#neutron + - package: .#gaia20 .#neutron command: gaiad,neutrond account_prefix: cosmos,neutron steps: @@ -223,6 +218,7 @@ jobs: --features interchain-security,ica interchain_security:: interchain-security-icq: + if: false # Disable CCQ test runs-on: ubuntu-20.04 strategy: fail-fast: false diff --git a/.github/workflows/multi-chains.yaml b/.github/workflows/multi-chains.yaml index 43846c1347..9aeb907848 100644 --- a/.github/workflows/multi-chains.yaml +++ b/.github/workflows/multi-chains.yaml @@ -58,7 +58,7 @@ jobs: fail-fast: false matrix: first-package: - - package: gaia18 + - package: gaia20 command: gaiad account_prefix: cosmos - package: ibc-go-v7-simapp From beeda6818b01d91e5f5625ba9940f93b33d1ddc5 Mon Sep 17 00:00:00 2001 From: Luca Joss Date: Wed, 16 Oct 2024 11:58:09 +0200 Subject: [PATCH 09/11] Improve error and metric recording for ICS31 cross chain queries --- .../relayer/src/worker/cross_chain_query.rs | 32 +++++++++++++------ crates/relayer/src/worker/error.rs | 18 +++++++++-- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/crates/relayer/src/worker/cross_chain_query.rs b/crates/relayer/src/worker/cross_chain_query.rs index d6293ebefc..29f1f69db6 100644 --- a/crates/relayer/src/worker/cross_chain_query.rs +++ b/crates/relayer/src/worker/cross_chain_query.rs @@ -94,7 +94,7 @@ fn handle_cross_chain_query( }, IncludeProof::No, ) - .map_err(|_| TaskError::Fatal(RunError::query()))? + .map_err(|e| TaskError::Fatal(RunError::relayer(e)))? .0; // Retrieve client based on client id @@ -103,19 +103,21 @@ fn handle_cross_chain_query( chain_a_handle.clone(), connection_end.client_id(), ) - .map_err(|_| TaskError::Fatal(RunError::query()))?; + .map_err(|e| TaskError::Fatal(RunError::foreign_client(e)))?; let target_height = Height::new( chain_b_handle.id().version(), cross_chain_query_responses.first().unwrap().height as u64, ) - .map_err(|_| TaskError::Fatal(RunError::query()))? + .map_err(|e| TaskError::Fatal(RunError::ics02(e)))? .increment(); // Push update client msg let mut chain_a_msgs = client_a .wait_and_build_update_client(target_height) - .map_err(|_| TaskError::Fatal(RunError::query()))?; + .map_err(|e| TaskError::Fatal(RunError::foreign_client(e)))?; + + let num_cross_chain_query_responses = cross_chain_query_responses.len(); for response in cross_chain_query_responses { info!("response arrived: query_id: {}", response.query_id); @@ -125,24 +127,36 @@ fn handle_cross_chain_query( .try_to_any( chain_a_handle .get_signer() - .map_err(|_| TaskError::Fatal(RunError::query()))?, + .map_err(|e| TaskError::Fatal(RunError::relayer(e)))?, ) - .map_err(|_| TaskError::Fatal(RunError::query()))?, + .map_err(|e| TaskError::Fatal(RunError::ics31(e)))?, ); } - let _ccq_responses = chain_a_handle + let ccq_responses = chain_a_handle .send_messages_and_wait_check_tx(TrackedMsgs::new_uuid( chain_a_msgs, Uuid::new_v4(), )) - .map_err(|_| TaskError::Ignore(RunError::query()))?; + .map_err(|e| { + // Since all the CCQs failed, generate a failure code for the telemetry + let failed_codes = + vec![tendermint::abci::Code::from(1); num_cross_chain_query_responses]; + telemetry!( + cross_chain_query_responses, + &cross_chain_query.dst_chain_id, + &cross_chain_query.src_chain_id, + failed_codes + ); + + TaskError::Ignore(RunError::relayer(e)) + })?; telemetry!( cross_chain_query_responses, &cross_chain_query.dst_chain_id, &cross_chain_query.src_chain_id, - _ccq_responses + ccq_responses .iter() .map(|ccq_response| ccq_response.code) .collect() diff --git a/crates/relayer/src/worker/error.rs b/crates/relayer/src/worker/error.rs index a694678539..e95b2290ed 100644 --- a/crates/relayer/src/worker/error.rs +++ b/crates/relayer/src/worker/error.rs @@ -1,9 +1,12 @@ use crossbeam_channel::RecvError; use flex_error::{define_error, DisplayOnly}; +use ibc_relayer_types::applications::ics31_icq::error::Error as Ics31Error; use ibc_relayer_types::core::ics02_client::error::Error as Ics02Error; use crate::channel::ChannelError; use crate::connection::ConnectionError; +use crate::error::Error as RelayerError; +use crate::foreign_client::ForeignClientError; use crate::link::error::LinkError; define_error! { @@ -12,6 +15,10 @@ define_error! { [ Ics02Error ] | _ | { "client error" }, + Ics31 + [ Ics31Error ] + | _ | { "cross chain query error" }, + Connection [ ConnectionError ] | _ | { "connection error" }, @@ -20,10 +27,18 @@ define_error! { [ ChannelError ] | _ | { "channel error" }, + ForeignClient + [ ForeignClientError ] + | _ | { "foreign client error" }, + Link [ LinkError ] | _ | { "link error" }, + Relayer + [ RelayerError ] + | _ | { "relayer error" }, + Retry { retries: retry::Error } | e | { format_args!("worker failed after {} retries", e.retries) }, @@ -31,8 +46,5 @@ define_error! { Recv [ DisplayOnly ] | _ | { "error receiving from channel: sender end has been closed" }, - - Query - | _ | { "error occurred during querying" } } } From f17051e12df0fc5c0106f8aeafeca6ca3e88d148 Mon Sep 17 00:00:00 2001 From: Luca Joss Date: Thu, 7 Nov 2024 13:23:20 +0100 Subject: [PATCH 10/11] Use Gaia v20 in CI jobs --- .github/workflows/integration.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 763cf8d830..fb98841907 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -310,7 +310,7 @@ jobs: fail-fast: false matrix: chain: - - package: .#gaia18 + - package: .#gaia20 command: gaiad account_prefix: cosmos steps: From 55bf894c6df05e69cc394e870679ba1aaa475465 Mon Sep 17 00:00:00 2001 From: Luca Joss Date: Thu, 7 Nov 2024 13:34:37 +0100 Subject: [PATCH 11/11] Add changelog entry --- .../ibc-integration-test/4204-update-gaia-to-v20.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .changelog/unreleased/improvements/ibc-integration-test/4204-update-gaia-to-v20.md diff --git a/.changelog/unreleased/improvements/ibc-integration-test/4204-update-gaia-to-v20.md b/.changelog/unreleased/improvements/ibc-integration-test/4204-update-gaia-to-v20.md new file mode 100644 index 0000000000..2fcbe90beb --- /dev/null +++ b/.changelog/unreleased/improvements/ibc-integration-test/4204-update-gaia-to-v20.md @@ -0,0 +1,2 @@ +- Update the version of Gaia running the integration tests in the CI from `v18.1.0` + to `v20.0.0` ([\#4204](https://github.com/informalsystems/hermes/issues/4204)) \ No newline at end of file