Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CompatMode configuration #3667

Merged
merged 10 commits into from
Nov 2, 2023
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Add an optional per-chain setting `compat_mode`, which can be
used to specify which CometBFT compatibility mode is used for interacting with the node over RPC.
([\#3623](https://github.com/informalsystems/hermes/issues/3623))
45 changes: 45 additions & 0 deletions .github/workflows/integration.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,51 @@ jobs:
cargo nextest run -p ibc-integration-test --no-fail-fast --failure-output final --test-threads=2 \
--features interchain-security,ics31 interchain_security::

celestia-to-gaia:
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
chain:
- package: celestia
command: celestia-appd
account_prefix: celestia
native_token: utia
steps:
- uses: actions/checkout@v4
- uses: cachix/install-nix-action@v22
with:
install_url: https://nixos-nix-install-tests.cachix.org/serve/vij683ly7sl95nnhb67bdjjfabclr85m/install
install_options: '--tarball-url-prefix https://nixos-nix-install-tests.cachix.org/serve'
extra_nix_config: |
experimental-features = nix-command flakes
- uses: cachix/cachix-action@v12
with:
name: cosmos
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
- uses: Swatinem/rust-cache@v2
- uses: actions-rs/cargo@v1
with:
command: test
args: -p ibc-integration-test --features interchain-security --no-fail-fast --no-run
- name: Install cargo-nextest
run: curl -LsSf https://get.nexte.st/latest/linux | tar zxf - -C ${CARGO_HOME:-~/.cargo}/bin
- env:
RUST_LOG: info
RUST_BACKTRACE: 1
NO_COLOR_LOG: 1
COMPAT_MODES: 0.34
CHAIN_COMMAND_PATHS: ${{ matrix.chain.command }},gaiad
ACCOUNT_PREFIXES: ${{ matrix.chain.account_prefix }},cosmos
NATIVE_TOKENS: ${{ matrix.chain.native_token }},stake
run: |
nix shell .#python .#gaia12 .#${{ matrix.chain.package }} -c \
cargo nextest run -p ibc-integration-test --no-fail-fast --failure-output final --test-threads=2 \
--features celestia

model-based-test:
runs-on: ubuntu-20.04
timeout-minutes: 60
Expand Down
9 changes: 9 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,15 @@ memo_prefix = ''
# submitted to this chain.
# fee_granter = ''

# Specify the CometBFT compatibility mode to use.
# The following behaviours are applied whether the `compat_mode` is configured or not:
# * compat_mode is specified and the version queried from /status is the same as the one configured: Use that version without log output
# * compat_mode is specified but the version queried from /status differs: The CompatMode configured is used, but a warning log is outputted
# * compat_mode is not specified but /status returns a correct version: The CompatMode retrieved from the endpoint is used
# * compat_mode is not specified and /status does not return a valid version: Hermes stops and outputs an error informing the user a compat_mode needs to be configured
# Possible values: [`0.34`, `0.37`]
# compat_mode = '0.34'

[[chains]]
id = 'ibc-1'
type = "CosmosSdk"
Expand Down
1 change: 1 addition & 0 deletions crates/relayer-cli/src/chain_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ where
address_type: AddressType::default(),
sequential_batch_tx: false,
extension_options: Vec::new(),
compat_mode: None,
}))
}

Expand Down
10 changes: 6 additions & 4 deletions crates/relayer-cli/src/commands/listen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use ibc_relayer::{
chain::handle::Subscription,
config::{ChainConfig, EventSourceMode},
event::source::EventSource,
util::compat_mode::compat_mode_from_version,
};
use ibc_relayer_types::{core::ics24_host::identifier::ChainId, events::IbcEvent};

