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 setting to configure the rate at which to refresh clients #3402

Merged
merged 12 commits into from
Jan 5, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
- Add a `client_refresh_rate` setting to specify the rate at which to
refresh clients referencing this chain, relative to its trusting period.
([\#3402](https://github.com/informalsystems/hermes/issues/3402))
15 changes: 12 additions & 3 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -314,14 +314,23 @@ max_block_time = '30s'
# Specify the amount of time to be used as the light client trusting period.
# It should be significantly less than the unbonding period
# (e.g. unbonding period = 3 weeks, trusting period = 2 weeks).
#
# Default: 2/3 of the `unbonding period` for Cosmos SDK chains
trusting_period = '14days'

# The rate at which to refresh the client referencing this chain,
# expressed as a fraction of the trusting period.
#
# Default: 1/3 (ie. three times per trusting period)
client_refresh_rate = '1/3'

# Specify the trust threshold for the light client, ie. the minimum fraction of validators
# which must overlap across two blocks during light client verification.
# Default: { numerator = '2', denominator = '3' }, ie. 2/3.
#
# Warning: This is an advanced feature! Modify with caution.
trust_threshold = { numerator = '2', denominator = '3' }
#
# Default: 2/3
trust_threshold = '2/3'

# Specify a string that Hermes will use as a memo for each transaction it submits
# to this chain. The string is limited to 50 characters. Default: '' (empty).
Expand Down Expand Up @@ -413,5 +422,5 @@ max_tx_size = 2097152
clock_drift = '5s'
max_block_time = '30s'
trusting_period = '14days'
trust_threshold = { numerator = '2', denominator = '3' }
trust_threshold = '2/3'
address_type = { derivation = 'cosmos' }
4 changes: 2 additions & 2 deletions crates/relayer-cli/src/chain_registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,10 @@ use ibc_chain_registry::querier::*;
use ibc_relayer::chain::cosmos::config::CosmosSdkConfig;
use ibc_relayer::config::filter::{FilterPattern, PacketFilter};
use ibc_relayer::config::gas_multiplier::GasMultiplier;
use ibc_relayer::config::types::{MaxMsgNum, MaxTxSize, Memo};
use ibc_relayer::config::types::{MaxMsgNum, MaxTxSize, Memo, TrustThreshold};
use ibc_relayer::config::{default, AddressType, ChainConfig, EventSourceMode, GasPrice};
use ibc_relayer::keyring::Store;

use tendermint_light_client_verifier::types::TrustThreshold;
use tendermint_rpc::Url;

const MAX_HEALTHY_QUERY_RETRIES: u8 = 5;
Expand Down Expand Up @@ -148,6 +147,7 @@ where
clock_drift: default::clock_drift(),
max_block_time: default::max_block_time(),
trusting_period: None,
client_refresh_rate: default::client_refresh_rate(),
ccv_consumer_chain: false,
memo_prefix: Memo::default(),
proof_specs: Default::default(),
Expand Down
2 changes: 1 addition & 1 deletion crates/relayer-cli/src/commands/config/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ impl Runnable for ValidateCmd {
// No need to output the underlying error, this is done already when the application boots.
// See `application::CliApp::after_config`.
match config.validate_config() {
Ok(_) => Output::success("configuration is valid").exit(),
Ok(_) => Output::success_msg("configuration is valid").exit(),
Err(_) => Output::error("configuration is invalid").exit(),
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,6 @@ impl ClientState {
})
}

/// Get the refresh time to ensure the state does not expire
pub fn refresh_time(&self) -> Option<Duration> {
Some(2 * self.trusting_period / 3)
}

/// Helper method to produce a [`Options`] struct for use in
/// Tendermint-specific light client verification.
pub fn as_light_client_options(&self) -> Options {
Expand Down
127 changes: 110 additions & 17 deletions crates/relayer-types/src/core/ics02_client/trust_threshold.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use std::convert::TryFrom;
use std::fmt::{Display, Error as FmtError, Formatter};
use std::str::FromStr;

use ibc_proto::Protobuf;
use num_rational::Ratio;
Expand Down Expand Up @@ -121,23 +122,37 @@ impl Display for TrustThreshold {
}
}

impl FromStr for TrustThreshold {
type Err = String;

fn from_str(s: &str) -> Result<Self, Self::Err> {
let parts: Vec<&str> = s.split('/').collect();

if parts.len() != 2 {
return Err(format!("invalid trust threshold, must be a fraction: {s}"));
}

let (num, denom) = (parts[0].parse(), parts[1].parse());

if let (Ok(num), Ok(denom)) = (num, denom) {
TrustThreshold::new(num, denom).map_err(|e| e.to_string())
} else {
Err(format!("invalid trust threshold, must be a fraction: {s}",))
}
}
}

impl Serialize for TrustThreshold {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
#[derive(Serialize)]
struct TrustThreshold {
numerator: u64,
denominator: u64,
}
use serde::ser::SerializeStruct;

let tt = TrustThreshold {
numerator: self.numerator(),
denominator: self.denominator(),
};

tt.serialize(serializer)
let mut s = serializer.serialize_struct("TrustThreshold", 2)?;
s.serialize_field("numerator", &self.numerator())?;
s.serialize_field("denominator", &self.denominator())?;
s.end()
}
}

Expand All @@ -146,13 +161,91 @@ impl<'de> Deserialize<'de> for TrustThreshold {
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct TrustThreshold {
numerator: u64,
denominator: u64,
use serde::de::{self, Visitor};
use std::fmt;

// This is a Visitor that forwards string types to T's `FromStr` impl and
// forwards map types to T's `Deserialize` impl. The `PhantomData` is to
// keep the compiler from complaining about T being an unused generic type
// parameter. We need T in order to know the Value type for the Visitor
// impl.
struct StringOrStruct;

impl<'de> Visitor<'de> for StringOrStruct {
type Value = TrustThreshold;

fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str(
"string (eg. '1/3') or map `{ numerator = <int>, denominator = <int> }`",
)
}

fn visit_str<E>(self, value: &str) -> Result<TrustThreshold, E>
where
E: de::Error,
{
Ok(FromStr::from_str(value).unwrap())
}

fn visit_map<M>(self, map: M) -> Result<TrustThreshold, M::Error>
where
M: de::MapAccess<'de>,
{
#[derive(Deserialize)]
struct TT {
#[serde(deserialize_with = "string_or_int")]
numerator: u64,
#[serde(deserialize_with = "string_or_int")]
denominator: u64,
}

let tt = TT::deserialize(de::value::MapAccessDeserializer::new(map))?;

TrustThreshold::new(tt.numerator, tt.denominator).map_err(de::Error::custom)
}
}

let tt = TrustThreshold::deserialize(deserializer)?;
Self::new(tt.numerator, tt.denominator).map_err(serde::de::Error::custom)
deserializer.deserialize_any(StringOrStruct)
}
}

