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

Fix the connection delay logic to use the header update block time #1917

Merged
merged 51 commits into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
8f0dbaa
Return update height from update_client_{dst,src} methods
hu55a1n1 Feb 23, 2022
a376a67
Add query_host_consensus_state()
hu55a1n1 Feb 24, 2022
6f799dd
Set scheduled time appropriately
hu55a1n1 Feb 24, 2022
c7593ab
Handle connection delay only if batch contains packet events
hu55a1n1 Feb 24, 2022
79e09c3
Apply suggestion
hu55a1n1 Feb 24, 2022
f8ed1ba
Fix mock impl
hu55a1n1 Feb 24, 2022
6877290
Fix schedule_time calculation
hu55a1n1 Feb 24, 2022
52a4a44
Update comment
hu55a1n1 Feb 24, 2022
eab6908
Fix comment
hu55a1n1 Feb 24, 2022
3fcb91f
Add .changelog entry
hu55a1n1 Feb 24, 2022
09b1da6
Improve has_packet_msgs()
hu55a1n1 Feb 24, 2022
6181fb9
Implement query_host_consensus_state() for wrapper chain handles
hu55a1n1 Mar 8, 2022
2b8ef52
Apply suggestion
hu55a1n1 Mar 10, 2022
02e6170
Fix clippy errors
hu55a1n1 Mar 10, 2022
9192eec
Rework update methods
hu55a1n1 Mar 14, 2022
e61c05e
Minor refactoring
hu55a1n1 Mar 14, 2022
6d1a0de
Avoid partitioning events
hu55a1n1 Mar 14, 2022
0691987
Handle misbehaviour case explicitly
hu55a1n1 Mar 14, 2022
a089c6e
Merge remote-tracking branch 'origin/master' into hu55a1n1/1772-fix-c…
soareschen Mar 15, 2022
aa799bf
Add connection delay test
soareschen Mar 15, 2022
27ef25f
Merge remote-tracking branch 'origin/master' into hu55a1n1/1772-fix-c…
soareschen Mar 15, 2022
a9abda4
Use first UpdateClient event to determine processed_height
hu55a1n1 Mar 15, 2022
54281b6
Add comment
hu55a1n1 Mar 15, 2022
fc5c866
Check for frozen clients in case of misbehavior during client update
hu55a1n1 Mar 15, 2022
792011e
Adjust connection delay for avg block time
hu55a1n1 Mar 15, 2022
4636652
Merge branch 'hu55a1n1/1772-fix-conn-delay-check' of github.com:infor…
hu55a1n1 Mar 15, 2022
75d7501
Add TODO for moving to /header endpoint
hu55a1n1 Mar 16, 2022
bf79965
Make update_client_dst() similar to update_client_src()
hu55a1n1 Mar 16, 2022
009d4f4
Fix integration test
hu55a1n1 Mar 16, 2022
4df249a
Compare scheduled time against latest chain time
hu55a1n1 Mar 16, 2022
a42d830
Allow query_host_consensus_state to return latest state when Height i…
hu55a1n1 Mar 16, 2022
c98829c
Fix conn delay elapsed calculation
hu55a1n1 Mar 16, 2022
68fa5d2
Cleanup
hu55a1n1 Mar 16, 2022
f3c4a93
Check for block delay
hu55a1n1 Mar 16, 2022
111fb68
Add config comment for max_expected_time_per_block
hu55a1n1 Mar 16, 2022
8526a1f
Wait for conn delay
hu55a1n1 Mar 16, 2022
52684a3
More comments
hu55a1n1 Mar 17, 2022
287e3a9
Extract all connection-delay logic from RelayPath
hu55a1n1 Mar 18, 2022
d1b712b
Address review feedback
hu55a1n1 Mar 24, 2022
1b2db6e
Merge remote-tracking branch 'origin/master' into hu55a1n1/1772-fix-c…
hu55a1n1 Mar 25, 2022
b9323b8
Merge branch 'master' into hu55a1n1/1772-fix-conn-delay-check
ancazamfir Mar 29, 2022
ef95dd2
Address review feedback
hu55a1n1 Mar 30, 2022
beb9fd9
Closures for conn-delay specific lazy eval
hu55a1n1 Mar 30, 2022
818e614
Merge branch 'hu55a1n1/1772-fix-conn-delay-check' of github.com:infor…
hu55a1n1 Mar 30, 2022
ec076a7
Minor refactoring
hu55a1n1 Mar 30, 2022
afff3c4
Add helpers has_conn_delay_elapsed() and conn_delay_remaining()
hu55a1n1 Mar 30, 2022
8b32f51
Handle connection block delay
hu55a1n1 Mar 30, 2022
2f5679e
Cast block delay to u32
hu55a1n1 Mar 30, 2022
e058358
Extract out CLI specific link code
hu55a1n1 Mar 31, 2022
f8f6a94
Make opdata methods priv
hu55a1n1 Apr 1, 2022
14dcb2a
Apply suggestions from review
hu55a1n1 Apr 1, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Fix the connection delay logic to use the timestamp of the host block when the client update header was installed.
([#1772](https://github.com/informalsystems/ibc-rs/issues/1772))
1 change: 1 addition & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ clock_drift = '5s'
# The block time together with the clock drift are added to the source drift to estimate
# the maximum clock drift when creating a client on this chain. Default: 10s
# For cosmos-SDK chains a good approximation is `timeout_propose` + `timeout_commit`
# Note: This MUST be the same as the `max_expected_time_per_block` genesis parameter for Tendermint chains.
max_block_time = '10s'

# Specify the amount of time to be used as the light client trusting period.
Expand Down
20 changes: 13 additions & 7 deletions modules/src/core/ics04_channel/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,7 @@ pub trait ChannelReader {
fn max_expected_time_per_block(&self) -> Duration;

fn block_delay(&self, delay_period_time: Duration) -> u64 {
let expected_time_per_block = self.max_expected_time_per_block();
if expected_time_per_block.is_zero() {
return 0;
}

FloatCore::ceil(delay_period_time.as_secs_f64() / expected_time_per_block.as_secs_f64())
as u64
calculate_block_delay(delay_period_time, self.max_expected_time_per_block())
}
}

Expand Down Expand Up @@ -279,3 +273,15 @@ pub trait ChannelKeeper {
/// Should never fail.
fn increase_channel_counter(&mut self);
}

pub fn calculate_block_delay(
delay_period_time: Duration,
max_expected_time_per_block: Duration,
) -> u64 {
let expected_time_per_block = max_expected_time_per_block;
hu55a1n1 marked this conversation as resolved.
Show resolved Hide resolved
if expected_time_per_block.is_zero() {
return 0;
}

FloatCore::ceil(delay_period_time.as_secs_f64() / expected_time_per_block.as_secs_f64()) as u64
}
2 changes: 2 additions & 0 deletions relayer/src/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,8 @@ pub trait ChainEndpoint: Sized {
request: QueryBlockRequest,
) -> Result<(Vec<IbcEvent>, Vec<IbcEvent>), Error>;

fn query_host_consensus_state(&self, height: ICSHeight) -> Result<Self::ConsensusState, Error>;

// Provable queries
fn proven_client_state(
&self,
Expand Down
14 changes: 14 additions & 0 deletions relayer/src/chain/cosmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1948,6 +1948,20 @@ impl ChainEndpoint for CosmosSdkChain {
}
}

fn query_host_consensus_state(&self, height: ICSHeight) -> Result<Self::ConsensusState, Error> {
let height = Height::try_from(height.revision_height).map_err(Error::invalid_height)?;

// TODO(hu55a1n1): use the `/header` RPC endpoint instead when we move to tendermint v0.35.x
let rpc_call = match height.value() {
0 => self.rpc_client.latest_block(),
_ => self.rpc_client.block(height),
};
let response = self
.block_on(rpc_call)
.map_err(|e| Error::rpc(self.config.rpc_addr.clone(), e))?;
Ok(response.block.header.into())
}

fn proven_client_state(
&self,
client_id: &ClientId,
Expand Down
7 changes: 7 additions & 0 deletions relayer/src/chain/handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,11 @@ pub enum ChainRequest {
request: QueryBlockRequest,
reply_to: ReplyTo<(Vec<IbcEvent>, Vec<IbcEvent>)>,
},

QueryHostConsensusState {
height: Height,
reply_to: ReplyTo<AnyConsensusState>,
},
}

pub trait ChainHandle: Clone + Send + Sync + Serialize + Debug + 'static {
Expand Down Expand Up @@ -560,4 +565,6 @@ pub trait ChainHandle: Clone + Send + Sync + Serialize + Debug + 'static {
&self,
request: QueryBlockRequest,
) -> Result<(Vec<IbcEvent>, Vec<IbcEvent>), Error>;

fn query_host_consensus_state(&self, height: Height) -> Result<AnyConsensusState, Error>;
}
4 changes: 4 additions & 0 deletions relayer/src/chain/handle/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,10 @@ impl ChainHandle for BaseChainHandle {
) -> Result<(Vec<IbcEvent>, Vec<IbcEvent>), Error> {
self.send(|reply_to| ChainRequest::QueryPacketEventDataFromBlocks { request, reply_to })
}

