From 0e45fc896ad2bb65dc6163bc51ae0663fb1bca55 Mon Sep 17 00:00:00 2001 From: Luca Joss <43531661+ljoss17@users.noreply.github.com> Date: Fri, 8 Nov 2024 09:24:26 +0100 Subject: [PATCH] Add Gaia v20.0.0 to chains running tests in CI (#4210) * Update bootstrapping of consumer chain * Add Gaia v20 to CI and update Gaia used in CI tests * Correctly create consumer chain and opt-in validator * WIP: Fix CCQs between Stride and Gaia * Add metrics for ICS31 cross chain queries * Remove unnecessary code * Fix clippy * Disable CCQ test * Improve error and metric recording for ICS31 cross chain queries * Use Gaia v20 in CI jobs * Add changelog entry --- .../4204-update-gaia-to-v20.md | 2 + .github/workflows/integration.yaml | 13 +- .github/workflows/multi-chains.yaml | 2 +- Cargo.lock | 66 ++++- .../relayer/src/worker/cross_chain_query.rs | 47 +++- crates/relayer/src/worker/error.rs | 18 +- crates/telemetry/src/state.rs | 59 +++++ flake.lock | 6 +- tools/test-framework/Cargo.toml | 2 + .../test-framework/src/bootstrap/consumer.rs | 18 +- tools/test-framework/src/bootstrap/single.rs | 1 + .../test-framework/src/chain/cli/provider.rs | 235 +++++++++++++++++- tools/test-framework/src/chain/config.rs | 10 + .../test-framework/src/chain/ext/bootstrap.rs | 189 +++++++++++--- .../src/framework/binary/ics.rs | 41 +-- 15 files changed, 620 insertions(+), 89 deletions(-) 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 diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 528ea08c6c..fb98841907 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -45,7 +45,7 @@ jobs: fail-fast: false matrix: chain: - - package: gaia18 + - package: gaia20 command: gaiad account_prefix: cosmos native_token: stake @@ -178,10 +178,10 @@ jobs: fail-fast: false matrix: chain: - - package: .#gaia18 .#stride + - package: .#gaia20 .#stride command: gaiad,strided account_prefix: cosmos,stride - - package: .#gaia18 .#neutron + - package: .#gaia20 .#neutron command: gaiad,neutrond account_prefix: cosmos,neutron steps: @@ -218,12 +218,13 @@ jobs: --features interchain-security,ica interchain_security:: interchain-security-icq: + if: false # Disable CCQ test runs-on: ubuntu-20.04 strategy: 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 +265,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 @@ -309,7 +310,7 @@ jobs: fail-fast: false matrix: chain: - - package: .#gaia18 + - package: .#gaia20 command: gaiad account_prefix: cosmos steps: 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 diff --git a/Cargo.lock b/Cargo.lock index 72866405bc..a2a7a87a97 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" @@ -116,9 +131,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.92" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74f37166d7d48a0284b99dd824694c26119c700b53bf0d1540cdb147dbdaaf13" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arc-swap" @@ -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" @@ -1521,6 +1550,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" @@ -1758,6 +1810,7 @@ dependencies = [ name = "ibc-test-framework" version = "0.29.3" dependencies = [ + "chrono", "color-eyre", "crossbeam-channel", "eyre", @@ -4232,6 +4285,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/crates/relayer/src/worker/cross_chain_query.rs b/crates/relayer/src/worker/cross_chain_query.rs index 811bb14b36..29f1f69db6 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 @@ -87,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 @@ -96,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); @@ -118,18 +127,40 @@ 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)))?, ); } - 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 + .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" } } } 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; diff --git a/flake.lock b/flake.lock index 6b423bbc94..279a3e3895 100644 --- a/flake.lock +++ b/flake.lock @@ -1465,11 +1465,11 @@ }, "nixpkgs_5": { "locked": { - "lastModified": 1729788628, - "narHash": "sha256-3suayUinicnvE/4shMZwp9FHT5izUM8gMpdEO/NHBTo=", + "lastModified": 1730831018, + "narHash": "sha256-2S0HwIFRxYp+afuoFORcZA9TjryAf512GmE0MTfEOPU=", "owner": "nixos", "repo": "nixpkgs", - "rev": "63487b2f26fa065cfeeaa47dddb08e2856ba53e8", + "rev": "8c4dc69b9732f6bbe826b5fbb32184987520ff26", "type": "github" }, "original": { 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 6c85595331..c473ab6c28 100644 --- a/tools/test-framework/src/bootstrap/consumer.rs +++ b/tools/test-framework/src/bootstrap/consumer.rs @@ -8,6 +8,7 @@ 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::ext::bootstrap::ChainBootstrapMethodsExt; use crate::error::Error; @@ -22,6 +23,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"); @@ -38,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()?; @@ -53,11 +55,23 @@ 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)); + + 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, + )?; + + // Wait enough time so that the spawn_time passed thread::sleep(Duration::from_secs(30)); 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/bootstrap/single.rs b/tools/test-framework/src/bootstrap/single.rs index c7570ad0c3..df87589b35 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/provider.rs b/tools/test-framework/src/chain/cli/provider.rs index 2c5dc6f8cd..76310a2a6d 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,234 @@ 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, +) -> Result<(), Error> { + simple_exec( + chain_id, + command_path, + &[ + "tx", + "provider", + "opt-in", + consumer_id, + "--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/config.rs b/tools/test-framework/src/chain/config.rs index 3cdf1c3877..4d76a1cf43 100644 --- a/tools/test-framework/src/chain/config.rs +++ b/tools/test-framework/src/chain/config.rs @@ -103,6 +103,16 @@ 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") diff --git a/tools/test-framework/src/chain/ext/bootstrap.rs b/tools/test-framework/src/chain/ext/bootstrap.rs index 4fef50083d..07103fcd05 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; @@ -14,10 +17,12 @@ 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, 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, 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 +112,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 +297,132 @@ impl ChainBootstrapMethodsExt for ChainDriver { &self, consumer_chain_id: &str, fees: &str, - _spawn_time: &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, + 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" + }, + "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": "" + } + }"#; + 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)?; + + 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> { + validator_opt_in( + self.chain_id.as_str(), + &self.command_path, + &self.home_path, + &self.rpc_listen_address(), + fees, + consumer_chain_id, + ) + } + + 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", @@ -294,37 +437,23 @@ 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 - }, - "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": [] + "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" + } }"#; 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); + let proposal = proposal.replace("{validator}", validator); - self.write_file("consumer_proposal.json", &proposal)?; + self.write_file("consumer_update_proposal.json", &proposal)?; - submit_consumer_chain_proposal( + update_consumer( self.chain_id.as_str(), &self.command_path, &self.home_path, diff --git a/tools/test-framework/src/framework/binary/ics.rs b/tools/test-framework/src/framework/binary/ics.rs index 24086eff6c..e620ccfedc 100644 --- a/tools/test-framework/src/framework/binary/ics.rs +++ b/tools/test-framework/src/framework/binary/ics.rs @@ -1,17 +1,16 @@ use std::str::FromStr; +use std::thread; 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`]. @@ -71,47 +70,29 @@ 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(), - &provider_fee, - "2023-05-31T12:09:47.048227Z", - )?; + let consumer_id = node_a + .chain_driver + .create_permisionless_consumer(chain_id.as_str(), &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", - )?; + thread::sleep(std::time::Duration::from_secs(3)); - 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(), + node_a.chain_driver.update_consumer( + &consumer_id, &provider_fee, - "1", + node_a.wallets.validator.address.as_str(), )?; - 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", + &consumer_id, &node_a, |config| self.test.get_overrides().modify_node_config(config), |genesis| self.test.get_overrides().modify_genesis_file(genesis), 1, &node_a.chain_driver, + &provider_fee, )?; let _node_process_a = node_a.process.clone();