fn string_or_int<'de, D>(deserializer: D) -> Result<u64, D::Error>
where
D: serde::Deserializer<'de>,
{
use serde::de::{self, Visitor};
use std::fmt;

struct StringOrInt;

impl<'de> Visitor<'de> for StringOrInt {
type Value = u64;

fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
formatter.write_str("string or int")
}

fn visit_str<E>(self, value: &str) -> Result<u64, E>
where
E: de::Error,
{
FromStr::from_str(value).map_err(de::Error::custom)
}

fn visit_i64<E>(self, value: i64) -> Result<u64, E>
where
E: de::Error,
{
Ok(value as u64)
}

fn visit_u64<E>(self, value: u64) -> Result<u64, E>
where
E: de::Error,
{
Ok(value)
}
}

deserializer.deserialize_any(StringOrInt)
}
4 changes: 0 additions & 4 deletions crates/relayer-types/src/mock/client_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ impl MockClientState {
pub fn latest_height(&self) -> Height {
self.header.height()
}

pub fn refresh_time(&self) -> Option<Duration> {
None
}
}

impl Protobuf<RawMockClientState> for MockClientState {}
Expand Down
2 changes: 1 addition & 1 deletion crates/relayer/src/chain/cosmos/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl Settings {

let trust_threshold = options
.trust_threshold
.unwrap_or_else(|| src_chain_config.trust_threshold.into());
.unwrap_or(src_chain_config.trust_threshold);

Settings {
max_clock_drift,
Expand Down
28 changes: 17 additions & 11 deletions crates/relayer/src/chain/cosmos/config.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
use core::time::Duration;
use std::path::PathBuf;

use byte_unit::Byte;
use serde_derive::{Deserialize, Serialize};
use tendermint_rpc::Url;

use ibc_relayer_types::core::ics23_commitment::specs::ProofSpecs;
use ibc_relayer_types::core::ics24_host::identifier::ChainId;

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};
use crate::config::types::{MaxMsgNum, MaxTxSize, Memo, TrustThreshold};
use crate::config::{
self, AddressType, EventSourceMode, ExtensionOption, GasPrice, GenesisRestart, PacketFilter,
};
use byte_unit::Byte;
use core::time::Duration;
use ibc_relayer_types::core::ics23_commitment::specs::ProofSpecs;
use ibc_relayer_types::core::ics24_host::identifier::ChainId;
use serde_derive::{Deserialize, Serialize};
use std::path::PathBuf;
use tendermint_light_client::verifier::types::TrustThreshold;
use tendermint_rpc::Url;

use crate::config::{default, RefreshRate};
use crate::keyring::Store;

pub mod error;
Expand Down Expand Up @@ -85,6 +86,11 @@ pub struct CosmosSdkConfig {
#[serde(default, with = "humantime_serde")]
pub trusting_period: Option<Duration>,

/// The rate at which to refresh the client referencing this chain,
/// expressed as a fraction of the trusting period.
#[serde(default = "default::client_refresh_rate")]
pub client_refresh_rate: RefreshRate,

/// CCV consumer chain
#[serde(default = "default::ccv_consumer_chain")]
pub ccv_consumer_chain: bool,
Expand Down
50 changes: 24 additions & 26 deletions crates/relayer/src/chain/cosmos/config/error.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,32 @@
use flex_error::define_error;
use ibc_relayer_types::core::ics02_client::trust_threshold::TrustThreshold;
use ibc_relayer_types::core::ics24_host::identifier::ChainId;
use tendermint_light_client_verifier::types::TrustThreshold;

define_error! {

Error {
InvalidTrustThreshold
{
threshold: TrustThreshold,
chain_id: ChainId,
reason: String
}
|e| {
format!("config file specifies an invalid `trust_threshold` ({0}) for the chain '{1}', caused by: {2}",
e.threshold, e.chain_id, e.reason)
},

DeprecatedGasAdjustment
{
gas_adjustment: f64,
gas_multiplier: f64,
chain_id: ChainId,
}
|e| {
format!(
"config file specifies deprecated setting `gas_adjustment = {1}` for the chain '{0}'; \
to get the same behavior, use `gas_multiplier = {2}",
e.chain_id, e.gas_adjustment, e.gas_multiplier
)
},
InvalidTrustThreshold
{
threshold: TrustThreshold,
chain_id: ChainId,
reason: String
}
|e| {
format!("config file specifies an invalid `trust_threshold` ({0}) for the chain '{1}', caused by: {2}",
e.threshold, e.chain_id, e.reason)
},

DeprecatedGasAdjustment
{
gas_adjustment: f64,
gas_multiplier: f64,
chain_id: ChainId,
}
|e| {
format!(
"config file specifies deprecated setting `gas_adjustment = {1}` for the chain '{0}'; \
to get the same behavior, use `gas_multiplier = {2}",
e.chain_id, e.gas_adjustment, e.gas_multiplier
)
},
}
}
Loading
Loading