fn query_host_consensus_state(&self, height: Height) -> Result<AnyConsensusState, Error> {
self.send(|reply_to| ChainRequest::QueryHostConsensusState { height, reply_to })
}
}

impl Serialize for BaseChainHandle {
Expand Down
4 changes: 4 additions & 0 deletions relayer/src/chain/handle/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -412,4 +412,8 @@ impl<Handle: ChainHandle> ChainHandle for CachingChainHandle<Handle> {
) -> Result<(Vec<IbcEvent>, Vec<IbcEvent>), Error> {
self.inner().query_blocks(request)
}

fn query_host_consensus_state(&self, height: Height) -> Result<AnyConsensusState, Error> {
self.inner.query_host_consensus_state(height)
}
}
4 changes: 4 additions & 0 deletions relayer/src/chain/handle/counting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,4 +448,8 @@ impl<Handle: ChainHandle> ChainHandle for CountingChainHandle<Handle> {
self.inc_metric("query_blocks");
self.inner().query_blocks(request)
}

fn query_host_consensus_state(&self, height: Height) -> Result<AnyConsensusState, Error> {
self.inner.query_host_consensus_state(height)
}
}
4 changes: 4 additions & 0 deletions relayer/src/chain/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ impl ChainEndpoint for MockChain {
unimplemented!()
}

