Skip to content

Commit 966d0de

Browse files
imp(ibc)!: refactor Timestamp type and distinguish timeout timestamp (#1307)
* imp: refactor Timestamp type and distinguish timeout timestamp * fix: timestamp tests * fix: correct serde impls + tests * imp: From<u64> nevel fails + tests * fix: From<u64> for TimeoutTimestamp never fails * fix: correct from_unix_timestamp to reject large seconds + remove From<u64> for Timestamp * chore: apply review comments * fix: cargo doc * Fix typo in doc comment * fix: remove redundant negative check in Sub impl * tests: add timout arithmetic tests * docs: add a comment for From<u64> for TimeoutTimestamp * fix: revert client_processed_times signature * chore: add changelog --------- Co-authored-by: Sean Chen <[email protected]>
1 parent 7e39192 commit 966d0de

File tree

45 files changed

+636
-408
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+636
-408
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- [ibc-core] Separate the packet timeout timestamp from the host `Timestamp` by
2+
defining a new specific `TimeoutTimestamp`.
3+
([\#1296](https://github.com/cosmos/ibc-rs/issues/1296))
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- [ibc-primitive] Decouple `Timestamp` definition from `tendermint::Time`.
2+
([\#180](https://github.com/cosmos/ibc-rs/issues/180))
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
- [ibc-core] Prevent `Timestamp::nanoseconds` from panicking by disallowing
2+
negative values from `tendermint::Time`.
3+
([\#1306](https://github.com/cosmos/ibc-rs/issues/1306))

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ lint: ## Lint the code using rustfmt, clippy and whitespace lints.
1414
$(MAKE) fmt
1515
$(MAKE) clippy
1616
$(MAKE) lint-toml
17-
$(MAKE) -C ./cosmwasm lint $@
17+
$(MAKE) -C ./cosmwasm lint
1818
bash ./ci/code-quality/whitespace-lints.sh
1919

2020
fmt: ## Format the code using nightly rustfmt.

ci/no-std-check/Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cosmwasm/ibc-clients/cw-context/src/context/client_ctx.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,7 @@ where
6464
description: "time key cannot be converted to u64".to_string(),
6565
})?);
6666

67-
let timestamp = Timestamp::from_nanoseconds(time).map_err(|e| ClientError::Other {
68-
description: e.to_string(),
69-
})?;
67+
let timestamp = Timestamp::from_nanoseconds(time);
7068

7169
let height_key = self.client_update_height_key(height);
7270

cosmwasm/ibc-clients/cw-context/src/context/custom_ctx.rs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,7 @@ where
2121
fn host_timestamp(&self) -> Result<Timestamp, ContextError> {
2222
let time = self.env().block.time;
2323

24-
let host_timestamp =
25-
Timestamp::from_nanoseconds(time.nanos()).map_err(|e| ClientError::Other {
26-
description: e.to_string(),
27-
})?;
24+
let host_timestamp = Timestamp::from_nanoseconds(time.nanos());
2825

2926
Ok(host_timestamp)
3027
}

cosmwasm/ibc-clients/ics07-tendermint/src/tests/helper.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ pub fn dummy_checksum() -> Binary {
1717
pub fn dummy_sov_consensus_state(timestamp: IbcTimestamp) -> ConsensusState {
1818
ConsensusState::new(
1919
vec![0].into(),
20-
timestamp.into_tm_time().expect("Time exists"),
20+
timestamp.into_tm_time(),
2121
// Hash of default validator set
2222
Hash::from_str("D6B93922C33AAEBEC9043566CB4B1B48365B1358B67C7DEF986D9EE1861BC143")
2323
.expect("Never fails"),

ibc-apps/ics20-transfer/types/src/msgs/transfer.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
//! Defines the token transfer message type
22
33
use ibc_core::channel::types::error::PacketError;
4-
use ibc_core::channel::types::timeout::TimeoutHeight;
4+
use ibc_core::channel::types::timeout::{TimeoutHeight, TimeoutTimestamp};
55
use ibc_core::handler::types::error::ContextError;
66
use ibc_core::host::types::identifiers::{ChannelId, PortId};
77
use ibc_core::primitives::prelude::*;
8-
use ibc_core::primitives::Timestamp;
98
use ibc_proto::google::protobuf::Any;
109
use ibc_proto::ibc::applications::transfer::v1::MsgTransfer as RawMsgTransfer;
1110
use ibc_proto::Protobuf;
@@ -45,22 +44,20 @@ pub struct MsgTransfer {
4544
pub timeout_height_on_b: TimeoutHeight,
4645
/// Timeout timestamp relative to the current block timestamp.
4746
/// The timeout is disabled when set to 0.
48-
pub timeout_timestamp_on_b: Timestamp,
47+
pub timeout_timestamp_on_b: TimeoutTimestamp,
4948
}
5049

5150
impl TryFrom<RawMsgTransfer> for MsgTransfer {
5251
type Error = TokenTransferError;
5352

5453
fn try_from(raw_msg: RawMsgTransfer) -> Result<Self, Self::Error> {
55-
let timeout_timestamp_on_b = Timestamp::from_nanoseconds(raw_msg.timeout_timestamp)
56-
.map_err(PacketError::InvalidPacketTimestamp)
57-
.map_err(ContextError::from)?;
58-
5954
let timeout_height_on_b: TimeoutHeight = raw_msg
6055
.timeout_height
6156
.try_into()
6257
.map_err(ContextError::from)?;
6358

59+
let timeout_timestamp_on_b: TimeoutTimestamp = raw_msg.timeout_timestamp.into();
60+
6461
// Packet timeout height and packet timeout timestamp cannot both be unset.
6562
if !timeout_height_on_b.is_set() && !timeout_timestamp_on_b.is_set() {
6663
return Err(ContextError::from(PacketError::MissingTimeout))?;

ibc-apps/ics721-nft-transfer/types/src/msgs/transfer.rs

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
//! Defines the Non-Fungible Token Transfer message type
22
33
use ibc_core::channel::types::error::PacketError;
4-
use ibc_core::channel::types::timeout::TimeoutHeight;
4+
use ibc_core::channel::types::timeout::{TimeoutHeight, TimeoutTimestamp};
55
use ibc_core::handler::types::error::ContextError;
66
use ibc_core::host::types::identifiers::{ChannelId, PortId};
77
use ibc_core::primitives::prelude::*;
8-
use ibc_core::primitives::Timestamp;
98
use ibc_proto::google::protobuf::Any;
109
use ibc_proto::ibc::applications::nft_transfer::v1::MsgTransfer as RawMsgTransfer;
1110
use ibc_proto::Protobuf;
@@ -45,22 +44,20 @@ pub struct MsgTransfer {
4544
pub timeout_height_on_b: TimeoutHeight,
4645
/// Timeout timestamp relative to the current block timestamp.
4746
/// The timeout is disabled when set to 0.
48-
pub timeout_timestamp_on_b: Timestamp,
47+
pub timeout_timestamp_on_b: TimeoutTimestamp,
4948
}
5049

5150
impl TryFrom<RawMsgTransfer> for MsgTransfer {
5251
type Error = NftTransferError;
5352

5453
fn try_from(raw_msg: RawMsgTransfer) -> Result<Self, Self::Error> {
55-
let timeout_timestamp_on_b = Timestamp::from_nanoseconds(raw_msg.timeout_timestamp)
56-
.map_err(PacketError::InvalidPacketTimestamp)
57-
.map_err(ContextError::from)?;
58-
5954
let timeout_height_on_b: TimeoutHeight = raw_msg
6055
.timeout_height
6156
.try_into()
6257
.map_err(ContextError::from)?;
6358

59+
let timeout_timestamp_on_b: TimeoutTimestamp = raw_msg.timeout_timestamp.into();
60+
6461
// Packet timeout height and packet timeout timestamp cannot both be unset.
6562
if !timeout_height_on_b.is_set() && !timeout_timestamp_on_b.is_set() {
6663
return Err(ContextError::from(PacketError::MissingTimeout))?;

ibc-clients/ics07-tendermint/src/client_state/common.rs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,12 +150,6 @@ pub fn consensus_state_status<CS: ConsensusState>(
150150
host_timestamp: &Timestamp,
151151
trusting_period: Duration,
152152
) -> Result<Status, ClientError> {
153-
if !host_timestamp.is_set() {
154-
return Err(ClientError::Other {
155-
description: "host timestamp is none".into(),
156-
});
157-
}
158-
159153
// Note: if the `duration_since()` is `None`, indicating that the latest
160154
// consensus state is in the future, then we don't consider the client
161155
// to be expired.

ibc-clients/ics07-tendermint/src/client_state/execution.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -336,12 +336,7 @@ where
336336
let tm_consensus_state: ConsensusStateType =
337337
consensus_state.try_into().map_err(Into::into)?;
338338

339-
let host_timestamp =
340-
ctx.host_timestamp()?
341-
.into_tm_time()
342-
.ok_or_else(|| ClientError::Other {
343-
description: String::from("host timestamp is not a valid TM timestamp"),
344-
})?;
339+
let host_timestamp = ctx.host_timestamp()?.into_tm_time();
345340

346341
let tm_consensus_state_timestamp = tm_consensus_state.timestamp();
347342
let tm_consensus_state_expiry = (tm_consensus_state_timestamp

ibc-clients/ics07-tendermint/src/client_state/misbehaviour.rs

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ pub fn verify_misbehaviour_header<H>(
8484
header: &TmHeader,
8585
chain_id: &ChainId,
8686
options: &Options,
87-
trusted_timestamp: Time,
87+
trusted_time: Time,
8888
trusted_next_validator_hash: Hash,
8989
current_timestamp: Timestamp,
9090
verifier: &impl Verifier,
@@ -97,12 +97,15 @@ where
9797

9898
// ensure trusted consensus state is within trusting period
9999
{
100-
let duration_since_consensus_state = current_timestamp
101-
.duration_since(&trusted_timestamp.into())
102-
.ok_or_else(|| ClientError::InvalidConsensusStateTimestamp {
103-
time1: trusted_timestamp.into(),
104-
time2: current_timestamp,
105-
})?;
100+
let trusted_timestamp = trusted_time.try_into().expect("time conversion failed");
101+
102+
let duration_since_consensus_state =
103+
current_timestamp.duration_since(&trusted_timestamp).ok_or(
104+
ClientError::InvalidConsensusStateTimestamp {
105+
time1: trusted_timestamp,
106+
time2: current_timestamp,
107+
},
108+
)?;
106109

107110
if duration_since_consensus_state >= options.trusting_period {
108111
return Err(Error::ConsensusStateTimestampGteTrustingPeriod {
@@ -123,15 +126,10 @@ where
123126
description: format!("failed to parse chain id: {e}"),
124127
})?;
125128

126-
let trusted_state = header.as_trusted_block_state(
127-
tm_chain_id,
128-
trusted_timestamp,
129-
trusted_next_validator_hash,
130-
)?;
129+
let trusted_state =
130+
header.as_trusted_block_state(tm_chain_id, trusted_time, trusted_next_validator_hash)?;
131131

132-
let current_timestamp = current_timestamp.into_tm_time().ok_or(ClientError::Other {
133-
description: "host timestamp must not be zero".to_string(),
134-
})?;
132+
let current_timestamp = current_timestamp.into_tm_time();
135133

136134
verifier
137135
.verify_misbehaviour_header(untrusted_state, trusted_state, options, current_timestamp)

ibc-clients/ics07-tendermint/src/client_state/update_client.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,12 +83,7 @@ where
8383
next_validators: None,
8484
};
8585

86-
let now =
87-
ctx.host_timestamp()?
88-
.into_tm_time()
89-
.ok_or_else(|| ClientError::ClientSpecific {
90-
description: "host timestamp is not a valid TM timestamp".to_string(),
91-
})?;
86+
let now = ctx.host_timestamp()?.into_tm_time();
9287

9388
// main header verification, delegated to the tendermint-light-client crate.
9489
verifier

ibc-clients/ics07-tendermint/src/consensus_state.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ impl ConsensusStateTrait for ConsensusState {
9393
}
9494

9595
fn timestamp(&self) -> Timestamp {
96-
self.0.timestamp.into()
96+
self.0
97+
.timestamp
98+
.try_into()
99+
.expect("UNIX Timestamp can't be negative")
97100
}
98101
}

ibc-clients/ics07-tendermint/types/src/error.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use ibc_core_commitment_types::error::CommitmentError;
99
use ibc_core_host_types::error::IdentifierError;
1010
use ibc_core_host_types::identifiers::ClientId;
1111
use ibc_primitives::prelude::*;
12+
use ibc_primitives::TimestampError;
1213
use tendermint::{Error as TendermintError, Hash};
1314
use tendermint_light_client_verifier::errors::VerificationErrorDetail as LightClientErrorDetail;
1415
use tendermint_light_client_verifier::operations::VotingPowerTally;
@@ -58,10 +59,8 @@ pub enum Error {
5859
InvalidRawHeader(TendermintError),
5960
/// invalid raw misbehaviour: `{reason}`
6061
InvalidRawMisbehaviour { reason: String },
61-
/// given other previous updates, header timestamp should be at most `{max}`, but was `{actual}`
62-
HeaderTimestampTooHigh { actual: String, max: String },
63-
/// given other previous updates, header timestamp should be at least `{min}`, but was `{actual}`
64-
HeaderTimestampTooLow { actual: String, min: String },
62+
/// invalid header timestamp: `{0}`
63+
InvalidHeaderTimestamp(TimestampError),
6564
/// header revision height = `{height}` is invalid
6665
InvalidHeaderHeight { height: u64 },
6766
/// frozen height is missing

ibc-clients/ics07-tendermint/types/src/header.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,12 @@ impl Display for Header {
4747
}
4848

4949
impl Header {
50-
pub fn timestamp(&self) -> Timestamp {
51-
self.signed_header.header.time.into()
50+
pub fn timestamp(&self) -> Result<Timestamp, Error> {
51+
self.signed_header
52+
.header
53+
.time
54+
.try_into()
55+
.map_err(Error::InvalidHeaderTimestamp)
5256
}
5357

5458
pub fn height(&self) -> Height {

ibc-core/ics02-client/types/src/error.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,6 @@ pub enum ClientError {
8181
},
8282
/// invalid commitment proof bytes error: `{0}`
8383
InvalidCommitmentProof(CommitmentError),
84-
/// invalid packet timeout timestamp value error: `{0}`
85-
InvalidPacketTimestamp(ibc_primitives::ParseTimestampError),
8684
/// mismatch between client and arguments types
8785
ClientArgsTypeMismatch { client_type: ClientType },
8886
/// timestamp is invalid or missing, timestamp=`{time1}`, now=`{time2}`
@@ -133,7 +131,6 @@ impl std::error::Error for ClientError {
133131
| Self::InvalidClientIdentifier(e)
134132
| Self::InvalidRawMisbehaviour(e) => Some(e),
135133
Self::InvalidCommitmentProof(e) | Self::Ics23Verification(e) => Some(e),
136-
Self::InvalidPacketTimestamp(e) => Some(e),
137134
_ => None,
138135
}
139136
}

ibc-core/ics03-connection/types/src/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use ibc_core_client_types::{error as client_error, Height};
55
use ibc_core_host_types::error::IdentifierError;
66
use ibc_core_host_types::identifiers::{ClientId, ConnectionId};
77
use ibc_primitives::prelude::*;
8-
use ibc_primitives::{Timestamp, TimestampOverflowError};
8+
use ibc_primitives::{Timestamp, TimestampError};
99

1010
use crate::version::Version;
1111

@@ -80,7 +80,7 @@ pub enum ConnectionError {
8080
earliest_valid_time: Timestamp,
8181
},
8282
/// timestamp overflowed error: `{0}`
83-
TimestampOverflow(TimestampOverflowError),
83+
TimestampOverflow(TimestampError),
8484
/// connection counter overflow error
8585
CounterOverflow,
8686
/// other error: `{description}`

ibc-core/ics04-channel/src/handler/recv_packet.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use ibc_core_host::types::path::{
1616
use ibc_core_host::{ExecutionContext, ValidationContext};
1717
use ibc_core_router::module::Module;
1818
use ibc_primitives::prelude::*;
19-
use ibc_primitives::Expiry;
2019

2120
pub fn recv_packet_validate<ValCtx>(ctx_b: &ValCtx, msg: MsgRecvPacket) -> Result<(), ContextError>
2221
where
@@ -171,7 +170,11 @@ where
171170
}
172171

173172
let latest_timestamp = ctx_b.host_timestamp()?;
174-
if let Expiry::Expired = latest_timestamp.check_expiry(&msg.packet.timeout_timestamp_on_b) {
173+
if msg
174+
.packet
175+
.timeout_timestamp_on_b
176+
.has_expired(&latest_timestamp)
177+
{
175178
return Err(PacketError::LowPacketTimestamp.into());
176179
}
177180

ibc-core/ics04-channel/src/handler/send_packet.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ use ibc_core_host::types::path::{
1010
ChannelEndPath, ClientConsensusStatePath, CommitmentPath, SeqSendPath,
1111
};
1212
use ibc_primitives::prelude::*;
13-
use ibc_primitives::Expiry;
1413

1514
use crate::context::{SendPacketExecutionContext, SendPacketValidationContext};
1615

@@ -81,7 +80,7 @@ pub fn send_packet_validate(
8180
client_val_ctx_a.consensus_state(&client_cons_state_path_on_a)?;
8281
let latest_timestamp = consensus_state_of_b_on_a.timestamp();
8382
let packet_timestamp = packet.timeout_timestamp_on_b;
84-
if let Expiry::Expired = latest_timestamp.check_expiry(&packet_timestamp) {
83+
if packet_timestamp.has_expired(&latest_timestamp) {
8584
return Err(PacketError::LowPacketTimestamp.into());
8685
}
8786

ibc-core/ics04-channel/types/Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ tendermint = { workspace = true }
4343
parity-scale-codec = { workspace = true, optional = true }
4444
scale-info = { workspace = true, optional = true }
4545

46+
[dev-dependencies]
47+
rstest = { workspace = true }
48+
serde-json = { workspace = true }
49+
4650
[features]
4751
default = [ "std" ]
4852
std = [

ibc-core/ics04-channel/types/src/commitment.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
//! Types and utilities related to packet commitments.
22
33
use ibc_primitives::prelude::*;
4-
use ibc_primitives::Timestamp;
54

65
use super::acknowledgement::Acknowledgement;
7-
use crate::timeout::TimeoutHeight;
6+
use crate::timeout::{TimeoutHeight, TimeoutTimestamp};
87

98
/// Packet commitment
109
#[cfg_attr(
@@ -87,7 +86,7 @@ impl From<Vec<u8>> for AcknowledgementCommitment {
8786
pub fn compute_packet_commitment(
8887
packet_data: &[u8],
8988
timeout_height: &TimeoutHeight,
90-
timeout_timestamp: &Timestamp,
89+
timeout_timestamp: &TimeoutTimestamp,
9190
) -> PacketCommitment {
9291
let mut hash_input = [0; 8 * 3 + 32];
9392

@@ -128,7 +127,7 @@ mod test {
128127
let actual = compute_packet_commitment(
129128
b"packet data",
130129
&TimeoutHeight::At(ibc_core_client_types::Height::new(42, 24).unwrap()),
131-
&Timestamp::from_nanoseconds(0x42).unwrap(),
130+
&TimeoutTimestamp::from(0x42),
132131
);
133132
assert_eq!(&expected[..], actual.as_ref());
134133
}

0 commit comments

Comments
 (0)