Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 4 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ resolver = "2"

members = [
"crates/ibc-types",
"crates/ibc-types-core-client",
"crates/ibc-types-lightclients-tendermint",
"crates/ibc-types-timestamp",
"crates/ibc-types-identifier",
"crates/ibc-types-domain-type",
"crates/ibc-types-core-client",
"crates/ibc-types-core-connection",
#"crates/ibc-types-lightclients-tendermint",
]

exclude = [
Expand Down
2 changes: 2 additions & 0 deletions crates/ibc-types-core-client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ all-features = true
default = ["std"]
std = [
"ibc-types-timestamp/std",
"ibc-types-identifier/std",
"ibc-proto/std",
"ics23/std",
"serde/std",
Expand Down Expand Up @@ -54,6 +55,7 @@ mocks-no-std = ["cfg-if"]

[dependencies]
ibc-types-timestamp = { version = "0.2.0", path = "../ibc-types-timestamp", default-features = false }
ibc-types-identifier = { version = "0.2.0", path = "../ibc-types-identifier", default-features = false }
ibc-types-domain-type = { version = "0.2.0", path = "../ibc-types-domain-type", default-features = false }
# Proto definitions for all IBC-related interfaces, e.g., connections or channels.
ibc-proto = { git = "https://github.com/penumbra-zone/ibc-proto-rs", branch = "penumbra",version = "0.30.0", default-features = false, features = ["parity-scale-codec", "borsh"] }
Expand Down
129 changes: 4 additions & 125 deletions crates/ibc-types-core-client/src/client_id.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use core::{
};

use derive_more::Into;
use ibc_types_identifier::{validate_client_identifier, IdentifierError};

use crate::{client_type::ClientType, prelude::*};

Expand All @@ -24,7 +25,7 @@ impl ClientId {
/// assert!(tm_client_id.is_ok());
/// tm_client_id.map(|id| { assert_eq!(&id, "07-tendermint-0") });
/// ```
pub fn new(client_type: ClientType, counter: u64) -> Result<Self, ClientIdParseError> {
pub fn new(client_type: ClientType, counter: u64) -> Result<Self, IdentifierError> {
let prefix = client_type.as_str();
let id = format!("{prefix}-{counter}");
Self::from_str(id.as_str())
Expand All @@ -51,15 +52,15 @@ impl Display for ClientId {
}

impl FromStr for ClientId {
type Err = ClientIdParseError;
type Err = IdentifierError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
validate_client_identifier(s).map(|_| Self(s.to_string()))
}
}

impl TryFrom<String> for ClientId {
type Error = ClientIdParseError;
type Error = IdentifierError;

fn try_from(value: String) -> Result<Self, Self::Error> {
validate_client_identifier(&value).map(|_| Self(value))
Expand Down Expand Up @@ -91,125 +92,3 @@ impl PartialEq<ClientId> for str {
other.as_str().eq(self)
}
}

use displaydoc::Display;

/// An error while parsing a [`ClientId`].
#[derive(Debug, Display)]
pub enum ClientIdParseError {
/// identifier `{id}` cannot contain separator '/'
ContainSeparator { id: String },
/// identifier `{id}` has invalid length `{length}` must be between `{min}`-`{max}` characters
InvalidLength {
id: String,
length: usize,
min: usize,
max: usize,
},
/// identifier `{id}` must only contain alphanumeric characters or `.`, `_`, `+`, `-`, `#`, - `[`, `]`, `<`, `>`
InvalidCharacter { id: String },
/// identifier cannot be empty
Empty,
}

#[cfg(feature = "std")]
impl std::error::Error for ClientIdParseError {}

// TODO: should validate_identifier be put in a common crate with timestamps or something

/// Path separator (ie. forward slash '/')
const PATH_SEPARATOR: char = '/';
const VALID_SPECIAL_CHARS: &str = "._+-#[]<>";

/// Default validator function for identifiers.
///
/// A valid identifier only contain lowercase alphabetic characters, and be of a given min and max
/// length.
fn validate_identifier(id: &str, min: usize, max: usize) -> Result<(), ClientIdParseError> {
assert!(max >= min);

// Check identifier is not empty
if id.is_empty() {
return Err(ClientIdParseError::Empty);
}

// Check identifier does not contain path separators
if id.contains(PATH_SEPARATOR) {
return Err(ClientIdParseError::ContainSeparator { id: id.into() });
}

// Check identifier length is between given min/max
if id.len() < min || id.len() > max {
return Err(ClientIdParseError::InvalidLength {
id: id.into(),
length: id.len(),
min,
max,
});
}

// Check that the identifier comprises only valid characters:
// - Alphanumeric
// - `.`, `_`, `+`, `-`, `#`
// - `[`, `]`, `<`, `>`
if !id
.chars()
.all(|c| c.is_alphanumeric() || VALID_SPECIAL_CHARS.contains(c))
{
return Err(ClientIdParseError::InvalidCharacter { id: id.into() });
}

// All good!
Ok(())
}

/// Default validator function for Client identifiers.
///
/// A valid identifier must be between 9-64 characters and only contain lowercase
/// alphabetic characters,
fn validate_client_identifier(id: &str) -> Result<(), ClientIdParseError> {
validate_identifier(id, 9, 64)
}

#[cfg(test)]
mod tests {
use super::*;
use test_log::test;

#[test]
fn parse_invalid_client_id_min() {
// invalid min client id
let id = validate_client_identifier("client");
assert!(id.is_err())
}

#[test]
fn parse_client_id_max() {
// invalid max client id (test string length is 65)
let id = validate_client_identifier(
"f0isrs5enif9e4td3r2jcbxoevhz6u1fthn4aforq7ams52jn5m48eiesfht9ckpn",
);
assert!(id.is_err())
}

#[test]
fn parse_invalid_id_chars() {
// invalid id chars
let id = validate_identifier("channel@01", 1, 10);
assert!(id.is_err())
}

#[test]
fn parse_invalid_id_empty() {
// invalid id empty
let id = validate_identifier("", 1, 10);
assert!(id.is_err())
}

#[test]
fn parse_invalid_id_path_separator() {
// invalid id with path separator
let id = validate_identifier("id/1", 1, 10);
assert!(id.is_err())
}
}
14 changes: 6 additions & 8 deletions crates/ibc-types-core-client/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@ use crate::prelude::*;

use displaydoc::Display;
use ibc_proto::protobuf::Error as TendermintProtoError;
use ibc_types_identifier::IdentifierError;
use ibc_types_timestamp::Timestamp;

use crate::{
client_id::{self, ClientId},
client_type::ClientType,
};
use crate::{client_id::ClientId, client_type::ClientType};

use crate::height::Height;

Expand All @@ -20,7 +18,7 @@ pub enum Error {
ClientIdentifierConstructor {
client_type: ClientType,
counter: u64,
validation_error: client_id::ClientIdParseError,
validation_error: IdentifierError,
},
/// client not found: `{client_id}`
ClientNotFound { client_id: ClientId },
Expand Down Expand Up @@ -51,19 +49,19 @@ pub enum Error {
/// missing raw client consensus state
MissingRawConsensusState,
/// invalid client id in the update client message: `{0}`
InvalidMsgUpdateClientId(client_id::ClientIdParseError),
InvalidMsgUpdateClientId(IdentifierError),
/// Encode error: `{0}`
Encode(TendermintProtoError),
/// decode error: `{0}`
Decode(prost::DecodeError),
/// invalid client identifier error: `{0}`
InvalidClientIdentifier(client_id::ClientIdParseError),
InvalidClientIdentifier(IdentifierError),
/// invalid raw header error: `{0}`
InvalidRawHeader(TendermintProtoError),
/// missing raw header
MissingRawHeader,
/// invalid raw misbehaviour error: `{0}`
InvalidRawMisbehaviour(client_id::ClientIdParseError),
InvalidRawMisbehaviour(IdentifierError),
/// missing raw misbehaviour
MissingRawMisbehaviour,
/// revision height cannot be zero
Expand Down
2 changes: 1 addition & 1 deletion crates/ibc-types-core-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ mod prelude;
pub mod events;
pub mod msgs;

pub use client_id::{ClientId, ClientIdParseError};
pub use client_id::ClientId;
pub use client_type::ClientType;
pub use error::Error;
pub use height::{Height, HeightParseError};
Expand Down
118 changes: 118 additions & 0 deletions crates/ibc-types-core-connection/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
[package]
name = "ibc-types-core-connection"
version = "0.2.0"
edition = "2021"
license = "Apache-2.0"
readme = "../../README.md"
keywords = ["blockchain", "consensus", "cosmos", "ibc", "tendermint"]
repository = "https://github.com/penumbra-zone/ibc-types"
authors = ["Penumbra Labs <[email protected]"]
rust-version = "1.60"
description = """
Data types for the Inter-Blockchain Communication (IBC) protocol.
This crate defines common data structures that can be reused by different IBC implementations or ecosystem tooling.
"""
# Not publishing to crates.io yet; will iterate quickly via git deps.
publish = false
[package.metadata.docs.rs]
all-features = true

[features]
default = ["std"]
std = [
"ibc-types-timestamp/std",
"ibc-types-identifier/std",
"ibc-types-core-client/std",
"ibc-proto/std",
"ics23/std",
"serde/std",
"serde_json/std",
"erased-serde/std",
"tracing/std",
"prost/std",
"bytes/std",
"subtle-encoding/std",
"sha2/std",
"displaydoc/std",
"num-traits/std",
"uint/std",
"primitive-types/std",
"tendermint/clock",
"tendermint/std",
]
parity-scale-codec = ["dep:parity-scale-codec", "dep:scale-info"]
borsh = ["dep:borsh"]

# This feature is required for token transfer (ICS-20)
serde = ["dep:serde", "dep:serde_derive", "serde_json", "erased-serde"]

# This feature guards the unfinished implementation of the `UpgradeClient` handler.
upgrade_client = []

# This feature grants access to development-time mocking libraries, such as `MockContext` or `MockHeader`.
# Depends on the `testgen` suite for generating Tendermint light blocks.
mocks = ["tendermint-testgen", "tendermint/clock", "cfg-if", "parking_lot"]
mocks-no-std = ["cfg-if"]

[dependencies]
ibc-types-timestamp = { version = "0.2.0", path = "../ibc-types-timestamp", default-features = false }
ibc-types-identifier = { version = "0.2.0", path = "../ibc-types-identifier", default-features = false }
ibc-types-domain-type = { version = "0.2.0", path = "../ibc-types-domain-type", default-features = false }
ibc-types-core-client = { version = "0.2.0", path = "../ibc-types-core-client", default-features = false }
# Proto definitions for all IBC-related interfaces, e.g., connections or channels.
ibc-proto = { git = "https://github.com/penumbra-zone/ibc-proto-rs", branch = "penumbra",version = "0.30.0", default-features = false, features = ["parity-scale-codec", "borsh"] }
ics23 = { version = "0.10.1", default-features = false, features = ["host-functions"] }
time = { version = ">=0.3.0, <0.3.20", default-features = false }
serde_derive = { version = "1.0.104", default-features = false, optional = true }
serde = { version = "1.0", default-features = false, optional = true }
serde_json = { version = "1", default-features = false, optional = true }
erased-serde = { version = "0.3", default-features = false, features = ["alloc"], optional = true }
tracing = { version = "0.1.36", default-features = false }
prost = { version = "0.11", default-features = false }
bytes = { version = "1.2.1", default-features = false }
safe-regex = { version = "0.2.5", default-features = false }
subtle-encoding = { version = "0.5", default-features = false }
sha2 = { version = "0.10.6", default-features = false }
displaydoc = { version = "0.2", default-features = false }
num-traits = { version = "0.2.15", default-features = false }
derive_more = { version = "0.99.17", default-features = false, features = ["from", "into", "display"] }
uint = { version = "0.9", default-features = false }
primitive-types = { version = "0.12.0", default-features = false, features = ["serde_no_std"] }
dyn-clone = "1.0.8"
## for codec encode or decode
parity-scale-codec = { version = "3.0.0", default-features = false, features = ["full"], optional = true }
scale-info = { version = "2.1.2", default-features = false, features = ["derive"], optional = true }
## for borsh encode or decode
borsh = {version = "0.10.0", default-features = false, optional = true }
parking_lot = { version = "0.12.1", default-features = false, optional = true }
cfg-if = { version = "1.0.0", optional = true }

[dependencies.tendermint]
version = "0.31.1"
default-features = false

[dependencies.tendermint-proto]
version = "0.31.1"
default-features = false

[dependencies.tendermint-light-client-verifier]
version = "0.31.1"
default-features = false
features = ["rust-crypto"]

[dependencies.tendermint-testgen]
version = "0.31.1"
optional = true
default-features = false

[dev-dependencies]
env_logger = "0.10.0"
rstest = "0.16.0"
tracing-subscriber = { version = "0.3.14", features = ["fmt", "env-filter", "json"]}
test-log = { version = "0.2.10", features = ["trace"] }
tendermint-rpc = { version = "0.31.1", features = ["http-client", "websocket-client"] }
tendermint-testgen = { version = "0.31.1" } # Needed for generating (synthetic) light blocks.
parking_lot = { version = "0.12.1" }
cfg-if = { version = "1.0.0" }

ibc-types-core-client = { version = "0.2.0", path = "../ibc-types-core-client", features = ["mocks"] }
Loading