Expand Down Expand Up @@ -181,10 +182,11 @@ fn detect_compatibility_mode(
};
let client = HttpClient::new(rpc_addr)?;
let status = rt.block_on(client.status())?;
let compat_mode = CompatMode::from_version(status.node_info.version).unwrap_or_else(|e| {
warn!("Unsupported tendermint version, will use v0.34 compatibility mode but relaying might not work as desired: {e}");
CompatMode::V0_34
});
let compat_mode = match config {
ChainConfig::CosmosSdk(config) => {
compat_mode_from_version(&config.compat_mode, status.node_info.version)?.into()
}
};
Ok(compat_mode)
}

Expand Down
6 changes: 2 additions & 4 deletions crates/relayer/src/chain/cosmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ use crate::keyring::{KeyRing, Secp256k1KeyPair, SigningKeyPair};
use crate::light_client::tendermint::LightClient as TmLightClient;
use crate::light_client::{LightClient, Verified};
use crate::misbehaviour::MisbehaviourEvidence;
use crate::util::compat_mode::compat_mode_from_version;
use crate::util::pretty::{
PrettyIdentifiedChannel, PrettyIdentifiedClientState, PrettyIdentifiedConnection,
};
Expand Down Expand Up @@ -886,10 +887,7 @@ impl ChainEndpoint for CosmosSdkChain {

let node_info = rt.block_on(fetch_node_info(&rpc_client, &config))?;

let compat_mode = CompatMode::from_version(node_info.version).unwrap_or_else(|e| {
warn!("Unsupported tendermint version, will use v0.34 compatibility mode but relaying might not work as desired: {e}");
CompatMode::V0_34
});
let compat_mode = compat_mode_from_version(&config.compat_mode, node_info.version)?.into();
rpc_client.set_compat_mode(compat_mode);

let light_client = TmLightClient::from_cosmos_sdk_config(&config, node_info.id)?;
Expand Down
2 changes: 2 additions & 0 deletions crates/relayer/src/chain/cosmos/config.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::chain::cosmos::config::error::Error as ConfigError;
use crate::config::compat_mode::CompatMode;
use crate::config::default;
use crate::config::gas_multiplier::GasMultiplier;
use crate::config::types::{MaxMsgNum, MaxTxSize, Memo};
Expand Down Expand Up @@ -123,6 +124,7 @@ pub struct CosmosSdkConfig {
pub address_type: AddressType,
#[serde(default = "Vec::new", skip_serializing_if = "Vec::is_empty")]
pub extension_options: Vec<ExtensionOption>,
pub compat_mode: Option<CompatMode>,
}

impl CosmosSdkConfig {
Expand Down
4 changes: 3 additions & 1 deletion crates/relayer/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Relayer configuration

pub mod compat_mode;
pub mod error;
pub mod filter;
pub mod gas_multiplier;
Expand All @@ -17,7 +18,8 @@ use core::{
use serde_derive::{Deserialize, Serialize};
use std::{fs, fs::File, io::Write, ops::Range, path::Path};
use tendermint::block::Height as BlockHeight;
use tendermint_rpc::{Url, WebSocketClientUrl};
use tendermint_rpc::Url;
use tendermint_rpc::WebSocketClientUrl;

use ibc_proto::google::protobuf::Any;
use ibc_relayer_types::core::ics24_host::identifier::{ChainId, ChannelId, PortId};
Expand Down
97 changes: 97 additions & 0 deletions crates/relayer/src/config/compat_mode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
use core::fmt::{Display, Error as FmtError, Formatter};
use core::str::FromStr;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::Serializer;

use tendermint_rpc::client::CompatMode as TmCompatMode;

use crate::config::Error;

/// CometBFT RPC compatibility mode
///
/// Can be removed in favor of the one in tendermint-rs, once
/// <https://github.com/informalsystems/tendermint-rs/pull/1367> is merged.
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum CompatMode {
/// Use version 0.34 of the protocol.
V0_34,
/// Use version 0.37 of the protocol.
V0_37,
}

impl Display for CompatMode {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match self {
Self::V0_34 => write!(f, "v0.34"),
Self::V0_37 => write!(f, "v0.37"),
}
}
}

impl FromStr for CompatMode {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
const VALID_COMPAT_MODES: &str = "0.34, 0.37";

// Trim leading 'v', if present
match s.trim_start_matches('v') {
"0.34" => Ok(CompatMode::V0_34),
"0.37" => Ok(CompatMode::V0_37),
_ => Err(Error::invalid_compat_mode(
s.to_string(),
VALID_COMPAT_MODES,
)),
}
}
}

impl<'de> Deserialize<'de> for CompatMode {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
use serde::de;

let s = String::deserialize(deserializer)?;
FromStr::from_str(&s).map_err(de::Error::custom)
}
}

