-
Notifications
You must be signed in to change notification settings - Fork 326
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
Handle some gas corner-cases #2537
Changes from 27 commits
7ad5084
634fb08
ac7a9aa
aae6868
3137a64
32c1f5f
0c5a177
b225c71
00ad6e7
0dd3fe1
9c9040f
af17334
7d4314e
b77ec3e
17b3840
49050ae
e47ad36
06cfdcd
2b3cbab
59c357d
ca7ca04
f59e9b9
8aa2877
d8a831a
8014545
2dcb204
ddfec74
5c522c4
64b16d6
47a49b9
ffa0bbd
f949690
6cf2fc7
b596d66
e82c9d9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
- Added health-check to warn user if 'gas_multiplier' is smaller than 1.1. | ||
Improved client refresh frequency to depend on 'trusting_period'. | ||
([#2487](https://github.com/informalsystems/ibc-rs/issues/2487)) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -71,8 +71,6 @@ fn adjust_estimated_gas( | |
gas_amount, | ||
}: AdjustGas, | ||
) -> u64 { | ||
assert!(gas_multiplier >= 1.0); | ||
|
||
Comment on lines
-74
to
-75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather keep it here, just in case someone removes the checks in a refactoring. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
// No need to compute anything if the gas amount is zero | ||
if gas_amount == 0 { | ||
return 0; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,9 +6,6 @@ use crate::config::{ChainConfig, GasPrice}; | |
/// Default gas limit when submitting a transaction. | ||
const DEFAULT_MAX_GAS: u64 = 400_000; | ||
|
||
/// By default, we do not increase the estimated gas to compute the fee. | ||
const DEFAULT_GAS_MULTIPLIER: f64 = 1.1; | ||
|
||
const DEFAULT_FEE_GRANTER: &str = ""; | ||
|
||
#[derive(Debug, Clone)] | ||
|
@@ -48,8 +45,8 @@ pub fn max_gas_from_config(config: &ChainConfig) -> u64 { | |
} | ||
|
||
/// The gas multiplier | ||
fn gas_multiplier_from_config(config: &ChainConfig) -> f64 { | ||
config.gas_multiplier.unwrap_or(DEFAULT_GAS_MULTIPLIER) | ||
pub fn gas_multiplier_from_config(config: &ChainConfig) -> f64 { | ||
config.gas_multiplier.unwrap_or_default().to_f64() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
} | ||
|
||
/// Get the fee granter address | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -258,6 +258,86 @@ pub mod memo { | |
} | ||
} | ||
|
||
pub use gas_multiplier::GasMultiplier; | ||
|
||
pub mod gas_multiplier { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's better to create a separate module file, this is quite long already for an inline module. |
||
|
||
flex_error::define_error! { | ||
Error { | ||
TooSmall | ||
{ value: f64 } | ||
|e| { | ||
format_args!("`gas_multiplier` must be greater than or equal to {}, found {}", | ||
GasMultiplier::MIN_BOUND, e.value) | ||
}, | ||
} | ||
} | ||
|
||
#[derive(Debug, Clone, Copy)] | ||
pub struct GasMultiplier(f64); | ||
|
||
impl GasMultiplier { | ||
const DEFAULT: f64 = 1.1; | ||
const MIN_BOUND: f64 = 1.0; | ||
|
||
pub fn new(value: f64) -> Result<Self, Error> { | ||
if value < Self::MIN_BOUND { | ||
return Err(Error::too_small(value)); | ||
} | ||
Ok(Self(value)) | ||
} | ||
|
||
// Unsafe GasMultiplier used for test cases only. | ||
pub fn unsafe_new(value: f64) -> Self { | ||
Self(value) | ||
} | ||
|
||
pub fn to_f64(self) -> f64 { | ||
self.0 | ||
} | ||
} | ||
|
||
impl Default for GasMultiplier { | ||
fn default() -> Self { | ||
Self(Self::DEFAULT) | ||
} | ||
} | ||
|
||
use serde::de::Unexpected; | ||
use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; | ||
ljoss17 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
impl<'de> Deserialize<'de> for GasMultiplier { | ||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> | ||
where | ||
D: Deserializer<'de>, | ||
{ | ||
let value = f64::deserialize(deserializer)?; | ||
|
||
GasMultiplier::new(value).map_err(|e| match e.detail() { | ||
ErrorDetail::TooSmall(_) => D::Error::invalid_value( | ||
Unexpected::Float(value), | ||
&format!("a f64 less than {}", Self::MIN_BOUND).as_str(), | ||
ljoss17 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
), | ||
}) | ||
} | ||
} | ||
|
||
impl Serialize for GasMultiplier { | ||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> | ||
where | ||
S: Serializer, | ||
{ | ||
self.0.serialize(serializer) | ||
} | ||
} | ||
|
||
impl From<GasMultiplier> for f64 { | ||
fn from(m: GasMultiplier) -> Self { | ||
m.0 | ||
} | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
#[allow(dead_code)] // the fields of the structs defined below are never accessed | ||
mod tests { | ||
|
@@ -323,4 +403,34 @@ mod tests { | |
|
||
assert!(err.contains("a string length of at most")); | ||
} | ||
|
||
#[test] | ||
fn parse_invalid_gas_multiplier() { | ||
#[derive(Debug, Deserialize)] | ||
struct DummyConfig { | ||
gas_multiplier: GasMultiplier, | ||
} | ||
|
||
let err = toml::from_str::<DummyConfig>("gas_multiplier = 0.9") | ||
.unwrap_err() | ||
.to_string(); | ||
|
||
assert!(err.contains("expected a f64 less than")); | ||
} | ||
|
||
#[test] | ||
fn safe_gas_multiplier() { | ||
let gas_multiplier = GasMultiplier::new(0.6); | ||
assert!( | ||
gas_multiplier.is_err(), | ||
"Gas multiplier should be an error if value is lower than 1.0: {:?}", | ||
gas_multiplier | ||
); | ||
} | ||
|
||
#[test] | ||
fn unsafe_gas_multiplier() { | ||
let gas_multiplier = GasMultiplier::unsafe_new(0.6); | ||
assert_eq!(gas_multiplier.to_f64(), 0.6); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -459,6 +459,15 @@ define_error! { | |
e.chain_id, e.default_gas, e.max_gas) | ||
}, | ||
|
||
ConfigValidationGasMultiplierLow | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are many I wonder if there is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will open an issue for this |
||
{ | ||
chain_id: ChainId, | ||
gas_multiplier: f64, | ||
} | ||
|e| { | ||
format!("semantic config validation failed for option `gas_multiplier` of chain '{}', reason: gas multiplier ({}) is smaller than `1.1`, which could trigger gas fee errors in production", e.chain_id, e.gas_multiplier) | ||
}, | ||
|
||
SdkModuleVersion | ||
{ | ||
chain_id: ChainId, | ||
|
Original file line number | Diff line number | Diff line change | ||||||
---|---|---|---|---|---|---|---|---|
|
@@ -29,7 +29,7 @@ use ibc::core::ics02_client::msgs::upgrade_client::MsgUpgradeAnyClient; | |||||||
use ibc::core::ics02_client::trust_threshold::TrustThreshold; | ||||||||
use ibc::core::ics24_host::identifier::{ChainId, ClientId}; | ||||||||
use ibc::downcast; | ||||||||
use ibc::events::{IbcEvent, WithBlockDataType}; | ||||||||
use ibc::events::{IbcEvent, IbcEventType, WithBlockDataType}; | ||||||||
use ibc::timestamp::{Timestamp, TimestampOverflowError}; | ||||||||
use ibc::tx_msg::Msg; | ||||||||
use ibc::Height; | ||||||||
|
@@ -783,6 +783,36 @@ impl<DstChain: ChainHandle, SrcChain: ChainHandle> ForeignClient<DstChain, SrcCh | |||||||
} | ||||||||
|
||||||||
pub fn refresh(&mut self) -> Result<Option<Vec<IbcEvent>>, ForeignClientError> { | ||||||||
match self.try_refresh() { | ||||||||
Ok(res) => { | ||||||||
// If elapsed < refresh_window for the client, `try_refresh()` will | ||||||||
// be successful with an empty vector. | ||||||||
if let Some(ibc_events) = res.clone() { | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think you could avoid cloning the whole collection here: if it destructures to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see, so I removed the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, see if |
||||||||
// The assumption is that only one IbcEventType::ChainError will be | ||||||||
// in the resulting Vec<IbcEvent> if an error occurred. | ||||||||
match ibc_events | ||||||||
.into_iter() | ||||||||
.find(|e| e.event_type() == IbcEventType::ChainError) | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should document the assumption we're making.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or, alternatively, we could have strong type guarantees on this. CC @soareschen @romac There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like @soareschen, I think we should get rid of these There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Tracked in #2565 |
||||||||
{ | ||||||||
Some(ev) => { | ||||||||
return Err(ForeignClientError::chain_error_event( | ||||||||
self.dst_chain().id(), | ||||||||
ev, | ||||||||
)); | ||||||||
} | ||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are many different execution branches and it's not clear what are the post-conditions of the Let's maybe also put a comment to describe what is this match arm for. There is also an |
||||||||
None => { | ||||||||
return Ok(res); | ||||||||
} | ||||||||
} | ||||||||
} | ||||||||
// Return the successful empty vector. | ||||||||
Ok(res) | ||||||||
} | ||||||||
Err(e) => Err(e), | ||||||||
} | ||||||||
} | ||||||||
|
||||||||
fn try_refresh(&mut self) -> Result<Option<Vec<IbcEvent>>, ForeignClientError> { | ||||||||
let (client_state, elapsed) = self.validated_client_state()?; | ||||||||
|
||||||||
// The refresh_window is the maximum duration | ||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Take care to also remove the
Error
enum variant if it is no longer in use.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh thank you, I forgot to remove it.