fn query_host_consensus_state(&self, _height: Height) -> Result<Self::ConsensusState, Error> {
unimplemented!()
}

fn proven_client_state(
&self,
_client_id: &ClientId,
Expand Down
19 changes: 19 additions & 0 deletions relayer/src/chain/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,10 @@ where
self.query_blocks(request, reply_to)?
},

Ok(ChainRequest::QueryHostConsensusState { height, reply_to }) => {
self.query_host_consensus_state(height, reply_to)?
},

Err(e) => error!("received error via chain request channel: {}", e),
}
},
Expand Down Expand Up @@ -870,4 +874,19 @@ where

Ok(())
}

fn query_host_consensus_state(
&self,
height: Height,
reply_to: ReplyTo<AnyConsensusState>,
) -> Result<(), Error> {
let result = self
.chain
.query_host_consensus_state(height)
.map(|h| h.wrap_any());

reply_to.send(result).map_err(Error::send)?;

Ok(())
}
}
4 changes: 2 additions & 2 deletions relayer/src/link.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ impl<ChainA: ChainHandle, ChainB: ChainHandle> Link<ChainA, ChainB> {
let mut results = vec![];

// Block waiting for all of the scheduled data (until `None` is returned)
while let Some(odata) = self.a_to_b.fetch_scheduled_operational_data() {
while let Some(odata) = self.a_to_b.fetch_scheduled_operational_data()? {
let mut last_res = self
.a_to_b
.relay_from_operational_data::<relay_sender::SyncSender>(odata)?;
Expand All @@ -237,7 +237,7 @@ impl<ChainA: ChainHandle, ChainB: ChainHandle> Link<ChainA, ChainB> {
let mut results = vec![];

// Block waiting for all of the scheduled data
while let Some(odata) = self.a_to_b.fetch_scheduled_operational_data() {
while let Some(odata) = self.a_to_b.fetch_scheduled_operational_data()? {
let mut last_res = self
.a_to_b
.relay_from_operational_data::<relay_sender::SyncSender>(odata)?;
Expand Down
4 changes: 3 additions & 1 deletion relayer/src/link/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,9 @@ define_error! {
e.channel_id, e.chain_id)
},

}
UpdateClientFailed
|_| { "failed to update client" },
}
}

impl HasExpiredOrFrozenError for LinkErrorDetail {
Expand Down
143 changes: 136 additions & 7 deletions relayer/src/link/operational_data.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
use alloc::borrow::Cow;
use core::fmt;
use core::iter;
use std::time::Instant;
use std::time::{Duration, Instant};

use ibc_proto::google::protobuf::Any;
use nanoid::nanoid;
use tracing::{debug, info};

use ibc::core::ics02_client::client_state::ClientState;
use ibc::core::ics04_channel::context::calculate_block_delay;
use ibc::events::IbcEvent;
use ibc::Height;

Expand Down Expand Up @@ -89,23 +91,28 @@ pub struct OperationalData {
pub proofs_height: Height,
pub batch: Vec<TransitMessage>,
pub target: OperationalDataTarget,
/// Stores the time when the clients on the target chain has been updated, i.e., when this data
/// was scheduled. Necessary for packet delays.
pub scheduled_time: Instant,
pub tracking_id: String,
/// Stores `Some(ConnectionDelay)` if the delay is non-zero and `None` otherwise
connection_delay: Option<ConnectionDelay>,
}

impl OperationalData {
pub fn new(
proofs_height: Height,
target: OperationalDataTarget,
tracking_id: impl Into<String>,
connection_delay: Duration,
) -> Self {
let connection_delay = if !connection_delay.is_zero() {
Some(ConnectionDelay::new(connection_delay))
} else {
None
};
OperationalData {
proofs_height,
batch: vec![],
target,
scheduled_time: Instant::now(),
connection_delay,
tracking_id: tracking_id.into(),
}
}
Expand Down Expand Up @@ -141,7 +148,7 @@ impl OperationalData {
relay_path: &RelayPath<ChainA, ChainB>,
) -> Result<TrackedMsgs, LinkError> {
// For zero delay we prepend the client update msgs.
let client_update_msg = if relay_path.zero_delay() {
let client_update_msg = if !self.conn_delay_needed() {
let update_height = self.proofs_height.increment();

debug!(
Expand All @@ -162,7 +169,22 @@ impl OperationalData {

client_update_opt.pop()
} else {
None
let client_state = match self.target {
OperationalDataTarget::Source => relay_path
.src_chain()
.query_client_state(relay_path.src_client_id(), Height::zero())
.map_err(|e| LinkError::query(relay_path.src_chain().id(), e))?,
OperationalDataTarget::Destination => relay_path
.dst_chain()
.query_client_state(relay_path.dst_client_id(), Height::zero())
.map_err(|e| LinkError::query(relay_path.dst_chain().id(), e))?,
};

if client_state.is_frozen() {
return Ok(TrackedMsgs::new(vec![], &self.tracking_id));
} else {
None
}
};

let msgs: Vec<Any> = match client_update_msg {
Expand All @@ -178,6 +200,113 @@ impl OperationalData {

Ok(tm)
}

/// Returns true iff the batch contains a packet event
fn has_packet_msgs(&self) -> bool {
self.batch.iter().any(|msg| msg.event.packet().is_some())
}

/// Returns the `connection_delay` iff the connection delay for this relaying path is non-zero
/// and the `batch` contains packet messages.
fn get_delay_if_needed(&self) -> Option<&ConnectionDelay> {
self.connection_delay
.as_ref()
.filter(|_| self.has_packet_msgs())
}

/// Returns `true` iff the connection delay for this relaying path is non-zero and `op_data`
/// contains packet messages.
pub fn conn_delay_needed(&self) -> bool {
self.get_delay_if_needed().is_some()
}

/// Sets the scheduled time that is used for connection-delay calculations
pub fn set_scheduled_time(&mut self, scheduled_time: Instant) {
if let Some(mut delay) = self.connection_delay.as_mut() {
delay.scheduled_time = scheduled_time;
}
}

/// Sets the update height that is used for connection-delay calculations
pub fn set_update_height(&mut self, update_height: Height) {
if let Some(mut delay) = self.connection_delay.as_mut() {
delay.update_height = Some(update_height);
}
}

/// Returns `Ok(())` if the connection-delay has elapsed and `Err(remaining-delay)` otherwise.
pub fn conn_time_delay_elapsed(&self, chain_time: Instant) -> Result<(), Duration> {
if let Some(delay) = self.get_delay_if_needed() {
delay.conn_time_delay_elapsed(chain_time)
} else {
Ok(())
}
}

/// Returns `Ok(())` if the connection-delay has elapsed and `Err(remaining-delay)` otherwise.
pub fn conn_block_delay_elapsed(
&self,
max_expected_time_per_block: Duration,
latest_height: Height,
) -> Result<(), u64> {
if let Some(delay) = self.get_delay_if_needed() {
let block_delay = delay.conn_block_delay(max_expected_time_per_block);
delay.conn_block_delay_elapsed(block_delay, latest_height)
} else {
Ok(())
}
}
}

/// A struct that holds everything that is required to calculate and deal with the connection-delay
/// feature.
#[derive(Clone)]
struct ConnectionDelay {
delay: Duration,
scheduled_time: Instant,
update_height: Option<Height>,
}

impl ConnectionDelay {
fn new(delay: Duration) -> Self {
Self {
delay,
scheduled_time: Instant::now(),
update_height: None,
}
}

/// Returns `Ok(())` if the connection-delay has elapsed and `Err(remaining-delay)` otherwise.
fn conn_time_delay_elapsed(&self, chain_time: Instant) -> Result<(), Duration> {
// since chain time instant is relative to relayer's current time, it is possible that
// `scheduled_time` is later (by nano secs) than `chain_time`, hence the call to
// `saturating_duration_since()`.
let elapsed = chain_time.saturating_duration_since(self.scheduled_time);
if elapsed > self.delay {
Ok(())
} else {
Err(elapsed)
}
}

/// Returns `Ok(())` if the connection-delay has elapsed and `Err(remaining-delay)` otherwise.
fn conn_block_delay_elapsed(&self, block_delay: u64, latest_height: Height) -> Result<(), u64> {
let acceptable_height = self
.update_height
.expect("processed height not set")
.add(block_delay);
if latest_height >= acceptable_height {
Ok(())
} else {
debug_assert!(acceptable_height.revision_number == latest_height.revision_number);
Err(acceptable_height.revision_height - latest_height.revision_height)
}
}

/// Calculates and returns the block-delay based on the `max_expected_time_per_block`
fn conn_block_delay(&self, max_expected_time_per_block: Duration) -> u64 {
calculate_block_delay(self.delay, max_expected_time_per_block)
}
}

/// A lightweight informational data structure that can be extracted
Expand Down
Loading