impl Serialize for CompatMode {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.to_string().serialize(serializer)
}
}

impl From<TmCompatMode> for CompatMode {
fn from(value: TmCompatMode) -> Self {
match value {
TmCompatMode::V0_34 => Self::V0_34,
TmCompatMode::V0_37 => Self::V0_37,
}
}
}

impl From<CompatMode> for TmCompatMode {
fn from(value: CompatMode) -> Self {
match value {
CompatMode::V0_34 => Self::V0_34,
CompatMode::V0_37 => Self::V0_37,
}
}
}

impl CompatMode {
pub fn equal_to_tm_compat_mode(&self, tm_compat_mode: TmCompatMode) -> bool {
match self {
Self::V0_34 => tm_compat_mode == TmCompatMode::V0_34,
Self::V0_37 => tm_compat_mode == TmCompatMode::V0_37,
}
}
}
4 changes: 4 additions & 0 deletions crates/relayer/src/config/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ define_error! {
[ TraceError<toml::de::Error> ]
|_| { "invalid configuration" },

InvalidCompatMode
{ compat_mode: String, valide_modes: &'static str }
|e| { format!("invalid compatibility mode: '{}' (supported: {})", e.compat_mode, e.valide_modes) },

Encode
[ TraceError<toml::ser::Error> ]
|_| { "invalid configuration" },
Expand Down
4 changes: 4 additions & 0 deletions crates/relayer/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,10 @@ define_error! {
{ address: String }
[ TendermintRpcError ]
|e| { format!("invalid archive node address {}", e.address) },

InvalidCompatMode
[ TendermintRpcError ]
|_| { "Invalid CompatMode queried from chain and no `compat_mode` configured in Hermes. This can be fixed by specifying a `compat_mode` in Hermes config.toml" },
}
}

Expand Down
1 change: 1 addition & 0 deletions crates/relayer/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ mod block_on;
pub use block_on::{block_on, spawn_blocking};

pub mod collate;
pub mod compat_mode;
pub mod debug_section;
pub mod diff;
pub mod iter;
Expand Down
27 changes: 27 additions & 0 deletions crates/relayer/src/util/compat_mode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
use tracing::warn;

use tendermint::Version;
use tendermint_rpc::client::CompatMode as TmCompatMode;

use crate::config::compat_mode::CompatMode;
use crate::error::Error;

/// This is a wrapper around tendermint-rs CompatMode::from_version() method.
///
pub fn compat_mode_from_version(
configured_version: &Option<CompatMode>,
version: Version,
) -> Result<CompatMode, Error> {
let queried_version = TmCompatMode::from_version(version);

// This will prioritize the use of the CompatMode specified in Hermes configuration file
match (configured_version, queried_version) {
(Some(configured), Ok(queried)) if !configured.equal_to_tm_compat_mode(queried) => {
warn!("Be wary of potential CompatMode version misconfiguration. Configured version: {}, Version output from chain: {}. Hermes will use the configured CompatMode version `{}`. If this configuration is done on purpose this message can be ignored.", configured, queried, configured);
ljoss17 marked this conversation as resolved.
Show resolved Hide resolved
Ok(configured.clone())
}
(Some(configured), _) => Ok(configured.clone()),
(_, Ok(queried)) => Ok(queried.into()),
(_, Err(e)) => Err(Error::invalid_compat_mode(e)),
}
}
1 change: 1 addition & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
stride-consumer
migaloo
neutron
celestia
;

python = nixpkgs.python3.withPackages (p: [
Expand Down
1 change: 1 addition & 0 deletions guide/src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
- [Description of the parameters](./documentation/configuration/description.md)
- [Filter incentivized packets](./documentation/configuration/filter-incentivized.md)
- [Performance tuning](./documentation/configuration/performance.md)
- [CometBFT Compatibility modes](./documentation/configuration/comet-compat-mode.md)

- [Telemetry](./documentation/telemetry/index.md)
- [Operators guide](./documentation/telemetry/operators.md)
Expand Down
24 changes: 24 additions & 0 deletions guide/src/documentation/configuration/comet-compat-mode.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# CometBFT Compatibility modes
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome!


## Overview

There are two different compatibility modes for CometBFT, one for version v0.34 and one for versions v0.37 and v0.38. In order to verify the compatiblity used Hermes queries the node's `/status` endpoint, which contains the CometBFT version used. This can be an issue if a chain uses a custom version which does not output the version string Hermes expects. To still be able to relay for these chains a configuration can be set in Hermes.

## Configuration

The configuration is set per chain and can take two values `0.34` and `0.37`, other values will be invalid:

```toml
[[chains]]
...
compat_mode = '0.34'
```

Hermes will act in the following way whether or not the configuration is set:

* `compat_mode` is specified and the version queried from `/status` is the same as the one configured: Use that version without log output
* `compat_mode` is specified but the version queried from `/status` differs: The compatibility mode configured is used, but a warning log is outputted
* `compat_mode` is not specified but /status returns a correct version: The compatibility mode retrieved from the endpoint is used
* `compat_mode` is not specified and /status does not return a valid version: Hermes stops and outputs an error informing the user that the `compat_mode` needs to be configured

The configuration can also be found in the example [config.toml](https://github.com/informalsystems/hermes/blob/{{#include ../../templates/hermes-version.md}}/config.toml#382)
5 changes: 4 additions & 1 deletion guide/src/documentation/configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,7 @@ This section includes everything you need to know to configure Hermes.
* Examples on how to configure Hermes in order to filter incentivized packets

- **[Performance Tuning](./performance.md)**
* Learn about configurations allowing more refined performance tuning.
* Learn about configurations allowing more refined performance tuning.

- **[CometBFT Compatibility modes](./comet-compat-mode.md)**
* Handle different CometBFT compatibility modes.
1 change: 1 addition & 0 deletions tools/integration-test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ ics31 = []
clean-workers = []
fee-grant = []
interchain-security = []
celestia = []

[[bin]]
name = "test_setup_with_binary_channel"
Expand Down
4 changes: 3 additions & 1 deletion tools/integration-test/src/tests/clear_packet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,13 @@ impl TestOverrides for ClearPacketNoScanTest {
impl BinaryChannelTest for ClearPacketNoScanTest {
fn run<ChainA: ChainHandle, ChainB: ChainHandle>(
&self,
_config: &TestConfig,
config: &TestConfig,
relayer: RelayerDriver,
chains: ConnectedChains<ChainA, ChainB>,
channel: ConnectedChannel<ChainA, ChainB>,
) -> Result<(), Error> {
let denom_a = chains.node_a.denom();
let fee_denom_a = MonoTagged::new(Denom::base(&config.native_tokens[0]));

let wallet_a = chains.node_a.wallets().user1().cloned();
let wallet_b = chains.node_b.wallets().user1().cloned();
Expand Down Expand Up @@ -272,6 +273,7 @@ impl BinaryChannelTest for ClearPacketNoScanTest {
&channel.port_a.0,
&channel.channel_id_a.0,
&denom_a.with_amount(amount1).as_ref(),
&fee_denom_a.with_amount(1200u64).as_ref(),
&dst_height,
)?;

Expand Down
Loading
Loading