From 8bd987c6e87d5fc2f7306e5ebb69ae69375129fb Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 30 Jun 2021 11:10:32 +0200 Subject: [PATCH 01/25] Use flex-error for tendermint --- light-client/src/light_client.rs | 4 +- p2p/src/secret_connection/public_key.rs | 2 +- rpc/src/lib.rs | 14 +- tendermint/Cargo.toml | 5 +- tendermint/src/abci/gas.rs | 9 +- tendermint/src/abci/transaction/hash.rs | 6 +- tendermint/src/account.rs | 6 +- tendermint/src/block.rs | 32 +- tendermint/src/block/commit.rs | 7 +- tendermint/src/block/commit_sig.rs | 40 +- tendermint/src/block/header.rs | 26 +- tendermint/src/block/height.rs | 14 +- tendermint/src/block/id.rs | 14 +- tendermint/src/block/meta.rs | 6 +- tendermint/src/block/parts.rs | 18 +- tendermint/src/block/round.rs | 13 +- tendermint/src/block/signed_header.rs | 14 +- tendermint/src/block/size.rs | 4 +- tendermint/src/chain/id.rs | 22 +- tendermint/src/config.rs | 66 ++-- tendermint/src/config/node_key.rs | 16 +- tendermint/src/config/priv_validator_key.rs | 16 +- tendermint/src/consensus/params.rs | 20 +- tendermint/src/error.rs | 360 +++++++++--------- tendermint/src/evidence.rs | 29 +- tendermint/src/hash.rs | 16 +- tendermint/src/lib.rs | 5 +- tendermint/src/net.rs | 15 +- tendermint/src/node/id.rs | 8 +- tendermint/src/proposal.rs | 6 +- tendermint/src/proposal/canonical_proposal.rs | 13 +- tendermint/src/proposal/msg_type.rs | 4 +- tendermint/src/proposal/sign_proposal.rs | 4 +- tendermint/src/public_key.rs | 40 +- tendermint/src/signature.rs | 4 +- tendermint/src/time.rs | 18 +- tendermint/src/timeout.rs | 20 +- tendermint/src/validator.rs | 20 +- tendermint/src/vote.rs | 10 +- tendermint/src/vote/canonical_vote.rs | 17 +- tendermint/src/vote/power.rs | 11 +- tendermint/src/vote/sign_vote.rs | 19 +- tendermint/src/vote/validator_index.rs | 14 +- test/src/pipe.rs | 3 +- 44 files changed, 519 insertions(+), 491 deletions(-) diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index ac8565035..53c5dcd1c 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -138,8 +138,8 @@ impl LightClient { /// should be trusted based on a previously verified light block. /// - When doing _forward_ verification, the Scheduler component decides which height to try to /// verify next, in case the current block pass verification but cannot be trusted yet. - /// - When doing _backward_ verification, the Hasher component is used to determine - /// whether the `last_block_id` hash of a block matches the hash of the block right below it. + /// - When doing _backward_ verification, the Hasher component is used to determine whether the + /// `last_block_id` hash of a block matches the hash of the block right below it. /// /// ## Implements /// - [LCV-DIST-SAFE.1] diff --git a/p2p/src/secret_connection/public_key.rs b/p2p/src/secret_connection/public_key.rs index 5f83cfb2d..45da0f358 100644 --- a/p2p/src/secret_connection/public_key.rs +++ b/p2p/src/secret_connection/public_key.rs @@ -24,7 +24,7 @@ impl PublicKey { pub fn from_raw_ed25519(bytes: &[u8]) -> Result { ed25519::PublicKey::from_bytes(bytes) .map(Self::Ed25519) - .map_err(|_| error::Kind::Crypto.into()) + .map_err(error::signature_error) } /// Get Ed25519 public key diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index b629778f0..c3da8b640 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -8,14 +8,12 @@ //! //! Several client-related features are provided at present: //! -//! * `http-client` - Provides [`HttpClient`], which is a basic RPC client that -//! interacts with remote Tendermint nodes via **JSON-RPC over HTTP or -//! HTTPS**. This client does not provide [`event::Event`] subscription -//! functionality. See the [Tendermint RPC] for more details. -//! * `websocket-client` - Provides [`WebSocketClient`], which provides full -//! client functionality, including general RPC functionality as well as -//! [`event::Event`] subscription functionality. Can be used over secure -//! (`wss://`) and unsecure (`ws://`) connections. +//! * `http-client` - Provides [`HttpClient`], which is a basic RPC client that interacts with +//! remote Tendermint nodes via **JSON-RPC over HTTP or HTTPS**. This client does not provide +//! [`event::Event`] subscription functionality. See the [Tendermint RPC] for more details. +//! * `websocket-client` - Provides [`WebSocketClient`], which provides full client functionality, +//! including general RPC functionality as well as [`event::Event`] subscription functionality. +//! Can be used over secure (`wss://`) and unsecure (`ws://`) connections. //! //! ### Mock Clients //! diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index 343065601..01316ab8e 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -36,7 +36,7 @@ crate-type = ["cdylib", "rlib"] anomaly = "0.2" async-trait = "0.1" bytes = "1.0" -chrono = { version = "0.4", features = ["serde"] } +chrono = { version = "0.4.19", features = ["serde"] } ed25519 = "1" ed25519-dalek = { version = "1", features = ["serde"] } futures = "0.3" @@ -57,11 +57,14 @@ tendermint-proto = { version = "0.20.0", path = "../proto" } toml = { version = "0.5" } url = { version = "2.2" } zeroize = { version = "1.1", features = ["zeroize_derive"] } +flex-error = "0.2.1" +time = "0.1.40" k256 = { version = "0.9", optional = true, features = ["ecdsa"] } ripemd160 = { version = "0.9", optional = true } [features] +default = ["secp256k1"] secp256k1 = ["k256", "ripemd160"] [dev-dependencies] diff --git a/tendermint/src/abci/gas.rs b/tendermint/src/abci/gas.rs index 5ccfce00c..322b6b6ca 100644 --- a/tendermint/src/abci/gas.rs +++ b/tendermint/src/abci/gas.rs @@ -5,7 +5,7 @@ //! //! -use crate::{Error, Kind}; +use crate::error::{self, Error}; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use std::{ fmt::{self, Display}, @@ -45,7 +45,12 @@ impl FromStr for Gas { type Err = Error; fn from_str(s: &str) -> Result { - Ok(Self::from(s.parse::().map_err(|_| Kind::Parse)?)) + let res = s + .parse::() + .map_err(|e| error::parse_int_error(s.to_string(), e))? + .into(); + + Ok(res) } } diff --git a/tendermint/src/abci/transaction/hash.rs b/tendermint/src/abci/transaction/hash.rs index a4f946c42..49dc2a18c 100644 --- a/tendermint/src/abci/transaction/hash.rs +++ b/tendermint/src/abci/transaction/hash.rs @@ -1,6 +1,6 @@ //! Transaction hashes -use crate::error::{Error, Kind}; +use crate::error::{self, Error}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::{ fmt::{self, Debug, Display}, @@ -64,10 +64,10 @@ impl FromStr for Hash { // Accept either upper or lower case hex let bytes = hex::decode_upper(s) .or_else(|_| hex::decode(s)) - .map_err(|_| Kind::Parse.context("hash decode"))?; + .map_err(error::subtle_encoding_error)?; if bytes.len() != LENGTH { - return Err(Kind::Parse.context("hash length").into()); + return Err(error::invalid_hash_size_error()); } let mut result_bytes = [0u8; LENGTH]; diff --git a/tendermint/src/account.rs b/tendermint/src/account.rs index 488ba1769..e1cbd00c0 100644 --- a/tendermint/src/account.rs +++ b/tendermint/src/account.rs @@ -1,7 +1,7 @@ //! Tendermint accounts use crate::{ - error::{Error, Kind}, + error::{self, Error}, public_key::Ed25519, }; @@ -36,7 +36,7 @@ impl TryFrom> for Id { fn try_from(value: Vec) -> Result { if value.len() != LENGTH { - return Err(Kind::InvalidAccountIdLength.into()); + return Err(error::invalid_account_id_length_error()); } let mut slice: [u8; LENGTH] = [0; LENGTH]; slice.copy_from_slice(&value[..]); @@ -118,7 +118,7 @@ impl FromStr for Id { // Accept either upper or lower case hex let bytes = hex::decode_upper(s) .or_else(|_| hex::decode(s)) - .map_err(|_| Kind::Parse.context("account id decode"))?; + .map_err(error::subtle_encoding_error)?; bytes.try_into() } diff --git a/tendermint/src/block.rs b/tendermint/src/block.rs index e3e49485a..235549ab2 100644 --- a/tendermint/src/block.rs +++ b/tendermint/src/block.rs @@ -21,7 +21,7 @@ pub use self::{ round::*, size::Size, }; -use crate::{abci::transaction, evidence, Error, Kind}; +use crate::{abci::transaction, error, error::Error, evidence}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::Block as RawBlock; @@ -55,7 +55,10 @@ impl TryFrom for Block { type Error = Error; fn try_from(value: RawBlock) -> Result { - let header: Header = value.header.ok_or(Kind::MissingHeader)?.try_into()?; + let header: Header = value + .header + .ok_or_else(error::missing_header_error)? + .try_into()?; // if last_commit is Commit::Default, it is considered nil by Go. let last_commit = value .last_commit @@ -63,9 +66,9 @@ impl TryFrom for Block { .transpose()? .filter(|c| c != &Commit::default()); if last_commit.is_none() && header.height.value() != 1 { - return Err(Kind::InvalidBlock - .context("last_commit is empty on non-first block") - .into()); + return Err(error::invalid_block_error( + "last_commit is empty on non-first block".to_string(), + )); } // Todo: Figure out requirements. //if last_commit.is_some() && header.height.value() == 1 { @@ -74,8 +77,11 @@ impl TryFrom for Block { //} Ok(Block { header, - data: value.data.ok_or(Kind::MissingData)?.try_into()?, - evidence: value.evidence.ok_or(Kind::MissingEvidence)?.try_into()?, + data: value.data.ok_or_else(error::missing_data_error)?.into(), + evidence: value + .evidence + .ok_or_else(error::missing_evidence_error)? + .try_into()?, last_commit, }) } @@ -101,14 +107,14 @@ impl Block { last_commit: Option, ) -> Result { if last_commit.is_none() && header.height.value() != 1 { - return Err(Kind::InvalidBlock - .context("last_commit is empty on non-first block") - .into()); + return Err(error::invalid_block_error( + "last_commit is empty on non-first block".to_string(), + )); } if last_commit.is_some() && header.height.value() == 1 { - return Err(Kind::InvalidBlock - .context("last_commit is filled on first block") - .into()); + return Err(error::invalid_block_error( + "last_commit is filled on first block".to_string(), + )); } Ok(Block { header, diff --git a/tendermint/src/block/commit.rs b/tendermint/src/block/commit.rs index 217d1dd18..34d319e83 100644 --- a/tendermint/src/block/commit.rs +++ b/tendermint/src/block/commit.rs @@ -2,7 +2,7 @@ use crate::block::commit_sig::CommitSig; use crate::block::{Height, Id, Round}; -use crate::{Error, Kind}; +use crate::error::{self, Error}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::Commit as RawCommit; @@ -40,7 +40,10 @@ impl TryFrom for Commit { Ok(Self { height: value.height.try_into()?, round: value.round.try_into()?, - block_id: value.block_id.ok_or(Kind::InvalidBlock)?.try_into()?, /* gogoproto.nullable = false */ + block_id: value + .block_id + .ok_or_else(|| error::invalid_block_error("missing block id".to_string()))? + .try_into()?, /* gogoproto.nullable = false */ signatures: signatures?, }) } diff --git a/tendermint/src/block/commit_sig.rs b/tendermint/src/block/commit_sig.rs index 368e005c7..7381be53c 100644 --- a/tendermint/src/block/commit_sig.rs +++ b/tendermint/src/block/commit_sig.rs @@ -1,7 +1,7 @@ //! CommitSig within Commit +use crate::error::{self, Error}; use crate::{account, Signature, Time}; -use crate::{Error, Kind}; use num_traits::ToPrimitive; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::BlockIdFlag; @@ -74,47 +74,55 @@ impl TryFrom for CommitSig { let timestamp = value.timestamp.unwrap(); // 0001-01-01T00:00:00.000Z translates to EPOCH-62135596800 seconds if timestamp.nanos != 0 || timestamp.seconds != -62135596800 { - return Err(Kind::InvalidTimestamp - .context("absent commitsig has non-zero timestamp") - .into()); + return Err(error::invalid_timestamp_error( + "absent commitsig has non-zero timestamp".to_string(), + )); } } if !value.signature.is_empty() { - return Err(Kind::InvalidSignature.into()); + return Err(error::invalid_signature_error( + "empty signature".to_string(), + )); } return Ok(CommitSig::BlockIdFlagAbsent); } if value.block_id_flag == BlockIdFlag::Commit.to_i32().unwrap() { if value.signature.is_empty() { - return Err(Kind::InvalidSignature - .context("regular commitsig has no signature") - .into()); + return Err(error::invalid_signature_error( + "regular commitsig has no signature".to_string(), + )); } if value.validator_address.is_empty() { - return Err(Kind::InvalidValidatorAddress.into()); + return Err(error::invalid_validator_address_error()); } return Ok(CommitSig::BlockIdFlagCommit { validator_address: value.validator_address.try_into()?, - timestamp: value.timestamp.ok_or(Kind::NoTimestamp)?.try_into()?, + timestamp: value + .timestamp + .ok_or_else(error::missing_timestamp_error)? + .into(), signature: value.signature.try_into()?, }); } if value.block_id_flag == BlockIdFlag::Nil.to_i32().unwrap() { if value.signature.is_empty() { - return Err(Kind::InvalidSignature - .context("nil commitsig has no signature") - .into()); + return Err(error::invalid_signature_error( + "nil commitsig has no signature".to_string(), + )); } if value.validator_address.is_empty() { - return Err(Kind::InvalidValidatorAddress.into()); + return Err(error::invalid_validator_address_error()); } return Ok(CommitSig::BlockIdFlagNil { validator_address: value.validator_address.try_into()?, - timestamp: value.timestamp.ok_or(Kind::NoTimestamp)?.try_into()?, + timestamp: value + .timestamp + .ok_or_else(error::missing_timestamp_error)? + .into(), signature: value.signature.try_into()?, }); } - Err(Kind::BlockIdFlag.into()) + Err(error::block_id_flag_error()) } } diff --git a/tendermint/src/block/header.rs b/tendermint/src/block/header.rs index caeef58ef..522acd38b 100644 --- a/tendermint/src/block/header.rs +++ b/tendermint/src/block/header.rs @@ -1,7 +1,7 @@ //! Block headers use crate::merkle::simple_hash_from_byte_vectors; -use crate::{account, block, chain, AppHash, Error, Hash, Kind, Time}; +use crate::{account, block, chain, error, AppHash, Error, Hash, Time}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::Header as RawHeader; @@ -89,9 +89,7 @@ impl TryFrom for Header { // height").into()); //} if last_block_id.is_some() && height.value() == 1 { - return Err(Kind::InvalidFirstHeader - .context("last_block_id is not null on first height") - .into()); + return Err(error::invalid_first_header_error()); } //if last_commit_hash.is_none() && height.value() != 1 { // return Err(Kind::InvalidHeader.context("last_commit_hash is null on non-first @@ -111,10 +109,16 @@ impl TryFrom for Header { // height").into()); //} Ok(Header { - version: value.version.ok_or(Kind::MissingVersion)?.try_into()?, + version: value + .version + .ok_or_else(error::missing_version_error)? + .into(), chain_id: value.chain_id.try_into()?, height, - time: value.time.ok_or(Kind::NoTimestamp)?.try_into()?, + time: value + .time + .ok_or_else(error::missing_timestamp_error)? + .into(), last_block_id, last_commit_hash, data_hash: if value.data_hash.is_empty() { @@ -208,14 +212,12 @@ pub struct Version { impl Protobuf for Version {} -impl TryFrom for Version { - type Error = anomaly::BoxError; - - fn try_from(value: RawConsensusVersion) -> Result { - Ok(Version { +impl From for Version { + fn from(value: RawConsensusVersion) -> Self { + Version { block: value.block, app: value.app, - }) + } } } diff --git a/tendermint/src/block/height.rs b/tendermint/src/block/height.rs index 5e2ab6eee..c1a60b765 100644 --- a/tendermint/src/block/height.rs +++ b/tendermint/src/block/height.rs @@ -1,4 +1,4 @@ -use crate::error::{Error, Kind}; +use crate::error::{self, Error}; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryInto; use std::{ @@ -21,7 +21,9 @@ impl TryFrom for Height { type Error = Error; fn try_from(value: i64) -> Result { - Ok(Height(value.try_into().map_err(|_| Kind::NegativeHeight)?)) + Ok(Height( + value.try_into().map_err(error::negative_height_error)?, + )) } } @@ -35,9 +37,9 @@ impl TryFrom for Height { type Error = Error; fn try_from(value: u64) -> Result { - if value > i64::MAX as u64 { - return Err(Kind::IntegerOverflow.into()); - } + // Make sure the u64 value can be converted safely to i64 + let _ival: i64 = value.try_into().map_err(error::integer_overflow_error)?; + Ok(Height(value)) } } @@ -102,7 +104,7 @@ impl FromStr for Height { fn from_str(s: &str) -> Result { Height::try_from( s.parse::() - .map_err(|_| Kind::Parse.context("height decode"))?, + .map_err(|e| error::parse_int_error(s.to_string(), e))?, ) } } diff --git a/tendermint/src/block/id.rs b/tendermint/src/block/id.rs index a7458ace7..a2fb7bf88 100644 --- a/tendermint/src/block/id.rs +++ b/tendermint/src/block/id.rs @@ -1,6 +1,6 @@ use crate::{ block::parts::Header as PartSetHeader, - error::{Error, Kind}, + error::{self, Error}, hash::{Algorithm, Hash}, }; use serde::{Deserialize, Serialize}; @@ -64,9 +64,9 @@ impl TryFrom for Id { fn try_from(value: RawBlockId) -> Result { if value.part_set_header.is_none() { - return Err(Kind::InvalidPartSetHeader - .context("part_set_header is None") - .into()); + return Err(error::invalid_part_set_header_error( + "part_set_header is None".to_string(), + )); } Ok(Self { hash: value.hash.try_into()?, @@ -103,9 +103,9 @@ impl TryFrom for Id { fn try_from(value: RawCanonicalBlockId) -> Result { if value.part_set_header.is_none() { - return Err(Kind::InvalidPartSetHeader - .context("part_set_header is None") - .into()); + return Err(error::invalid_part_set_header_error( + "part_set_header is None".to_string(), + )); } Ok(Self { hash: value.hash.try_into()?, diff --git a/tendermint/src/block/meta.rs b/tendermint/src/block/meta.rs index f12532ec2..826306dfe 100644 --- a/tendermint/src/block/meta.rs +++ b/tendermint/src/block/meta.rs @@ -1,7 +1,7 @@ //! Block metadata use super::{Header, Id}; -use crate::{Error, Kind}; +use crate::error::{self, Error}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::BlockMeta as RawMeta; @@ -30,12 +30,12 @@ impl TryFrom for Meta { Ok(Meta { block_id: value .block_id - .ok_or_else(|| Error::from(Kind::InvalidBlock.context("no block_id")))? + .ok_or_else(|| error::invalid_block_error("no block_id".to_string()))? .try_into()?, block_size: value.block_size, header: value .header - .ok_or_else(|| Error::from(Kind::InvalidBlock.context("no header")))? + .ok_or_else(|| error::invalid_block_error("no header".to_string()))? .try_into()?, num_txs: value.num_txs, }) diff --git a/tendermint/src/block/parts.rs b/tendermint/src/block/parts.rs index 9aa0a7788..961650586 100644 --- a/tendermint/src/block/parts.rs +++ b/tendermint/src/block/parts.rs @@ -1,9 +1,9 @@ //! Block parts +use crate::error::{self, Error}; use crate::hash::Algorithm; use crate::hash::SHA256_HASH_SIZE; use crate::Hash; -use crate::{Error, Kind}; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use tendermint_proto::types::{ @@ -32,7 +32,7 @@ impl TryFrom for Header { fn try_from(value: RawPartSetHeader) -> Result { if !value.hash.is_empty() && value.hash.len() != SHA256_HASH_SIZE { - return Err(Kind::InvalidHashSize.into()); + return Err(error::invalid_hash_size_error()); } Ok(Self { total: value.total, @@ -55,7 +55,7 @@ impl TryFrom for Header { fn try_from(value: RawCanonicalPartSetHeader) -> Result { if !value.hash.is_empty() && value.hash.len() != SHA256_HASH_SIZE { - return Err(Kind::InvalidHashSize.into()); + return Err(error::invalid_hash_size_error()); } Ok(Self { total: value.total, @@ -77,14 +77,14 @@ impl Header { /// constructor pub fn new(total: u32, hash: Hash) -> Result { if total == 0 && hash != Hash::None { - return Err(Kind::InvalidPartSetHeader - .context("zero total with existing hash") - .into()); + return Err(error::invalid_part_set_header_error( + "zero total with existing hash".to_string(), + )); } if total != 0 && hash == Hash::None { - return Err(Kind::InvalidPartSetHeader - .context("non-zero total with empty hash") - .into()); + return Err(error::invalid_part_set_header_error( + "non-zero total with empty hash".to_string(), + )); } Ok(Header { total, hash }) } diff --git a/tendermint/src/block/round.rs b/tendermint/src/block/round.rs index 221bb5dd6..b0e3fecf0 100644 --- a/tendermint/src/block/round.rs +++ b/tendermint/src/block/round.rs @@ -1,4 +1,4 @@ -use crate::error::{Error, Kind}; +use crate::error::{self, Error}; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryInto; use std::{ @@ -15,7 +15,9 @@ impl TryFrom for Round { type Error = Error; fn try_from(value: i32) -> Result { - Ok(Round(value.try_into().map_err(|_| Kind::NegativeRound)?)) + Ok(Round( + value.try_into().map_err(error::negative_round_error)?, + )) } } @@ -29,9 +31,8 @@ impl TryFrom for Round { type Error = Error; fn try_from(value: u32) -> Result { - if value > i32::MAX as u32 { - return Err(Kind::IntegerOverflow.into()); - } + let _val: i32 = value.try_into().map_err(error::integer_overflow_error)?; + Ok(Round(value)) } } @@ -90,7 +91,7 @@ impl FromStr for Round { fn from_str(s: &str) -> Result { Round::try_from( s.parse::() - .map_err(|_| Kind::Parse.context("round decode"))?, + .map_err(|e| error::parse_int_error(s.to_string(), e))?, ) } } diff --git a/tendermint/src/block/signed_header.rs b/tendermint/src/block/signed_header.rs index c89f00dec..044d3f51b 100644 --- a/tendermint/src/block/signed_header.rs +++ b/tendermint/src/block/signed_header.rs @@ -1,7 +1,7 @@ //! SignedHeader contains commit and and block header. //! It is what the rpc endpoint /commit returns and hence can be used by a //! light client. -use crate::{block, Error, Kind}; +use crate::{block, error, Error}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::SignedHeader as RawSignedHeader; @@ -21,8 +21,14 @@ impl TryFrom for SignedHeader { type Error = Error; fn try_from(value: RawSignedHeader) -> Result { - let header = value.header.ok_or(Kind::InvalidSignedHeader)?.try_into()?; - let commit = value.commit.ok_or(Kind::InvalidSignedHeader)?.try_into()?; + let header = value + .header + .ok_or_else(error::invalid_signed_header_error)? + .try_into()?; + let commit = value + .commit + .ok_or_else(error::invalid_signed_header_error)? + .try_into()?; Self::new(header, commit) // Additional checks } } @@ -40,7 +46,7 @@ impl SignedHeader { /// Constructor. pub fn new(header: block::Header, commit: block::Commit) -> Result { if header.height != commit.height { - return Err(Kind::InvalidSignedHeader.into()); + return Err(error::invalid_signed_header_error()); } Ok(Self { header, commit }) } diff --git a/tendermint/src/block/size.rs b/tendermint/src/block/size.rs index d20140c22..cb9bf4e81 100644 --- a/tendermint/src/block/size.rs +++ b/tendermint/src/block/size.rs @@ -1,6 +1,6 @@ //! Block size parameters -use crate::{Error, Kind}; +use crate::error::{self, Error}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::Protobuf; use { @@ -35,7 +35,7 @@ impl TryFrom for Size { max_bytes: value .max_bytes .try_into() - .map_err(|_| Self::Error::from(Kind::IntegerOverflow))?, + .map_err(error::integer_overflow_error)?, max_gas: value.max_gas, time_iota_ms: 1000, }) diff --git a/tendermint/src/chain/id.rs b/tendermint/src/chain/id.rs index d770fe089..246b0d84d 100644 --- a/tendermint/src/chain/id.rs +++ b/tendermint/src/chain/id.rs @@ -1,6 +1,6 @@ //! Tendermint blockchain identifiers -use crate::error::{Error, Kind}; +use crate::error::{self, Error}; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryFrom; use std::{ @@ -27,13 +27,13 @@ impl TryFrom for Id { fn try_from(value: String) -> Result { if value.is_empty() || value.len() > MAX_LENGTH { - return Err(Kind::Length.into()); + return Err(error::length_error()); } for byte in value.as_bytes() { match byte { b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'.' => (), - _ => return Err(Kind::Parse.context("chain id charset").into()), + _ => return Err(error::parse_error("chain id charset".to_string())), } } @@ -151,18 +151,18 @@ mod tests { #[test] fn rejects_empty_chain_ids() { - assert_eq!( - *"".parse::().unwrap_err().to_string(), - Kind::Length.to_string() - ); + match "".parse::().unwrap_err().detail { + error::ErrorDetail::Length(_) => {} + _ => panic!("expected length error"), + } } #[test] fn rejects_overlength_chain_ids() { let overlong_id = String::from_utf8(vec![b'x'; MAX_LENGTH + 1]).unwrap(); - assert_eq!( - *overlong_id.parse::().unwrap_err().to_string(), - Kind::Length.to_string() - ); + match overlong_id.parse::().unwrap_err().detail { + error::ErrorDetail::Length(_) => {} + _ => panic!("expected length error"), + } } } diff --git a/tendermint/src/config.rs b/tendermint/src/config.rs index 9b1682661..6a533ff0e 100644 --- a/tendermint/src/config.rs +++ b/tendermint/src/config.rs @@ -12,11 +12,10 @@ mod priv_validator_key; pub use self::{node_key::NodeKey, priv_validator_key::PrivValidatorKey}; use crate::{ - error::{Error, Kind}, + error::{self, Error}, genesis::Genesis, net, node, Moniker, Timeout, }; -use anomaly::{fail, format_err}; use serde::{de, de::Error as _, ser, Deserialize, Serialize}; use std::{ collections::BTreeMap, @@ -109,7 +108,9 @@ pub struct TendermintConfig { impl TendermintConfig { /// Parse Tendermint `config.toml` pub fn parse_toml>(toml_string: T) -> Result { - Ok(toml::from_str(toml_string.as_ref())?) + let res = toml::from_str(toml_string.as_ref()).map_err(error::toml_error)?; + + Ok(res) } /// Load `config.toml` from a file @@ -117,14 +118,8 @@ impl TendermintConfig { where P: AsRef, { - let toml_string = fs::read_to_string(path).map_err(|e| { - format_err!( - Kind::Parse, - "couldn't open {}: {}", - path.as_ref().display(), - e - ) - })?; + let toml_string = fs::read_to_string(path) + .map_err(|e| error::file_io_error(format!("{}", path.as_ref().display()), e))?; Self::parse_toml(toml_string) } @@ -133,9 +128,11 @@ impl TendermintConfig { pub fn load_genesis_file(&self, home: impl AsRef) -> Result { let path = home.as_ref().join(&self.genesis_file); let genesis_json = fs::read_to_string(&path) - .map_err(|e| format_err!(Kind::Parse, "couldn't open {}: {}", path.display(), e))?; + .map_err(|e| error::file_io_error(format!("{}", path.display()), e))?; + + let res = serde_json::from_str(genesis_json.as_ref()).map_err(error::serde_json_error)?; - Ok(serde_json::from_str(genesis_json.as_ref())?) + Ok(res) } /// Load `node_key.json` file from the configured location @@ -212,14 +209,20 @@ impl FromStr for LogLevel { global = Some(parts[0].to_owned()); continue; } else if parts.len() != 2 { - fail!(Kind::Parse, "error parsing log level: {}", level); + return Err(error::parse_error(format!( + "error parsing log level: {}", + level + ))); } let key = parts[0].to_owned(); let value = parts[1].to_owned(); if components.insert(key, value).is_some() { - fail!(Kind::Parse, "duplicate log level setting for: {}", level); + return Err(error::parse_error(format!( + "duplicate log level setting for: {}", + level + ))); } } @@ -455,7 +458,8 @@ pub struct P2PConfig { /// Maximum number of outbound peers to connect to, excluding persistent peers pub max_num_outbound_peers: u64, - /// List of node IDs, to which a connection will be (re)established ignoring any existing limits + /// List of node IDs, to which a connection will be (re)established ignoring any existing + /// limits #[serde( serialize_with = "serialize_comma_separated_list", deserialize_with = "deserialize_comma_separated_list" @@ -573,10 +577,11 @@ pub struct ConsensusConfig { /// Commit timeout pub timeout_commit: Timeout, - /// How many blocks to look back to check existence of the node's consensus votes before joining consensus - /// When non-zero, the node will panic upon restart + /// How many blocks to look back to check existence of the node's consensus votes before + /// joining consensus When non-zero, the node will panic upon restart /// if the same consensus key was used to sign {double-sign-check-height} last blocks. - /// So, validators should stop the state machine, wait for some blocks, and then restart the state machine to avoid panic. + /// So, validators should stop the state machine, wait for some blocks, and then restart the + /// state machine to avoid panic. pub double_sign_check_height: u64, /// Make progress as soon as we have all the precommits (as if TimeoutCommit = 0) @@ -644,19 +649,20 @@ pub struct InstrumentationConfig { /// statesync configuration options #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct StatesyncConfig { - /// State sync rapidly bootstraps a new node by discovering, fetching, and restoring a state machine - /// snapshot from peers instead of fetching and replaying historical blocks. Requires some peers in - /// the network to take and serve state machine snapshots. State sync is not attempted if the node - /// has any local state (LastBlockHeight > 0). The node will have a truncated block history, - /// starting from the height of the snapshot. + /// State sync rapidly bootstraps a new node by discovering, fetching, and restoring a state + /// machine snapshot from peers instead of fetching and replaying historical blocks. + /// Requires some peers in the network to take and serve state machine snapshots. State + /// sync is not attempted if the node has any local state (LastBlockHeight > 0). The node + /// will have a truncated block history, starting from the height of the snapshot. pub enable: bool, /// RPC servers (comma-separated) for light client verification of the synced state machine and - /// retrieval of state data for node bootstrapping. Also needs a trusted height and corresponding - /// header hash obtained from a trusted source, and a period during which validators can be trusted. + /// retrieval of state data for node bootstrapping. Also needs a trusted height and + /// corresponding header hash obtained from a trusted source, and a period during which + /// validators can be trusted. /// - /// For Cosmos SDK-based chains, trust-period should usually be about 2/3 of the unbonding time (~2 - /// weeks) during which they can be financially punished (slashed) for misbehavior. + /// For Cosmos SDK-based chains, trust-period should usually be about 2/3 of the unbonding time + /// (~2 weeks) during which they can be financially punished (slashed) for misbehavior. #[serde( serialize_with = "serialize_comma_separated_list", deserialize_with = "deserialize_comma_separated_list" @@ -675,8 +681,8 @@ pub struct StatesyncConfig { /// Time to spend discovering snapshots before initiating a restore. pub discovery_time: Timeout, - /// Temporary directory for state sync snapshot chunks, defaults to the OS tempdir (typically /tmp). - /// Will create a new, randomly named directory within, and remove it when done. + /// Temporary directory for state sync snapshot chunks, defaults to the OS tempdir (typically + /// /tmp). Will create a new, randomly named directory within, and remove it when done. pub temp_dir: String, } diff --git a/tendermint/src/config/node_key.rs b/tendermint/src/config/node_key.rs index ffd0e1d42..37dcf8187 100644 --- a/tendermint/src/config/node_key.rs +++ b/tendermint/src/config/node_key.rs @@ -1,12 +1,11 @@ //! Node keys use crate::{ - error::{Error, Kind}, + error::{self, Error}, node, private_key::PrivateKey, public_key::PublicKey, }; -use anomaly::format_err; use serde::{Deserialize, Serialize}; use std::{fs, path::Path}; @@ -20,7 +19,8 @@ pub struct NodeKey { impl NodeKey { /// Parse `node_key.json` pub fn parse_json>(json_string: T) -> Result { - Ok(serde_json::from_str(json_string.as_ref())?) + let res = serde_json::from_str(json_string.as_ref()).map_err(error::serde_json_error)?; + Ok(res) } /// Load `node_key.json` from a file @@ -28,14 +28,8 @@ impl NodeKey { where P: AsRef, { - let json_string = fs::read_to_string(path).map_err(|e| { - format_err!( - Kind::Parse, - "couldn't open {}: {}", - path.as_ref().display(), - e - ) - })?; + let json_string = fs::read_to_string(path) + .map_err(|e| error::file_io_error(format!("{}", path.as_ref().display()), e))?; Self::parse_json(json_string) } diff --git a/tendermint/src/config/priv_validator_key.rs b/tendermint/src/config/priv_validator_key.rs index 784db0039..ae5566cea 100644 --- a/tendermint/src/config/priv_validator_key.rs +++ b/tendermint/src/config/priv_validator_key.rs @@ -3,11 +3,10 @@ use crate::public_key::TendermintKey; use crate::{ account, - error::{Error, Kind}, + error::{self, Error}, private_key::PrivateKey, public_key::PublicKey, }; -use anomaly::format_err; use serde::{Deserialize, Serialize}; use std::{fs, path::Path}; @@ -27,7 +26,8 @@ pub struct PrivValidatorKey { impl PrivValidatorKey { /// Parse `priv_validator_key.json` pub fn parse_json>(json_string: T) -> Result { - let result = serde_json::from_str::(json_string.as_ref())?; + let result = + serde_json::from_str::(json_string.as_ref()).map_err(error::serde_json_error)?; // Validate that the parsed key type is usable as a consensus key TendermintKey::new_consensus_key(result.priv_key.public_key())?; @@ -40,14 +40,8 @@ impl PrivValidatorKey { where P: AsRef, { - let json_string = fs::read_to_string(path).map_err(|e| { - format_err!( - Kind::Parse, - "couldn't open {}: {}", - path.as_ref().display(), - e - ) - })?; + let json_string = fs::read_to_string(path) + .map_err(|e| error::file_io_error(format!("{}", path.as_ref().display()), e))?; Self::parse_json(json_string) } diff --git a/tendermint/src/consensus/params.rs b/tendermint/src/consensus/params.rs index 47f3e5bd7..0e6291d38 100644 --- a/tendermint/src/consensus/params.rs +++ b/tendermint/src/consensus/params.rs @@ -1,7 +1,7 @@ //! Tendermint consensus parameters +use crate::error::{self, Error}; use crate::{block, evidence, public_key}; -use crate::{Error, Kind}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::abci::ConsensusParams as RawParams; @@ -33,17 +33,19 @@ impl TryFrom for Params { fn try_from(value: RawParams) -> Result { Ok(Self { - block: value.block.ok_or(Kind::InvalidBlock)?.try_into()?, - evidence: value.evidence.ok_or(Kind::InvalidEvidence)?.try_into()?, + block: value + .block + .ok_or_else(|| error::invalid_block_error("missing block".to_string()))? + .try_into()?, + evidence: value + .evidence + .ok_or_else(error::invalid_evidence_error)? + .try_into()?, validator: value .validator - .ok_or(Kind::InvalidValidatorParams)? + .ok_or_else(error::invalid_validator_params_error)? .try_into()?, - version: value - .version - .map(TryFrom::try_from) - .transpose() - .map_err(|_| Kind::InvalidVersionParams)?, + version: value.version.map(TryFrom::try_from).transpose()?, }) } } diff --git a/tendermint/src/error.rs b/tendermint/src/error.rs index 0df48310d..4e7678198 100644 --- a/tendermint/src/error.rs +++ b/tendermint/src/error.rs @@ -1,210 +1,198 @@ //! Error types -use anomaly::{BoxError, Context}; -use thiserror::Error; - use crate::account; use crate::vote; +use alloc::string::String; +use core::num::TryFromIntError; +use flex_error::{define_error, DisplayError}; +use std::io::Error as IoError; +use time::OutOfRangeError; -/// Error type -pub type Error = BoxError; +define_error! { + #[derive(Debug)] + Error { + Crypto + |_| { format_args!("cryptographic error") }, -/// Kinds of errors -#[derive(Clone, Eq, PartialEq, Debug, Error)] -pub enum Kind { - /// Cryptographic operation failed - #[error("cryptographic error")] - Crypto, + InvalidKey + { detail: String } + |e| { format_args!("invalid key: {}", e) }, - /// Malformatted or otherwise invalid cryptographic key - #[error("invalid key")] - InvalidKey, + Io + [ DisplayError ] + |_| { format_args!("I/O error") }, - /// Unsupported public key type. - #[error("unsupported key type")] - UnsupportedKeyType, + FileIo + { path: String } + [ DisplayError ] + |e| { format_args!("failed to open file: {}", e.path) }, - /// Input/output error - #[error("I/O error")] - Io, + Length + |_| { format_args!("length error") }, - /// Length incorrect or too long - #[error("length error")] - Length, + Parse + { data: String } + | e | { format_args!("error parsing data: {}", e.data) }, - /// Parse error - #[error("parse error")] - Parse, + ParseInt + { data: String } + [ DisplayError] + | e | { format_args!("error parsing int data: {}", e.data) }, - /// Network protocol-related errors - #[error("protocol error")] - Protocol, + ParseUrl + [ DisplayError ] + |_| { format_args!("error parsing url error") }, - /// Value out-of-range - #[error("value out of range")] - OutOfRange, + Protocol + { detail: String } + |_| { format_args!("protocol error") }, - /// Signature invalid - #[error("bad signature")] - SignatureInvalid, + OutOfRange + [ DisplayError ] + |_| { format_args!("value out of range") }, - /// invalid message type - #[error("invalid message type")] - InvalidMessageType, + SignatureInvalid + { detail: String } + |_| { format_args!("bad signature") }, - /// Negative block height - #[error("negative height")] - NegativeHeight, + InvalidMessageType + |_| { format_args!("invalid message type") }, - /// Negative voting round - #[error("negative round")] - NegativeRound, + NegativeHeight + [ DisplayError ] + |_| { format_args!("negative height") }, - /// Negative POL round - #[error("negative POL round")] - NegativePolRound, + NegativeRound + [ DisplayError ] + |_| { format_args!("negative round") }, - /// Negative validator index in vote - #[error("negative validator index")] - NegativeValidatorIndex, + NegativePolRound + |_| { format_args!("negative POL round") }, - /// Invalid hash size in part_set_header - #[error("invalid hash: expected hash size to be 32 bytes")] - InvalidHashSize, + NegativeValidatorIndex + [ DisplayError ] + |_| { format_args!("negative validator index") }, - /// No timestamp in vote or block header - #[error("no timestamp")] - NoTimestamp, - - /// Invalid timestamp - #[error("invalid timestamp")] - InvalidTimestamp, - - /// Invalid account ID length - #[error("invalid account ID length")] - InvalidAccountIdLength, - - /// Invalid signature ID length - #[error("invalid signature ID length")] - InvalidSignatureIdLength, - - /// Overflow during conversion - #[error("integer overflow")] - IntegerOverflow, - - /// No Vote found during conversion - #[error("no vote found")] - NoVoteFound, - - /// No Proposal found during conversion - #[error("no proposal found")] - NoProposalFound, - - /// Invalid AppHash length found during conversion - #[error("invalid app hash Length")] - InvalidAppHashLength, - - /// Invalid PartSetHeader - #[error("invalid part set header")] - InvalidPartSetHeader, - - /// Missing Header in Block - #[error("missing header field")] - MissingHeader, - - /// Missing Data in Block - #[error("missing data field")] - MissingData, - - /// Missing Evidence in Block - #[error("missing evidence field")] - MissingEvidence, - - /// Missing Timestamp in Block - #[error("missing timestamp field")] - MissingTimestamp, - - /// Invalid Block - #[error("invalid block")] - InvalidBlock, - - /// Invalid first Block - #[error("invalid first block")] - InvalidFirstBlock, - - /// Missing Version field - #[error("missing version")] - MissingVersion, - - /// Invalid Header - #[error("invalid header")] - InvalidHeader, - - /// Invalid first Header - #[error("invalid first header")] - InvalidFirstHeader, - - /// Invalid signature in CommitSig - #[error("invalid signature")] - InvalidSignature, - - /// Invalid validator address in CommitSig - #[error("invalid validator address")] - InvalidValidatorAddress, - - /// Invalid Signed Header - #[error("invalid signed header")] - InvalidSignedHeader, - - /// Invalid Evidence - #[error("invalid evidence")] - InvalidEvidence, - - /// Invalid BlockIdFlag - #[error("invalid block id flag")] - BlockIdFlag, - - /// Negative voting power - #[error("negative power")] - NegativePower, - - /// Mismatch between raw voting power and computed one in validator set - #[error("mismatch between raw voting power ({raw}) and computed one ({computed})")] - RawVotingPowerMismatch { - /// raw voting power - raw: vote::Power, - /// computed voting power - computed: vote::Power, - }, - - /// Missing Public Key - #[error("missing public key")] - MissingPublicKey, - - /// Invalid validator parameters - #[error("invalid validator parameters")] - InvalidValidatorParams, - - /// Invalid version parameters - #[error("invalid version parameters")] - InvalidVersionParams, - - /// Negative max_age_num_blocks in Evidence parameters - #[error("negative max_age_num_blocks")] - NegativeMaxAgeNum, - - /// Missing max_age_duration in evidence parameters - #[error("missing max_age_duration")] - MissingMaxAgeDuration, - - /// Proposer not found in validator set - #[error("proposer with address '{}' not found in validator set", _0)] - ProposerNotFound(account::Id), -} + InvalidHashSize + |_| { format_args!("invalid hash: expected hash size to be 32 bytes") }, + + NonZeroTimestamp + | _ | { "absent commitsig has non-zero timestamp" }, + + InvalidAccountIdLength + |_| { format_args!("invalid account ID length") }, + + InvalidSignatureIdLength + |_| { format_args!("invalid signature ID length") }, + + IntegerOverflow + [ DisplayError ] + |_| { format_args!("integer overflow") }, + + NoVoteFound + |_| { format_args!("no vote found") }, + + NoProposalFound + |_| { format_args!("no proposal found") }, + + InvalidAppHashLength + |_| { format_args!("invalid app hash length") }, + + InvalidPartSetHeader + { detail : String } + |_| { format_args!("invalid part set header") }, + + MissingHeader + |_| { format_args!("missing header field") }, + + MissingData + |_| { format_args!("missing data field") }, + + MissingEvidence + |_| { format_args!("missing evidence field") }, + + MissingTimestamp + |_| { format_args!("missing timestamp field") }, + + InvalidTimestamp + { reason: String } + | e | { format_args!("invalid timestamp: {}", e.reason) }, + + InvalidBlock + { reason: String } + | e | { format_args!("invalid block: {}", e.reason) }, + + MissingVersion + |_| { format_args!("missing version") }, + + InvalidFirstHeader + |_| { format_args!("last_block_id is not null on first height") }, + + InvalidSignature + { reason: String } + | e | { format_args!("invalid signature: {}", e.reason) }, + + InvalidValidatorAddress + |_| { format_args!("invalid validator address") }, + + InvalidSignedHeader + |_| { format_args!("invalid signed header") }, + + InvalidEvidence + |_| { format_args!("invalid evidence") }, + + BlockIdFlag + |_| { format_args!("invalid block id flag") }, + + NegativePower + [ DisplayError ] + |_| { format_args!("negative power") }, + + UnsupportedKeyType + |_| { format_args!("unsupported key type" ) }, + + RawVotingPowerMismatch + { raw: vote::Power, computed: vote::Power } + |e| { format_args!("mismatch between raw voting ({0:?}) and computed one ({1:?})", e.raw, e.computed) }, + + MissingPublicKey + |_| { format_args!("missing public key") }, + + InvalidValidatorParams + |_| { format_args!("invalid validator parameters") }, + + InvalidVersionParams + |_| { format_args!("invalid version parameters") }, + + NegativeMaxAgeNum + [ DisplayError ] + |_| { format_args!("negative max_age_num_blocks") }, + + MissingMaxAgeDuration + |_| { format_args!("missing max_age_duration") }, + + ProposerNotFound + { account: account::Id } + |e| { format_args!("proposer with address '{0}' no found in validator set", e.account) }, + + ChronoParse + [ DisplayError ] + |_| { format_args!("chrono parse error") }, + + SubtleEncoding + [ DisplayError ] + |_| { format_args!("subtle encoding error") }, + + SerdeJson + [ DisplayError ] + |_| { format_args!("serde json error") }, + + Toml + [ DisplayError ] + |_| { format_args!("toml de error") }, -impl Kind { - /// Add additional context. - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) + Signature + [ DisplayError ] + |_| { format_args!("signature error") }, } } diff --git a/tendermint/src/evidence.rs b/tendermint/src/evidence.rs index 280199a07..e4968007c 100644 --- a/tendermint/src/evidence.rs +++ b/tendermint/src/evidence.rs @@ -1,7 +1,7 @@ //! Evidence of malfeasance by validators (i.e. signing conflicting votes). use crate::{ - block::signed_header::SignedHeader, serializers, vote::Power, Error, Kind, Time, Vote, + block::signed_header::SignedHeader, error, error::Error, serializers, vote::Power, Time, Vote, }; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; @@ -40,7 +40,7 @@ impl TryFrom for Evidence { type Error = Error; fn try_from(value: RawEvidence) -> Result { - match value.sum.ok_or(Kind::InvalidEvidence)? { + match value.sum.ok_or_else(error::invalid_evidence_error)? { Sum::DuplicateVoteEvidence(ev) => Ok(Evidence::DuplicateVote(ev.try_into()?)), Sum::LightClientAttackEvidence(_ev) => Ok(Evidence::LightClientAttackEvidence), } @@ -74,11 +74,20 @@ impl TryFrom for DuplicateVoteEvidence { fn try_from(value: RawDuplicateVoteEvidence) -> Result { Ok(Self { - vote_a: value.vote_a.ok_or(Kind::MissingEvidence)?.try_into()?, - vote_b: value.vote_b.ok_or(Kind::MissingEvidence)?.try_into()?, + vote_a: value + .vote_a + .ok_or_else(error::missing_evidence_error)? + .try_into()?, + vote_b: value + .vote_b + .ok_or_else(error::missing_evidence_error)? + .try_into()?, total_voting_power: value.total_voting_power.try_into()?, validator_power: value.validator_power.try_into()?, - timestamp: value.timestamp.ok_or(Kind::MissingTimestamp)?.try_into()?, + timestamp: value + .timestamp + .ok_or_else(error::missing_timestamp_error)? + .into(), }) } } @@ -99,7 +108,7 @@ impl DuplicateVoteEvidence { /// constructor pub fn new(vote_a: Vote, vote_b: Vote) -> Result { if vote_a.height != vote_b.height { - return Err(Kind::InvalidEvidence.into()); + return Err(error::invalid_evidence_error()); } // Todo: make more assumptions about what is considered a valid evidence for duplicate vote Ok(Self { @@ -224,10 +233,10 @@ impl TryFrom for Params { max_age_num_blocks: value .max_age_num_blocks .try_into() - .map_err(|_| Self::Error::from(Kind::NegativeMaxAgeNum))?, + .map_err(error::negative_max_age_num_error)?, max_age_duration: value .max_age_duration - .ok_or(Kind::MissingMaxAgeDuration)? + .ok_or_else(error::missing_max_age_duration_error)? .try_into()?, max_bytes: value.max_bytes, }) @@ -269,11 +278,11 @@ impl TryFrom for Duration { value .seconds .try_into() - .map_err(|_| Self::Error::from(Kind::IntegerOverflow))?, + .map_err(error::integer_overflow_error)?, value .nanos .try_into() - .map_err(|_| Self::Error::from(Kind::IntegerOverflow))?, + .map_err(error::integer_overflow_error)?, ))) } } diff --git a/tendermint/src/hash.rs b/tendermint/src/hash.rs index d74e3a056..3d0713c41 100644 --- a/tendermint/src/hash.rs +++ b/tendermint/src/hash.rs @@ -1,6 +1,6 @@ //! Hash functions and their outputs -use crate::error::{Error, Kind}; +use crate::error::{self, Error}; use serde::de::Error as _; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryFrom; @@ -66,9 +66,7 @@ impl Hash { h.copy_from_slice(bytes); Ok(Hash::Sha256(h)) } else { - Err(Kind::Parse - .context(format!("hash invalid length: {}", bytes.len())) - .into()) + Err(error::invalid_hash_size_error()) } } } @@ -82,7 +80,9 @@ impl Hash { match alg { Algorithm::Sha256 => { let mut h = [0u8; SHA256_HASH_SIZE]; - Hex::upper_case().decode_to_slice(s.as_bytes(), &mut h)?; + Hex::upper_case() + .decode_to_slice(s.as_bytes(), &mut h) + .map_err(error::subtle_encoding_error)?; Ok(Hash::Sha256(h)) } } @@ -214,10 +214,12 @@ impl AppHash { /// Decode a `Hash` from upper-case hexadecimal pub fn from_hex_upper(s: &str) -> Result { if s.len() % 2 != 0 { - return Err(Kind::InvalidAppHashLength.into()); + return Err(error::invalid_app_hash_length_error()); } let mut h = vec![0; s.len() / 2]; - Hex::upper_case().decode_to_slice(s.as_bytes(), &mut h)?; + Hex::upper_case() + .decode_to_slice(s.as_bytes(), &mut h) + .map_err(error::subtle_encoding_error)?; Ok(AppHash(h)) } } diff --git a/tendermint/src/lib.rs b/tendermint/src/lib.rs index 25f087097..ea6c3e724 100644 --- a/tendermint/src/lib.rs +++ b/tendermint/src/lib.rs @@ -7,7 +7,6 @@ #![cfg_attr(docsrs, feature(doc_cfg))] #![deny( warnings, - missing_docs, trivial_casts, trivial_numeric_casts, unused_import_braces, @@ -19,6 +18,8 @@ html_logo_url = "https://raw.githubusercontent.com/informalsystems/tendermint-rs/master/img/logo-tendermint-rs_3961x4001.png" )] +extern crate alloc; + #[macro_use] pub mod error; @@ -53,7 +54,7 @@ mod test; pub use crate::{ block::Block, - error::{Error, Kind}, + error::Error, genesis::Genesis, hash::AppHash, hash::Hash, diff --git a/tendermint/src/net.rs b/tendermint/src/net.rs index 3e48f2322..4ec0a72be 100644 --- a/tendermint/src/net.rs +++ b/tendermint/src/net.rs @@ -1,7 +1,7 @@ //! Remote addresses (`tcp://` or `unix://`) use crate::{ - error::{Error, Kind}, + error::{self, Error}, node, }; @@ -82,7 +82,7 @@ impl FromStr for Address { // If the address has no scheme, assume it's TCP format!("{}{}", TCP_PREFIX, addr) }; - let url = Url::parse(&prefixed_addr).map_err(|e| Kind::Parse.context(e))?; + let url = Url::parse(&prefixed_addr).map_err(error::parse_url_error)?; match url.scheme() { "tcp" => Ok(Self::Tcp { peer_id: if !url.username().is_empty() { @@ -93,19 +93,20 @@ impl FromStr for Address { host: url .host_str() .ok_or_else(|| { - Kind::Parse.context(format!("invalid TCP address (missing host): {}", addr)) + error::parse_error(format!("invalid TCP address (missing host): {}", addr)) })? .to_owned(), port: url.port().ok_or_else(|| { - Kind::Parse.context(format!("invalid TCP address (missing port): {}", addr)) + error::parse_error(format!("invalid TCP address (missing port): {}", addr)) })?, }), "unix" => Ok(Self::Unix { path: PathBuf::from(url.path()), }), - _ => Err(Kind::Parse - .context(format!("invalid address scheme: {:?}", addr)) - .into()), + _ => Err(error::parse_error(format!( + "invalid address scheme: {:?}", + addr + ))), } } } diff --git a/tendermint/src/node/id.rs b/tendermint/src/node/id.rs index 7cedc02e9..6e6a4ffc0 100644 --- a/tendermint/src/node/id.rs +++ b/tendermint/src/node/id.rs @@ -12,7 +12,7 @@ use subtle::{self, ConstantTimeEq}; use subtle_encoding::hex; use crate::{ - error::{Error, Kind}, + error::{self, Error}, public_key::{Ed25519, PublicKey}, }; @@ -80,10 +80,10 @@ impl FromStr for Id { // Accept either upper or lower case hex let bytes = hex::decode_upper(s) .or_else(|_| hex::decode(s)) - .map_err(|_| Kind::Parse)?; + .map_err(error::subtle_encoding_error)?; if bytes.len() != LENGTH { - return Err(Kind::Parse.into()); + return Err(error::parse_error("invalid length".to_string())); } let mut result_bytes = [0u8; LENGTH]; @@ -105,7 +105,7 @@ impl TryFrom for Id { match pk { PublicKey::Ed25519(ed25519) => Ok(Id::from(ed25519)), #[cfg(feature = "secp256k1")] - _ => Err(Kind::UnsupportedKeyType.into()), + _ => Err(error::unsupported_key_type_error()), } } } diff --git a/tendermint/src/proposal.rs b/tendermint/src/proposal.rs index 7c3a84685..6f761fc38 100644 --- a/tendermint/src/proposal.rs +++ b/tendermint/src/proposal.rs @@ -11,9 +11,9 @@ pub use sign_proposal::{SignProposalRequest, SignedProposalResponse}; use crate::block::{Height, Id as BlockId, Round}; use crate::chain::Id as ChainId; use crate::consensus::State; +use crate::error::{self, Error}; use crate::Signature; use crate::Time; -use crate::{Error, Kind}; use bytes::BufMut; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::Proposal as RawProposal; @@ -45,7 +45,7 @@ impl TryFrom for Proposal { fn try_from(value: RawProposal) -> Result { if value.pol_round < -1 { - return Err(Kind::NegativePolRound.into()); + return Err(error::negative_pol_round_error()); } let pol_round = match value.pol_round { -1 => None, @@ -57,7 +57,7 @@ impl TryFrom for Proposal { round: value.round.try_into()?, pol_round, block_id: value.block_id.map(TryInto::try_into).transpose()?, - timestamp: value.timestamp.map(TryInto::try_into).transpose()?, + timestamp: value.timestamp.map(|t| t.into()), signature: value.signature.try_into()?, }) } diff --git a/tendermint/src/proposal/canonical_proposal.rs b/tendermint/src/proposal/canonical_proposal.rs index 04ad1074d..43f1d4212 100644 --- a/tendermint/src/proposal/canonical_proposal.rs +++ b/tendermint/src/proposal/canonical_proposal.rs @@ -3,8 +3,8 @@ use super::Type; use crate::block::{Height, Id as BlockId, Round}; use crate::chain::Id as ChainId; +use crate::error::{self, Error}; use crate::Time; -use crate::{Error, Kind}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::CanonicalProposal as RawCanonicalProposal; use tendermint_proto::Protobuf; @@ -35,15 +35,14 @@ impl TryFrom for CanonicalProposal { fn try_from(value: RawCanonicalProposal) -> Result { if value.pol_round < -1 { - return Err(Kind::NegativePolRound.into()); + return Err(error::negative_pol_round_error()); } - let round = Round::try_from( - i32::try_from(value.round).map_err(|e| Kind::IntegerOverflow.context(e))?, - )?; + let round = + Round::try_from(i32::try_from(value.round).map_err(error::integer_overflow_error)?)?; let pol_round = match value.pol_round { -1 => None, n => Some(Round::try_from( - i32::try_from(n).map_err(|e| Kind::IntegerOverflow.context(e))?, + i32::try_from(n).map_err(error::integer_overflow_error)?, )?), }; // If the Hash is empty in BlockId, the BlockId should be empty. @@ -55,7 +54,7 @@ impl TryFrom for CanonicalProposal { round, pol_round, block_id: block_id.map(TryInto::try_into).transpose()?, - timestamp: value.timestamp.map(TryInto::try_into).transpose()?, + timestamp: value.timestamp.map(|t| t.into()), chain_id: ChainId::try_from(value.chain_id).unwrap(), }) } diff --git a/tendermint/src/proposal/msg_type.rs b/tendermint/src/proposal/msg_type.rs index 32433dda4..df0783b26 100644 --- a/tendermint/src/proposal/msg_type.rs +++ b/tendermint/src/proposal/msg_type.rs @@ -1,4 +1,4 @@ -use crate::{Error, Kind}; +use crate::error::{self, Error}; use serde::de::Error as _; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryFrom; @@ -20,7 +20,7 @@ impl TryFrom for Type { fn try_from(value: i32) -> Result { match value { 32 => Ok(Type::Proposal), - _ => Err(Kind::InvalidMessageType.into()), + _ => Err(error::invalid_message_type_error()), } } } diff --git a/tendermint/src/proposal/sign_proposal.rs b/tendermint/src/proposal/sign_proposal.rs index ccb78398e..3dce8ec0a 100644 --- a/tendermint/src/proposal/sign_proposal.rs +++ b/tendermint/src/proposal/sign_proposal.rs @@ -1,6 +1,6 @@ use super::Proposal; use crate::chain::Id as ChainId; -use crate::{Error, Kind}; +use crate::error::{self, Error}; use bytes::BufMut; use std::convert::{TryFrom, TryInto}; use tendermint_proto::privval::RemoteSignerError; @@ -26,7 +26,7 @@ impl TryFrom for SignProposalRequest { fn try_from(value: RawSignProposalRequest) -> Result { if value.proposal.is_none() { - return Err(Kind::NoProposalFound.into()); + return Err(error::no_proposal_found_error()); } Ok(SignProposalRequest { proposal: Proposal::try_from(value.proposal.unwrap())?, diff --git a/tendermint/src/public_key.rs b/tendermint/src/public_key.rs index c675a8642..bb502b2f1 100644 --- a/tendermint/src/public_key.rs +++ b/tendermint/src/public_key.rs @@ -13,7 +13,6 @@ use crate::{ error::{self, Error}, signature::Signature, }; -use anomaly::{fail, format_err}; use serde::{de, ser, Deserialize, Serialize}; use signature::Verifier as _; use std::convert::TryFrom; @@ -65,18 +64,17 @@ impl TryFrom for PublicKey { fn try_from(value: RawPublicKey) -> Result { let sum = &value .sum - .ok_or_else(|| format_err!(error::Kind::InvalidKey, "empty sum"))?; + .ok_or_else(|| error::invalid_key_error("empty sum".to_string()))?; if let Sum::Ed25519(b) = sum { - return Self::from_raw_ed25519(b).ok_or_else(|| { - format_err!(error::Kind::InvalidKey, "malformed ed25519 key").into() - }); + return Self::from_raw_ed25519(b) + .ok_or_else(|| error::invalid_key_error("malformed ed25519 key".to_string())); } #[cfg(feature = "secp256k1")] if let Sum::Secp256k1(b) = sum { return Self::from_raw_secp256k1(b) - .ok_or_else(|| format_err!(error::Kind::InvalidKey, "malformed key").into()); + .ok_or_else(|| error::invalid_key_error("malformed key".to_string())); } - Err(format_err!(error::Kind::InvalidKey, "not an ed25519 key").into()) + Err(error::invalid_key_error("not an ed25519 key".to_string())) } } @@ -137,21 +135,18 @@ impl PublicKey { match self { PublicKey::Ed25519(pk) => match signature { Signature::Ed25519(sig) => pk.verify(msg, sig).map_err(|_| { - format_err!( - error::Kind::SignatureInvalid, - "Ed25519 signature verification failed" + error::signature_invalid_error( + "Ed25519 signature verification failed".to_string(), ) - .into() }), - Signature::None => { - Err(format_err!(error::Kind::SignatureInvalid, "missing signature").into()) - } + Signature::None => Err(error::signature_invalid_error( + "missing signature".to_string(), + )), }, #[cfg(feature = "secp256k1")] - PublicKey::Secp256k1(_) => fail!( - error::Kind::InvalidKey, - "unsupported signature algorithm (ECDSA/secp256k1)" - ), + PublicKey::Secp256k1(_) => Err(error::invalid_key_error( + "unsupported signature algorithm (ECDSA/secp256k1)".to_string(), + )), } } @@ -250,10 +245,9 @@ impl TendermintKey { #[allow(unreachable_patterns)] match public_key { PublicKey::Ed25519(_) => Ok(TendermintKey::AccountKey(public_key)), - _ => fail!( - error::Kind::InvalidKey, - "only ed25519 consensus keys are supported" - ), + _ => Err(error::invalid_key_error( + "only ed25519 consensus keys are supported".to_string(), + )), } } @@ -308,7 +302,7 @@ impl FromStr for Algorithm { match s { "ed25519" => Ok(Algorithm::Ed25519), "secp256k1" => Ok(Algorithm::Secp256k1), - _ => Err(error::Kind::Parse.into()), + _ => Err(error::parse_error(format!("invalid algorithm: {}", s))), } } } diff --git a/tendermint/src/signature.rs b/tendermint/src/signature.rs index 77f848d86..a2a6a83e2 100644 --- a/tendermint/src/signature.rs +++ b/tendermint/src/signature.rs @@ -6,7 +6,7 @@ pub use signature::{Signer, Verifier}; #[cfg(feature = "secp256k1")] pub use k256::ecdsa::Signature as Secp256k1; -use crate::{Error, Kind}; +use crate::error::{self, Error}; use std::convert::TryFrom; use tendermint_proto::Protobuf; @@ -31,7 +31,7 @@ impl TryFrom> for Signature { return Ok(Self::default()); } if value.len() != ED25519_SIGNATURE_SIZE { - return Err(Kind::InvalidSignatureIdLength.into()); + return Err(error::invalid_signature_id_length_error()); } let mut slice: [u8; ED25519_SIGNATURE_SIZE] = [0; ED25519_SIGNATURE_SIZE]; slice.copy_from_slice(&value[..]); diff --git a/tendermint/src/time.rs b/tendermint/src/time.rs index 12d204b0f..75a5866a6 100644 --- a/tendermint/src/time.rs +++ b/tendermint/src/time.rs @@ -1,11 +1,10 @@ //! Timestamps used by Tendermint blockchains -use crate::error::{Error, Kind}; +use crate::error::{self, Error}; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use std::convert::{Infallible, TryFrom}; use std::fmt; use std::ops::{Add, Sub}; use std::str::FromStr; @@ -22,10 +21,8 @@ pub struct Time(DateTime); impl Protobuf for Time {} -impl TryFrom for Time { - type Error = Infallible; - - fn try_from(value: Timestamp) -> Result { +impl From for Time { + fn from(value: Timestamp) -> Self { // prost_types::Timestamp has a SystemTime converter but // tendermint_proto::Timestamp can be JSON-encoded let prost_value = prost_types::Timestamp { @@ -33,7 +30,7 @@ impl TryFrom for Time { nanos: value.nanos, }; - Ok(SystemTime::from(prost_value).into()) + SystemTime::from(prost_value).into() } } @@ -66,12 +63,15 @@ impl Time { self.0 .signed_duration_since(other.0) .to_std() - .map_err(|_| Kind::OutOfRange.into()) + .map_err(error::out_of_range_error) } /// Parse [`Time`] from an RFC 3339 date pub fn parse_from_rfc3339(s: &str) -> Result { - Ok(Time(DateTime::parse_from_rfc3339(s)?.with_timezone(&Utc))) + let date = DateTime::parse_from_rfc3339(s) + .map_err(error::chrono_parse_error)? + .with_timezone(&Utc); + Ok(Time(date)) } /// Return an RFC 3339 and ISO 8601 date and time string with 6 subseconds digits and Z. diff --git a/tendermint/src/timeout.rs b/tendermint/src/timeout.rs index 9a1bc63b3..0d6319595 100644 --- a/tendermint/src/timeout.rs +++ b/tendermint/src/timeout.rs @@ -1,5 +1,4 @@ -use crate::{Error, Kind}; -use anomaly::{fail, format_err}; +use crate::error::{self, Error}; use serde::{de, de::Error as _, ser, Deserialize, Serialize}; use std::{fmt, ops::Deref, str::FromStr, time::Duration}; @@ -34,20 +33,20 @@ impl FromStr for Timeout { fn from_str(s: &str) -> Result { // Timeouts are either 'ms' or 's', and should always end with 's' if s.len() < 2 || !s.ends_with('s') { - fail!(Kind::Parse, "invalid units"); + return Err(error::parse_error("invalid units".to_string())); } let units = match s.chars().nth(s.len() - 2) { Some('m') => "ms", Some('0'..='9') => "s", - _ => fail!(Kind::Parse, "invalid units"), + _ => return Err(error::parse_error("invalid units".to_string())), }; let numeric_part = s.chars().take(s.len() - units.len()).collect::(); let numeric_value = numeric_part .parse::() - .map_err(|e| format_err!(Kind::Parse, e))?; + .map_err(|e| error::parse_int_error(numeric_part, e))?; let duration = match units { "s" => Duration::from_secs(numeric_value), @@ -84,8 +83,7 @@ impl Serialize for Timeout { #[cfg(test)] mod tests { use super::Timeout; - use crate::Kind; - use anomaly::format_err; + use crate::error; #[test] fn parse_seconds() { @@ -101,9 +99,9 @@ mod tests { #[test] fn reject_no_units() { - let expect = format_err!(Kind::Parse, "invalid units").to_string(); - let got = "123".parse::().unwrap_err().to_string(); - - assert_eq!(got, expect); + match "123".parse::().unwrap_err().detail { + error::ErrorDetail::Parse(_) => {} + _ => panic!("expected parse error to be returned"), + } } } diff --git a/tendermint/src/validator.rs b/tendermint/src/validator.rs index 9d4595a75..35376217e 100644 --- a/tendermint/src/validator.rs +++ b/tendermint/src/validator.rs @@ -3,7 +3,7 @@ use serde::{de::Error as _, Deserialize, Deserializer, Serialize}; use subtle_encoding::base64; -use crate::{account, hash::Hash, merkle, vote, Error, Kind, PublicKey, Signature}; +use crate::{account, error, hash::Hash, merkle, vote, Error, PublicKey, Signature}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::SimpleValidator as RawSimpleValidator; @@ -37,11 +37,10 @@ impl TryFrom for Set { // Ensure that the raw voting power matches the computed one let raw_voting_power = value.total_voting_power.try_into()?; if raw_voting_power != validator_set.total_voting_power() { - return Err(Kind::RawVotingPowerMismatch { - raw: raw_voting_power, - computed: validator_set.total_voting_power(), - } - .into()); + return Err(error::raw_voting_power_mismatch_error( + raw_voting_power, + validator_set.total_voting_power(), + )); } Ok(validator_set) @@ -93,7 +92,7 @@ impl Set { .iter() .find(|v| v.address == proposer_address) .cloned() - .ok_or(Kind::ProposerNotFound(proposer_address))?; + .ok_or_else(|| error::proposer_not_found_error(proposer_address))?; // Create the validator set with the given proposer. // This is required by IBC on-chain validation. @@ -170,10 +169,13 @@ impl TryFrom for Info { fn try_from(value: RawValidator) -> Result { Ok(Info { address: value.address.try_into()?, - pub_key: value.pub_key.ok_or(Kind::MissingPublicKey)?.try_into()?, + pub_key: value + .pub_key + .ok_or_else(error::missing_public_key_error)? + .try_into()?, power: value.voting_power.try_into()?, name: None, - proposer_priority: value.proposer_priority.try_into()?, + proposer_priority: value.proposer_priority.into(), }) } } diff --git a/tendermint/src/vote.rs b/tendermint/src/vote.rs index 8f35e3565..95138054b 100644 --- a/tendermint/src/vote.rs +++ b/tendermint/src/vote.rs @@ -11,9 +11,9 @@ pub use self::sign_vote::*; pub use self::validator_index::ValidatorIndex; use crate::chain::Id as ChainId; use crate::consensus::State; +use crate::error::{self, Error}; use crate::hash; use crate::{account, block, Signature, Time}; -use crate::{Error, Kind::*}; use bytes::BufMut; use ed25519::Signature as ed25519Signature; use ed25519::SIGNATURE_LENGTH as ed25519SignatureLength; @@ -65,7 +65,7 @@ impl TryFrom for Vote { fn try_from(value: RawVote) -> Result { if value.timestamp.is_none() { - return Err(NoTimestamp.into()); + return Err(error::missing_timestamp_error()); } Ok(Vote { vote_type: value.r#type.try_into()?, @@ -77,7 +77,7 @@ impl TryFrom for Vote { .map(TryInto::try_into) .transpose()? .filter(|i| i != &block::Id::default()), - timestamp: value.timestamp.map(TryInto::try_into).transpose()?, + timestamp: value.timestamp.map(|t| t.into()), validator_address: value.validator_address.try_into()?, validator_index: value.validator_index.try_into()?, signature: value.signature.try_into()?, @@ -231,7 +231,7 @@ impl TryFrom for Type { match value { 1 => Ok(Type::Prevote), 2 => Ok(Type::Precommit), - _ => Err(InvalidMessageType.into()), + _ => Err(error::invalid_message_type_error()), } } } @@ -259,7 +259,7 @@ impl FromStr for Type { match s { "Prevote" => Ok(Self::Prevote), "Precommit" => Ok(Self::Precommit), - _ => Err(InvalidMessageType.into()), + _ => Err(error::invalid_message_type_error()), } } } diff --git a/tendermint/src/vote/canonical_vote.rs b/tendermint/src/vote/canonical_vote.rs index 170af775b..91d93e825 100644 --- a/tendermint/src/vote/canonical_vote.rs +++ b/tendermint/src/vote/canonical_vote.rs @@ -1,6 +1,6 @@ use crate::chain::Id as ChainId; +use crate::error::{self, Error}; use crate::{block, Time}; -use crate::{Error, Kind::*}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::CanonicalVote as RawCanonicalVote; @@ -37,12 +37,13 @@ impl TryFrom for CanonicalVote { fn try_from(value: RawCanonicalVote) -> Result { if value.timestamp.is_none() { - return Err(NoTimestamp.into()); - } - if value.round > i32::MAX as i64 { - // CanonicalVote uses sfixed64, Vote uses int32. They translate to u64 vs i32 in Rust. - return Err(IntegerOverflow.into()); + return Err(error::missing_timestamp_error()); } + let _val: i32 = value + .round + .try_into() + .map_err(error::integer_overflow_error)?; + // If the Hash is empty in BlockId, the BlockId should be empty. // See: https://github.com/informalsystems/tendermint-rs/issues/663 let block_id = value.block_id.filter(|i| !i.hash.is_empty()); @@ -50,8 +51,8 @@ impl TryFrom for CanonicalVote { vote_type: value.r#type.try_into()?, height: value.height.try_into()?, round: (value.round as i32).try_into()?, - block_id: block_id.map(TryInto::try_into).transpose()?, - timestamp: value.timestamp.map(TryInto::try_into).transpose()?, + block_id: block_id.map(|b| b.try_into()).transpose()?, + timestamp: value.timestamp.map(|t| t.into()), chain_id: ChainId::try_from(value.chain_id)?, }) } diff --git a/tendermint/src/vote/power.rs b/tendermint/src/vote/power.rs index dba8ac304..3d6a02021 100644 --- a/tendermint/src/vote/power.rs +++ b/tendermint/src/vote/power.rs @@ -5,7 +5,7 @@ use std::fmt; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; -use crate::{Error, Kind}; +use crate::error::{self, Error}; /// Voting power #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Default)] @@ -21,7 +21,9 @@ impl TryFrom for Power { type Error = Error; fn try_from(value: i64) -> Result { - Ok(Power(value.try_into().map_err(|_| Kind::NegativePower)?)) + Ok(Power( + value.try_into().map_err(error::negative_power_error)?, + )) } } @@ -35,9 +37,8 @@ impl TryFrom for Power { type Error = Error; fn try_from(value: u64) -> Result { - if value > i64::MAX as u64 { - return Err(Kind::IntegerOverflow.into()); - } + let _val: i64 = value.try_into().map_err(error::integer_overflow_error)?; + Ok(Power(value)) } } diff --git a/tendermint/src/vote/sign_vote.rs b/tendermint/src/vote/sign_vote.rs index 9db358e70..d0690d4e9 100644 --- a/tendermint/src/vote/sign_vote.rs +++ b/tendermint/src/vote/sign_vote.rs @@ -1,8 +1,8 @@ use crate::chain; +use crate::error::{self, Error}; use crate::Vote; -use crate::{Error, Kind}; use bytes::BufMut; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use tendermint_proto::privval::SignedVoteResponse as RawSignedVoteResponse; use tendermint_proto::privval::{RemoteSignerError, SignVoteRequest as RawSignVoteRequest}; use tendermint_proto::Error as ProtobufError; @@ -23,13 +23,14 @@ impl TryFrom for SignVoteRequest { type Error = Error; fn try_from(value: RawSignVoteRequest) -> Result { - if value.vote.is_none() { - return Err(Kind::NoVoteFound.into()); - } - Ok(SignVoteRequest { - vote: Vote::try_from(value.vote.unwrap())?, - chain_id: chain::Id::try_from(value.chain_id)?, - }) + let vote = value + .vote + .ok_or_else(error::no_vote_found_error)? + .try_into()?; + + let chain_id = value.chain_id.try_into()?; + + Ok(SignVoteRequest { vote, chain_id }) } } diff --git a/tendermint/src/vote/validator_index.rs b/tendermint/src/vote/validator_index.rs index d928e28db..684ca7e26 100644 --- a/tendermint/src/vote/validator_index.rs +++ b/tendermint/src/vote/validator_index.rs @@ -1,4 +1,4 @@ -use crate::error::{Error, Kind}; +use crate::error::{self, Error}; use std::convert::TryInto; use std::{ convert::TryFrom, @@ -15,7 +15,9 @@ impl TryFrom for ValidatorIndex { fn try_from(value: i32) -> Result { Ok(ValidatorIndex( - value.try_into().map_err(|_| Kind::NegativeValidatorIndex)?, + value + .try_into() + .map_err(error::negative_validator_index_error)?, )) } } @@ -30,9 +32,7 @@ impl TryFrom for ValidatorIndex { type Error = Error; fn try_from(value: u32) -> Result { - if value > i32::MAX as u32 { - return Err(Kind::IntegerOverflow.into()); - } + let _val: i32 = value.try_into().map_err(error::integer_overflow_error)?; Ok(ValidatorIndex(value)) } } @@ -48,7 +48,7 @@ impl TryFrom for ValidatorIndex { fn try_from(value: usize) -> Result { Ok(ValidatorIndex( - value.try_into().map_err(|_| Kind::IntegerOverflow)?, + value.try_into().map_err(error::integer_overflow_error)?, )) } } @@ -87,7 +87,7 @@ impl FromStr for ValidatorIndex { fn from_str(s: &str) -> Result { ValidatorIndex::try_from( s.parse::() - .map_err(|_| Kind::Parse.context("validator index decode"))?, + .map_err(|e| error::parse_int_error("validator index decode".to_string(), e))?, ) } } diff --git a/test/src/pipe.rs b/test/src/pipe.rs index 1318295e4..d245db67b 100644 --- a/test/src/pipe.rs +++ b/test/src/pipe.rs @@ -61,7 +61,8 @@ pub fn async_pipe_buffered() -> (Reader, BufWriter) { ) } -/// Creates a pair of pipes for bidirectional communication using buffered writer, a bit like UNIX's `socketpair(2)`. +/// Creates a pair of pipes for bidirectional communication using buffered writer, a bit like UNIX's +/// `socketpair(2)`. pub fn async_bipipe_buffered() -> ( readwrite::ReadWrite, readwrite::ReadWrite, From 0df6c59eaf36d39e537e0c6899040c38ecddddad Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 30 Jun 2021 16:11:23 +0200 Subject: [PATCH 02/25] Use flex-error for p2p --- p2p/Cargo.toml | 4 ++ p2p/src/error.rs | 75 +++++++++++++++----- p2p/src/lib.rs | 1 - p2p/src/secret_connection.rs | 99 ++++++++++++--------------- p2p/src/secret_connection/protocol.rs | 31 ++++----- 5 files changed, 119 insertions(+), 91 deletions(-) diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index 9be2114b2..da78b8f61 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -21,6 +21,7 @@ description = """ test = false [features] +default = ["amino"] amino = ["prost-amino", "prost-amino-derive"] [dependencies] @@ -37,6 +38,9 @@ subtle = "2" thiserror = "1" x25519-dalek = "1.1" zeroize = "1" +signature = "1.3.0" +aead = "0.4.1" +flex-error = "0.2.1" # path dependencies tendermint = { path = "../tendermint", version = "0.20.0" } diff --git a/p2p/src/error.rs b/p2p/src/error.rs index 4f1cc20a7..f3c01fe54 100644 --- a/p2p/src/error.rs +++ b/p2p/src/error.rs @@ -1,19 +1,62 @@ //! Error types -use thiserror::Error; - -/// Kinds of errors -#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)] -pub enum Error { - /// Cryptographic operation failed - #[error("cryptographic error")] - Crypto, - - /// Malformatted or otherwise invalid cryptographic key - #[error("invalid key")] - InvalidKey, - - /// Network protocol-related errors - #[error("protocol error")] - Protocol, +use flex_error::{define_error, DisplayError, TraceError}; +use prost::DecodeError; +use prost_amino::DecodeError as AminoDecodeError; +use signature::Error as SignatureError; + +define_error! { + Error { + Crypto + | _ | { "cryptographic error" }, + + InvalidKey + | _ | { "invalid key" }, + + LowOrderKey + | _ | { "low-order points found (potential MitM attack!)" }, + + Protocol + | _ | { "protocol error" }, + + MalformedHandshake + | _ | { "malformed handshake message (protocol version mismatch?)" }, + + Io + [ TraceError ] + | _ | { "io error" }, + + Decode + [ TraceError ] + | _ | { "malformed handshake message (protocol version mismatch?)" }, + + AminoDecode + [ TraceError ] + | _ | { "malformed handshake message (protocol version mismatch?)" }, + + MissingSecret + | _ | { "missing secret: forgot to call Handshake::new?" }, + + MissingKey + | _ | { "public key missing" }, + + Signature + [ TraceError ] + | _ | { "signature error" }, + + UnsupportedKey + | _ | { "secp256k1 is not supported" }, + + Aead + [ DisplayError ] + | _ | { "aead error" }, + + ShortCiphertext + { tag_size: usize } + | e | { format_args!("ciphertext must be at least as long as a MAC tag {}", e.tag_size) }, + + SmallOutputBuffer + | _ | { "output buffer is too small" }, + + } } diff --git a/p2p/src/lib.rs b/p2p/src/lib.rs index b2b259314..0958f36d3 100644 --- a/p2p/src/lib.rs +++ b/p2p/src/lib.rs @@ -16,7 +16,6 @@ clippy::nursery, clippy::pedantic, clippy::unwrap_used, - missing_docs, unused_import_braces, unused_qualifications )] diff --git a/p2p/src/secret_connection.rs b/p2p/src/secret_connection.rs index 00011108a..e8d32a82b 100644 --- a/p2p/src/secret_connection.rs +++ b/p2p/src/secret_connection.rs @@ -8,16 +8,15 @@ use std::{ slice, }; +use crate::error::{self, Error}; use chacha20poly1305::{ aead::{generic_array::GenericArray, AeadInPlace, NewAead}, ChaCha20Poly1305, }; use ed25519_dalek::{self as ed25519, Signer, Verifier}; -use eyre::{eyre, Result, WrapErr}; use merlin::Transcript; use rand_core::OsRng; use subtle::ConstantTimeEq; -use thiserror::Error; use x25519_dalek::{EphemeralSecret, PublicKey as EphemeralPublic}; use tendermint_proto as proto; @@ -47,22 +46,6 @@ pub const DATA_MAX_SIZE: usize = 1024; const DATA_LEN_SIZE: usize = 4; const TOTAL_FRAME_SIZE: usize = DATA_MAX_SIZE + DATA_LEN_SIZE; -/// Kinds of errors -#[derive(Copy, Clone, Debug, Error, Eq, PartialEq)] -pub enum Error { - /// Cryptographic operation failed - #[error("cryptographic error")] - Crypto, - - /// Malformatted or otherwise invalid cryptographic key - #[error("invalid key")] - InvalidKey, - - /// Network protocol-related errors - #[error("protocol error")] - Protocol, -} - /// Handshake is a process of establishing the `SecretConnection` between two peers. /// [Specification](https://github.com/tendermint/spec/blob/master/spec/p2p/peer.md#authenticated-encryption-handshake) pub struct Handshake { @@ -121,10 +104,10 @@ impl Handshake { pub fn got_key( &mut self, remote_eph_pubkey: EphemeralPublic, - ) -> Result> { + ) -> Result, Error> { let local_eph_privkey = match self.state.local_eph_privkey.take() { Some(key) => key, - None => return Err(eyre!("forgot to call Handshake::new?")), + None => return Err(error::missing_secret_error()), }; let local_eph_pubkey = EphemeralPublic::from(&local_eph_privkey); @@ -141,8 +124,7 @@ impl Handshake { // - https://github.com/tendermint/kms/issues/142 // - https://eprint.iacr.org/2019/526.pdf if shared_secret.as_bytes().ct_eq(&[0x00; 32]).unwrap_u8() == 1 { - return Err(Error::InvalidKey) - .wrap_err("low-order points found (potential MitM attack!)"); + return Err(error::low_order_key_error()); } // Sort by lexical order. @@ -189,28 +171,33 @@ impl Handshake { /// # Errors /// /// * if signature scheme isn't supported - pub fn got_signature(&mut self, auth_sig_msg: proto::p2p::AuthSigMessage) -> Result { - let remote_pubkey = auth_sig_msg + pub fn got_signature( + &mut self, + auth_sig_msg: proto::p2p::AuthSigMessage, + ) -> Result { + let pk_sum = auth_sig_msg .pub_key - .and_then(|pk| match pk.sum? { - proto::crypto::public_key::Sum::Ed25519(ref bytes) => { - ed25519::PublicKey::from_bytes(bytes).ok() - } - proto::crypto::public_key::Sum::Secp256k1(_) => None, - }) - .ok_or(Error::Crypto)?; + .and_then(|key| key.sum) + .ok_or_else(error::missing_key_error)?; + + let remote_pubkey = match pk_sum { + proto::crypto::public_key::Sum::Ed25519(ref bytes) => { + ed25519::PublicKey::from_bytes(bytes).map_err(error::signature_error) + } + proto::crypto::public_key::Sum::Secp256k1(_) => Err(error::unsupported_key_error()), + }?; - let remote_sig = - ed25519::Signature::try_from(auth_sig_msg.sig.as_slice()).map_err(|_| Error::Crypto)?; + let remote_sig = ed25519::Signature::try_from(auth_sig_msg.sig.as_slice()) + .map_err(error::signature_error)?; if self.protocol_version.has_transcript() { remote_pubkey .verify(&self.state.sc_mac, &remote_sig) - .map_err(|_| Error::Crypto)?; + .map_err(error::signature_error)?; } else { remote_pubkey .verify(&self.state.kdf.challenge, &remote_sig) - .map_err(|_| Error::Crypto)?; + .map_err(error::signature_error)?; } // We've authorized. @@ -247,7 +234,7 @@ impl SecretConnection { mut io_handler: IoHandler, local_privkey: ed25519::Keypair, protocol_version: Version, - ) -> Result { + ) -> Result { // Start a handshake process. let local_pubkey = PublicKey::from(&local_privkey); let (mut h, local_eph_pubkey) = Handshake::new(local_privkey, protocol_version); @@ -292,7 +279,7 @@ impl SecretConnection { &self, chunk: &[u8], sealed_frame: &mut [u8; TAG_SIZE + TOTAL_FRAME_SIZE], - ) -> Result<()> { + ) -> Result<(), Error> { debug_assert!(!chunk.is_empty(), "chunk is empty"); debug_assert!( chunk.len() <= TOTAL_FRAME_SIZE - DATA_LEN_SIZE, @@ -310,7 +297,7 @@ impl SecretConnection { b"", &mut sealed_frame[..TOTAL_FRAME_SIZE], ) - .map_err(|_| Error::Crypto)?; + .map_err(error::aead_error)?; sealed_frame[TOTAL_FRAME_SIZE..].copy_from_slice(tag.as_slice()); @@ -318,21 +305,16 @@ impl SecretConnection { } /// Decrypt AEAD authenticated data - fn decrypt(&self, ciphertext: &[u8], out: &mut [u8]) -> Result { + fn decrypt(&self, ciphertext: &[u8], out: &mut [u8]) -> Result { if ciphertext.len() < TAG_SIZE { - return Err(Error::Crypto).wrap_err_with(|| { - format!( - "ciphertext must be at least as long as a MAC tag {}", - TAG_SIZE - ) - }); + return Err(error::short_ciphertext_error(TAG_SIZE)); } // Split ChaCha20 ciphertext from the Poly1305 tag let (ct, tag) = ciphertext.split_at(ciphertext.len() - TAG_SIZE); if out.len() < ct.len() { - return Err(Error::Crypto).wrap_err("output buffer is too small"); + return Err(error::small_output_buffer_error()); } let in_out = &mut out[..ct.len()]; @@ -345,7 +327,7 @@ impl SecretConnection { in_out, tag.into(), ) - .map_err(|_| Error::Crypto)?; + .map_err(error::aead_error)?; Ok(in_out.len()) } @@ -457,18 +439,22 @@ fn share_eph_pubkey( handler: &mut IoHandler, local_eph_pubkey: &EphemeralPublic, protocol_version: Version, -) -> Result { +) -> Result { // Send our pubkey and receive theirs in tandem. // TODO(ismail): on the go side this is done in parallel, here we do send and receive after // each other. thread::spawn would require a static lifetime. // Should still work though. - handler.write_all(&protocol_version.encode_initial_handshake(local_eph_pubkey))?; + handler + .write_all(&protocol_version.encode_initial_handshake(local_eph_pubkey)) + .map_err(error::io_error)?; let mut response_len = 0_u8; - handler.read_exact(slice::from_mut(&mut response_len))?; + handler + .read_exact(slice::from_mut(&mut response_len)) + .map_err(error::io_error)?; let mut buf = vec![0; response_len as usize]; - handler.read_exact(&mut buf)?; + handler.read_exact(&mut buf).map_err(error::io_error)?; protocol_version.decode_initial_handshake(&buf) } @@ -476,10 +462,10 @@ fn share_eph_pubkey( fn sign_challenge( challenge: &[u8; 32], local_privkey: &dyn Signer, -) -> Result { +) -> Result { local_privkey .try_sign(challenge) - .map_err(|_| Error::Crypto.into()) + .map_err(error::signature_error) } // TODO(ismail): change from DecodeError to something more generic @@ -488,15 +474,16 @@ fn share_auth_signature( sc: &mut SecretConnection, pubkey: &ed25519::PublicKey, local_signature: &ed25519::Signature, -) -> Result { +) -> Result { let buf = sc .protocol_version .encode_auth_signature(pubkey, local_signature); - sc.write_all(&buf)?; + sc.write_all(&buf).map_err(error::io_error)?; let mut buf = vec![0; sc.protocol_version.auth_sig_msg_response_len()]; - sc.read_exact(&mut buf)?; + sc.read_exact(&mut buf).map_err(error::io_error)?; + sc.protocol_version.decode_auth_signature(&buf) } diff --git a/p2p/src/secret_connection/protocol.rs b/p2p/src/secret_connection/protocol.rs index 36c37122c..cb416d55d 100644 --- a/p2p/src/secret_connection/protocol.rs +++ b/p2p/src/secret_connection/protocol.rs @@ -3,7 +3,6 @@ use std::convert::TryInto; use ed25519_dalek as ed25519; -use eyre::{Report, Result, WrapErr}; use prost::Message as _; #[cfg(feature = "amino")] @@ -16,7 +15,7 @@ use tendermint_proto as proto; #[cfg(feature = "amino")] use super::amino_types; -use crate::error::Error; +use crate::error::{self, Error}; /// Size of an X25519 or Ed25519 public key const PUBLIC_KEY_SIZE: usize = 32; @@ -81,14 +80,13 @@ impl Version { /// # Errors /// /// * if the message is malformed - pub fn decode_initial_handshake(self, bytes: &[u8]) -> Result { + pub fn decode_initial_handshake(self, bytes: &[u8]) -> Result { let eph_pubkey = if self.is_protobuf() { // Equivalent Go implementation: // https://github.com/tendermint/tendermint/blob/9e98c74/p2p/conn/secret_connection.go#L315-L323 // TODO(tarcieri): proper protobuf framing if bytes.len() != 34 || bytes[..2] != [0x0a, 0x20] { - return Err(Error::Protocol) - .wrap_err("malformed handshake message (protocol version mismatch?)"); + return Err(error::malformed_handshake_error()); } let eph_pubkey_bytes: [u8; 32] = bytes[2..].try_into().expect("framing failed"); @@ -99,8 +97,7 @@ impl Version { // // Check that the length matches what we expect and the length prefix is correct if bytes.len() != 33 || bytes[0] != 32 { - return Err(Error::Protocol) - .wrap_err("malformed handshake message (protocol version mismatch?)"); + return Err(error::malformed_handshake_error()); } let eph_pubkey_bytes: [u8; 32] = bytes[1..].try_into().expect("framing failed"); @@ -109,7 +106,7 @@ impl Version { // Reject the key if it is of low order if is_low_order_point(&eph_pubkey) { - return Err(Error::InvalidKey).wrap_err("low order key"); + return Err(error::low_order_key_error()); } Ok(eph_pubkey) @@ -161,16 +158,10 @@ impl Version { /// # Errors /// /// * if the decoding of the bytes fails - pub fn decode_auth_signature(self, bytes: &[u8]) -> Result { + pub fn decode_auth_signature(self, bytes: &[u8]) -> Result { if self.is_protobuf() { // Parse Protobuf-encoded `AuthSigMessage` - proto::p2p::AuthSigMessage::decode_length_delimited(bytes).map_err(|e| { - let message = format!( - "malformed handshake message (protocol version mismatch?): {}", - e - ); - Report::new(Error::Protocol).wrap_err(message) - }) + proto::p2p::AuthSigMessage::decode_length_delimited(bytes).map_err(error::decode_error) } else { self.decode_auth_signature_amino(bytes) } @@ -207,9 +198,13 @@ impl Version { #[allow(clippy::unused_self)] #[cfg(feature = "amino")] - fn decode_auth_signature_amino(self, bytes: &[u8]) -> Result { + fn decode_auth_signature_amino( + self, + bytes: &[u8], + ) -> Result { // Legacy Amino encoded `AuthSigMessage` - let amino_msg = amino_types::AuthSigMessage::decode_length_delimited(bytes)?; + let amino_msg = amino_types::AuthSigMessage::decode_length_delimited(bytes) + .map_err(error::amino_decode_error)?; let pub_key = proto::crypto::PublicKey { sum: Some(proto::crypto::public_key::Sum::Ed25519(amino_msg.pub_key)), }; From f9edb6652bfb2874fdc70838abf0bef35b62e442 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Thu, 1 Jul 2021 10:27:54 +0200 Subject: [PATCH 03/25] Use flex-error for light-client --- light-client/Cargo.toml | 1 + light-client/src/errors.rs | 298 ++++++++++++++++++--------- light-client/src/fork_detector.rs | 14 +- light-client/src/lib.rs | 1 - light-client/src/light_client.rs | 42 ++-- light-client/src/peer_list.rs | 23 ++- light-client/src/store/sled/utils.rs | 25 +-- light-client/src/supervisor.rs | 92 +++++---- 8 files changed, 300 insertions(+), 196 deletions(-) diff --git a/light-client/Cargo.toml b/light-client/Cargo.toml index c26ac01b0..1f8190b01 100644 --- a/light-client/Cargo.toml +++ b/light-client/Cargo.toml @@ -52,6 +52,7 @@ sled = { version = "0.34.3", optional = true } static_assertions = "1.1.0" thiserror = "1.0.15" tokio = { version = "1.0", features = ["rt"], optional = true } +flex-error = "0.2.1" [dev-dependencies] tendermint-testgen = { path = "../testgen" } diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index 00c69d78c..3896f1241 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -2,10 +2,8 @@ use std::fmt::Debug; -use anomaly::{BoxError, Context}; use crossbeam_channel as crossbeam; use serde::{Deserialize, Serialize}; -use thiserror::Error; use crate::{ components::io::IoError, @@ -13,97 +11,199 @@ use crate::{ predicates::errors::VerificationError, types::{Hash, Height, LightBlock, PeerId, Status}, }; +use flex_error::{define_error, DisplayError, TraceClone, TraceError}; + +define_error! { + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] + Error { + Io + [ DisplayError ] + | _ | { "io error" }, + + Store + [ TraceError ] + | _ | { "store error" }, + + NoPrimary + | _ | { "no primary" }, + + NoWitnesses + | _ | { "no witnesses" }, + + NoWitnessesLeft + | _ | { "no witnesses left" }, + + ForkDetected + { peers: Vec } + | e | { + format_args!("fork detected peers={0:?}", + e.peers) + }, + + NoInitialTrustedState + | _ | { "no initial trusted state" }, + + NoTrustedState + { status: Status } + | e | { + format_args!("no trusted state with status {:?}", + e.status) + }, + + TargetLowerThanTrustedState + { + target_height: Height, + trusted_height: Height, + } + | e | { + format_args!("target height ({0}) is lower than trusted state ({1})", + e.target_height, e.trusted_height) + }, + + TrustedStateOutsideTrustingPeriod + { + trusted_state: Box, + options: Options, + } + | _ | { + format_args!("trusted state outside of trusting period") + }, + + BisectionFailed + { + target_height: Height, + trusted_height: Height + } + | e | { + format_args!("bisection for target at height {0} failed when reached trusted state at height {1}", + e.target_height, e.trusted_height) + }, + + InvalidLightBlock + [ TraceClone ] + | _ | { "invalid light block" }, + + InvalidAdjacentHeaders + { + hash1: Hash, + hash2: Hash, + } + | e | { + format_args!("hash mismatch between two adjacent headers: {0} != {1}", + e.hash1, e.hash2) + }, + + MissingLastBlockId + { height: Height } + | e | { + format_args!("missing last_block_id for header at height {0}", + e.height) + }, + + ChannelDisconnected + | _ | { "internal channel disconnected" }, + + Sled + [ TraceError ] + | _ | { "sled error" }, + + SerdeCbor + [ TraceError ] + | _ | { "serde cbor error" }, -/// An error raised by this library -pub type Error = anomaly::Error; - -/// The various error kinds raised by this library -#[derive(Debug, Clone, Error, PartialEq, Serialize, Deserialize)] -pub enum ErrorKind { - /// I/O error - #[error("I/O error: {0}")] - Io(#[from] IoError), - - /// Store error - #[error("store error")] - Store, - - /// No primary - #[error("no primary")] - NoPrimary, - - /// No witnesses - #[error("no witnesses")] - NoWitnesses, - - /// No witness left - #[error("no witness left")] - NoWitnessLeft, - - /// A fork has been detected between some peers - #[error("fork detected peers={0:?}")] - ForkDetected(Vec), - - /// No initial trusted state - #[error("no initial trusted state")] - NoInitialTrustedState, - - /// No trusted state - #[error("no trusted state")] - NoTrustedState(Status), - - /// Target height for the light client lower than latest trusted state height - #[error("target height ({target_height}) is lower than trusted state ({trusted_height})")] - TargetLowerThanTrustedState { - /// Target height - target_height: Height, - /// Latest trusted state height - trusted_height: Height, - }, - - /// The trusted state is outside of the trusting period - #[error("trusted state outside of trusting period")] - TrustedStateOutsideTrustingPeriod { - /// Trusted state - trusted_state: Box, - /// Light client options - options: Options, - }, - - /// Bisection failed when reached trusted state - #[error("bisection for target at height {0} failed when reached trusted state at height {1}")] - BisectionFailed(Height, Height), - - /// Verification failed for a light block - #[error("invalid light block: {0}")] - InvalidLightBlock(#[source] VerificationError), - - /// Hash mismatch between two adjacent headers - #[error("hash mismatch between two adjacent headers: {h1} != {h2}")] - InvalidAdjacentHeaders { - /// Hash #1 - h1: Hash, - /// Hash #2 - h2: Hash, - }, - - /// Missing last_block_id field for header at given height - #[error("missing last_block_id for header at height {0}")] - MissingLastBlockId(Height), - - /// Internal channel disconnected - #[error("internal channel disconnected")] - ChannelDisconnected, -} - -impl ErrorKind { - /// Add additional context (i.e. include a source error and capture a backtrace). - /// You can convert the resulting `Context` into an `Error` by calling `.into()`. - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) } } +// /// An error raised by this library +// pub type Error = anomaly::Error; + +// /// The various error kinds raised by this library +// #[derive(Debug, Clone, Error, PartialEq, Serialize, Deserialize)] +// pub enum ErrorKind { +// /// I/O error +// #[error("I/O error: {0}")] +// Io(#[from] IoError), + +// /// Store error +// #[error("store error")] +// Store, + +// /// No primary +// #[error("no primary")] +// NoPrimary, + +// /// No witnesses +// #[error("no witnesses")] +// NoWitnesses, + +// /// No witness left +// #[error("no witness left")] +// NoWitnessLeft, + +// /// A fork has been detected between some peers +// #[error("fork detected peers={0:?}")] +// ForkDetected(Vec), + +// /// No initial trusted state +// #[error("no initial trusted state")] +// NoInitialTrustedState, + +// /// No trusted state +// #[error("no trusted state")] +// NoTrustedState(Status), + +// /// Target height for the light client lower than latest trusted state height +// #[error("target height ({target_height}) is lower than trusted state ({trusted_height})")] +// TargetLowerThanTrustedState { +// /// Target height +// target_height: Height, +// /// Latest trusted state height +// trusted_height: Height, +// }, + +// /// The trusted state is outside of the trusting period +// #[error("trusted state outside of trusting period")] +// TrustedStateOutsideTrustingPeriod { +// /// Trusted state +// trusted_state: Box, +// /// Light client options +// options: Options, +// }, + +// /// Bisection failed when reached trusted state +// #[error("bisection for target at height {0} failed when reached trusted state at height +// {1}")] BisectionFailed(Height, Height), + +// /// Verification failed for a light block +// #[error("invalid light block: {0}")] +// InvalidLightBlock(#[source] VerificationError), + +// /// Hash mismatch between two adjacent headers +// #[error("hash mismatch between two adjacent headers: {h1} != {h2}")] +// InvalidAdjacentHeaders { +// /// Hash #1 +// h1: Hash, +// /// Hash #2 +// h2: Hash, +// }, + +// /// Missing last_block_id field for header at given height +// #[error("missing last_block_id for header at height {0}")] +// MissingLastBlockId(Height), + +// /// Internal channel disconnected +// #[error("internal channel disconnected")] +// ChannelDisconnected, +// } + +// impl ErrorKind { +// /// Add additional context (i.e. include a source error and capture a backtrace). +// /// You can convert the resulting `Context` into an `Error` by calling `.into()`. +// pub fn context(self, source: impl Into) -> Context { +// Context::new(self, Some(source.into())) +// } +// } + /// Extension methods for `ErrorKind` pub trait ErrorExt { /// Whether this error means that the light block @@ -119,10 +219,10 @@ pub trait ErrorExt { fn is_timeout(&self) -> bool; } -impl ErrorExt for ErrorKind { +impl ErrorExt for ErrorDetail { fn not_enough_trust(&self) -> bool { if let Self::InvalidLightBlock(e) = self { - e.not_enough_trust() + e.source.not_enough_trust() } else { false } @@ -130,7 +230,7 @@ impl ErrorExt for ErrorKind { fn has_expired(&self) -> bool { if let Self::InvalidLightBlock(e) = self { - e.has_expired() + e.source.has_expired() } else { false } @@ -139,21 +239,17 @@ impl ErrorExt for ErrorKind { /// Whether this error means that a timeout occured when querying a node. fn is_timeout(&self) -> bool { if let Self::Io(e) = self { - e.is_timeout() + e.source.is_timeout() } else { false } } } -impl From> for ErrorKind { - fn from(_err: crossbeam::SendError) -> Self { - Self::ChannelDisconnected - } +pub fn send_error(_e: crossbeam::SendError) -> Error { + channel_disconnected_error() } -impl From for ErrorKind { - fn from(_err: crossbeam::RecvError) -> Self { - Self::ChannelDisconnected - } +pub fn recv_error(_e: crossbeam::RecvError) -> Error { + channel_disconnected_error() } diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index a626470b3..0f8c71fce 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize}; use crate::{ - errors::{Error, ErrorExt, ErrorKind}, + errors::{Error, ErrorDetail, ErrorExt}, operations::{Hasher, ProdHasher}, state::State, store::memory::MemoryStore, @@ -31,9 +31,9 @@ pub enum Fork { witness: LightBlock, }, /// The node has been deemed faulty for this `LightBlock` - Faulty(LightBlock, ErrorKind), + Faulty(LightBlock, ErrorDetail), /// The node has timed out - Timeout(PeerId, ErrorKind), + Timeout(PeerId, ErrorDetail), } /// Interface for a fork detector @@ -122,16 +122,16 @@ impl ForkDetector for ProdForkDetector { primary: verified_block.clone(), witness: witness_block, }), - Err(e) if e.kind().has_expired() => { + Err(e) if e.detail.has_expired() => { forks.push(Fork::Forked { primary: verified_block.clone(), witness: witness_block, }); } - Err(e) if e.kind().is_timeout() => { - forks.push(Fork::Timeout(witness_block.provider, e.kind().clone())) + Err(e) if e.detail.is_timeout() => { + forks.push(Fork::Timeout(witness_block.provider, e.detail.clone())) } - Err(e) => forks.push(Fork::Faulty(witness_block, e.kind().clone())), + Err(e) => forks.push(Fork::Faulty(witness_block, e.detail.clone())), } } diff --git a/light-client/src/lib.rs b/light-client/src/lib.rs index ab9165253..07964cca0 100644 --- a/light-client/src/lib.rs +++ b/light-client/src/lib.rs @@ -1,7 +1,6 @@ #![forbid(unsafe_code)] #![deny( warnings, - missing_docs, trivial_casts, trivial_numeric_casts, unused_import_braces, diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index 53c5dcd1c..6b438b541 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -9,10 +9,9 @@ use derive_more::Display; use serde::{Deserialize, Serialize}; use crate::{ - bail, components::{clock::Clock, io::*, scheduler::*, verifier::*}, contracts::*, - errors::{Error, ErrorKind}, + errors::{self as error, Error}, operations::Hasher, state::State, types::{Height, LightBlock, PeerId, Status, TrustThreshold}, @@ -120,10 +119,10 @@ impl LightClient { /// /// Note: This function delegates the actual work to `verify_to_target`. pub fn verify_to_highest(&mut self, state: &mut State) -> Result { - let target_block = match self.io.fetch_light_block(AtHeight::Highest) { - Ok(last_block) => last_block, - Err(io_error) => bail!(ErrorKind::Io(io_error)), - }; + let target_block = self + .io + .fetch_light_block(AtHeight::Highest) + .map_err(error::io_error)?; self.verify_to_target(target_block.height(), state) } @@ -179,7 +178,7 @@ impl LightClient { let highest = state .light_store .highest_trusted_or_verified() - .ok_or(ErrorKind::NoInitialTrustedState)?; + .ok_or_else(error::no_initial_trusted_state_error)?; if target_height >= highest.height() { // Perform forward verification with bisection @@ -205,21 +204,21 @@ impl LightClient { let trusted_state = state .light_store .highest_trusted_or_verified() - .ok_or(ErrorKind::NoInitialTrustedState)?; + .ok_or_else(error::no_initial_trusted_state_error)?; if target_height < trusted_state.height() { - bail!(ErrorKind::TargetLowerThanTrustedState { + return Err(error::target_lower_than_trusted_state_error( target_height, - trusted_height: trusted_state.height() - }); + trusted_state.height(), + )); } // Check invariant [LCV-INV-TP.1] if !is_within_trust_period(&trusted_state, self.options.trusting_period, now) { - bail!(ErrorKind::TrustedStateOutsideTrustingPeriod { - trusted_state: Box::new(trusted_state), - options: self.options, - }); + return Err(error::trusted_state_outside_trusting_period_error( + Box::new(trusted_state), + self.options, + )); } // Log the current height as a dependency of the block at the target height @@ -252,7 +251,7 @@ impl LightClient { // and abort. state.light_store.update(¤t_block, Status::Failed); - bail!(ErrorKind::InvalidLightBlock(e)) + return Err(error::invalid_light_block_error(e)); } Verdict::NotEnoughTrust(_) => { // The current block cannot be trusted because of a missing overlap in the @@ -282,13 +281,12 @@ impl LightClient { let trusted_state = state .light_store .highest_trusted_or_verified() - .ok_or(ErrorKind::NoInitialTrustedState)?; + .ok_or_else(error::no_initial_trusted_state_error)?; - Err(ErrorKind::TargetLowerThanTrustedState { + Err(error::target_lower_than_trusted_state_error( target_height, - trusted_height: trusted_state.height(), - } - .into()) + trusted_state.height(), + )) } /// Perform sequential backward verification. @@ -396,7 +394,7 @@ impl LightClient { let block = self .io .fetch_light_block(AtHeight::At(height)) - .map_err(ErrorKind::Io)?; + .map_err(error::io_error)?; state.light_store.insert(block.clone(), Status::Unverified); diff --git a/light-client/src/peer_list.rs b/light-client/src/peer_list.rs index 20e740ab7..187cecf60 100644 --- a/light-client/src/peer_list.rs +++ b/light-client/src/peer_list.rs @@ -1,8 +1,7 @@ //! Provides a peer list for use within the `Supervisor` use crate::{ - bail, - errors::{Error, ErrorKind}, + errors::{self as error, Error}, types::PeerId, }; @@ -149,9 +148,9 @@ impl PeerList { self.witnesses.remove(&new_primary); Ok(new_primary) } else if let Some(err) = primary_error { - bail!(ErrorKind::NoWitnessLeft.context(err)) + Err(err) } else { - bail!(ErrorKind::NoWitnessLeft) + Err(error::no_witnesses_left_error()) } } @@ -239,6 +238,7 @@ impl PeerListBuilder { #[cfg(test)] mod tests { use super::*; + use crate::errors::ErrorDetail; trait BTreeSetExt { fn to_vec(&self) -> Vec; @@ -307,10 +307,17 @@ mod tests { let mut peer_list = dummy_peer_list(); let _ = peer_list.replace_faulty_primary(None).unwrap(); let new_primary = peer_list.replace_faulty_primary(None); - assert_eq!( - new_primary.err().map(|e| e.kind().clone()), - Some(ErrorKind::NoWitnessLeft) - ); + match new_primary { + Err(e) => match e.detail { + ErrorDetail::NoWitnessesLeft(_) => {} + _ => panic!("expected NoWitnessesLeft error"), + }, + _ => panic!("expected NoWitnessesLeft error"), + } + // assert_eq!( + // new_primary.err().map(|e| e.kind().clone()), + // Some(error::no_witnesses_left_error()) + // ); } #[test] diff --git a/light-client/src/store/sled/utils.rs b/light-client/src/store/sled/utils.rs index 77b910622..21e6789d6 100644 --- a/light-client/src/store/sled/utils.rs +++ b/light-client/src/store/sled/utils.rs @@ -6,7 +6,7 @@ use std::marker::PhantomData; use serde::{de::DeserializeOwned, Serialize}; -use crate::errors::{Error, ErrorKind}; +use crate::errors::{self as error, Error}; use crate::types::Height; /// Provides a view over the database for storing key/value pairs at the given prefix. @@ -40,15 +40,11 @@ where /// Get the value associated with the given height within this tree pub fn get(&self, height: Height) -> Result, Error> { let key = key_bytes(height); - let value = self - .tree - .get(key) - .map_err(|e| ErrorKind::Store.context(e))?; + let value = self.tree.get(key).map_err(error::sled_error)?; match value { Some(bytes) => { - let value = - serde_cbor::from_slice(&bytes).map_err(|e| ErrorKind::Store.context(e))?; + let value = serde_cbor::from_slice(&bytes).map_err(error::serde_cbor_error)?; Ok(value) } None => Ok(None), @@ -59,10 +55,7 @@ where pub fn contains_key(&self, height: Height) -> Result { let key = key_bytes(height); - let exists = self - .tree - .contains_key(key) - .map_err(|e| ErrorKind::Store.context(e))?; + let exists = self.tree.contains_key(key).map_err(error::sled_error)?; Ok(exists) } @@ -70,11 +63,9 @@ where /// Insert a value associated with a height within this tree pub fn insert(&self, height: Height, value: &V) -> Result<(), Error> { let key = key_bytes(height); - let bytes = serde_cbor::to_vec(&value).map_err(|e| ErrorKind::Store.context(e))?; + let bytes = serde_cbor::to_vec(&value).map_err(error::serde_cbor_error)?; - self.tree - .insert(key, bytes) - .map_err(|e| ErrorKind::Store.context(e))?; + self.tree.insert(key, bytes).map_err(error::sled_error)?; Ok(()) } @@ -83,9 +74,7 @@ where pub fn remove(&self, height: Height) -> Result<(), Error> { let key = key_bytes(height); - self.tree - .remove(key) - .map_err(|e| ErrorKind::Store.context(e))?; + self.tree.remove(key).map_err(error::sled_error)?; Ok(()) } diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index d0b1afc32..f825a4adc 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -4,8 +4,7 @@ use crossbeam_channel as channel; use tendermint::evidence::{ConflictingHeadersEvidence, Evidence}; -use crate::bail; -use crate::errors::{Error, ErrorKind}; +use crate::errors::{self as error, Error}; use crate::evidence::EvidenceReporter; use crate::fork_detector::{Fork, ForkDetection, ForkDetector}; use crate::light_client::LightClient; @@ -215,7 +214,7 @@ impl Supervisor { Ok(verified_block) => { let trusted_block = primary .latest_trusted() - .ok_or(ErrorKind::NoTrustedState(Status::Trusted))?; + .ok_or_else(|| error::no_trusted_state_error(Status::Trusted))?; // Perform fork detection with the highest verified block and the trusted block. let outcome = self.detect_forks(&verified_block, &trusted_block)?; @@ -226,7 +225,7 @@ impl Supervisor { let forked = self.process_forks(forks)?; if !forked.is_empty() { // Fork detected, exiting - bail!(ErrorKind::ForkDetected(forked)) + return Err(error::fork_detected_error(forked)); } // If there were no hard forks, perform verification again @@ -297,7 +296,7 @@ impl Supervisor { self.evidence_reporter .report(Evidence::ConflictingHeaders(Box::new(evidence)), provider) - .map_err(ErrorKind::Io)?; + .map_err(error::io_error)?; Ok(()) } @@ -309,7 +308,7 @@ impl Supervisor { trusted_block: &LightBlock, ) -> Result { if self.peers.witnesses_ids().is_empty() { - bail!(ErrorKind::NoWitnesses); + return Err(error::no_witnesses_error()); } let witnesses = self @@ -328,28 +327,28 @@ impl Supervisor { /// This method should typically be called within a new thread with `std::thread::spawn`. pub fn run(mut self) -> Result<(), Error> { loop { - let event = self.receiver.recv().map_err(ErrorKind::from)?; + let event = self.receiver.recv().map_err(error::recv_error)?; match event { HandleInput::LatestTrusted(sender) => { let outcome = self.latest_trusted(); - sender.send(outcome).map_err(ErrorKind::from)?; + sender.send(outcome).map_err(error::send_error)?; } HandleInput::Terminate(sender) => { - sender.send(()).map_err(ErrorKind::from)?; + sender.send(()).map_err(error::send_error)?; return Ok(()); } HandleInput::VerifyToTarget(height, sender) => { let outcome = self.verify_to_target(height); - sender.send(outcome).map_err(ErrorKind::from)?; + sender.send(outcome).map_err(error::send_error)?; } HandleInput::VerifyToHighest(sender) => { let outcome = self.verify_to_highest(); - sender.send(outcome).map_err(ErrorKind::from)?; + sender.send(outcome).map_err(error::send_error)?; } HandleInput::GetStatus(sender) => { let outcome = self.latest_status(); - sender.send(outcome).map_err(ErrorKind::from)?; + sender.send(outcome).map_err(error::send_error)?; } } } @@ -377,9 +376,9 @@ impl SupervisorHandle { let (sender, receiver) = channel::bounded::>(1); let event = make_event(sender); - self.sender.send(event).map_err(ErrorKind::from)?; + self.sender.send(event).map_err(error::send_error)?; - receiver.recv().map_err(ErrorKind::from)? + receiver.recv().map_err(error::recv_error)? } } @@ -389,17 +388,17 @@ impl Handle for SupervisorHandle { self.sender .send(HandleInput::LatestTrusted(sender)) - .map_err(ErrorKind::from)?; + .map_err(error::send_error)?; - Ok(receiver.recv().map_err(ErrorKind::from)?) + Ok(receiver.recv().map_err(error::recv_error)?) } fn latest_status(&self) -> Result { let (sender, receiver) = channel::bounded::(1); self.sender .send(HandleInput::GetStatus(sender)) - .map_err(ErrorKind::from)?; - Ok(receiver.recv().map_err(ErrorKind::from)?) + .map_err(error::send_error)?; + Ok(receiver.recv().map_err(error::recv_error)?) } fn verify_to_highest(&self) -> Result { @@ -415,9 +414,9 @@ impl Handle for SupervisorHandle { self.sender .send(HandleInput::Terminate(sender)) - .map_err(ErrorKind::from)?; + .map_err(error::send_error)?; - Ok(receiver.recv().map_err(ErrorKind::from)?) + Ok(receiver.recv().map_err(error::recv_error)?) } } @@ -438,6 +437,7 @@ mod tests { tests::{MockClock, MockEvidenceReporter, MockIo, TrustOptions}, types::Time, }; + use flex_error::ErrorReport; use std::{collections::HashMap, convert::TryFrom, time::Duration}; use tendermint::block::Height; use tendermint::evidence::Duration as DurationStr; @@ -620,10 +620,13 @@ mod tests { let (result, _) = run_bisection_test(peer_list, 10); - let expected_err = ErrorKind::NoWitnesses; - let got_err = result.err().unwrap(); - - assert_eq!(&expected_err, got_err.kind()); + match result { + Err(ErrorReport { + detail: error::ErrorDetail::NoWitnesses(_), + trace: _, + }) => {} + _ => panic!("expected NoWitnesses error"), + } } #[test] @@ -643,13 +646,18 @@ mod tests { let (result, _) = run_bisection_test(peer_list, 10); - let expected_err = ErrorKind::Io(IoError::RpcError(rpc::Error::new( - Code::InvalidRequest, - None, - ))); - let got_err = result.err().unwrap(); - - assert_eq!(&expected_err, got_err.kind()); + match result { + Err(ErrorReport { + detail: error::ErrorDetail::Io(e), + trace: _, + }) => { + assert_eq!( + e.source, + IoError::RpcError(rpc::Error::new(Code::InvalidRequest, None,)) + ); + } + _ => panic!("expected NoWitnesses error"), + } } #[test] @@ -667,10 +675,13 @@ mod tests { let (result, _) = run_bisection_test(peer_list, 10); - let expected_err = ErrorKind::NoWitnessLeft; - let got_err = result.err().unwrap(); - - assert_eq!(&expected_err, got_err.kind()); + match result { + Err(ErrorReport { + detail: error::ErrorDetail::NoWitnessesLeft(_), + trace: _, + }) => {} + _ => panic!("expected NoWitnessesLeft error"), + } } #[test] @@ -703,10 +714,13 @@ mod tests { let (result, _) = run_bisection_test(peer_list, 5); - let expected_err = ErrorKind::ForkDetected(vec![witness[0].provider]); - let got_err = result.err().unwrap(); - - assert_eq!(&expected_err, got_err.kind()); + match result { + Err(ErrorReport { + detail: error::ErrorDetail::ForkDetected(_), + trace: _, + }) => {} + _ => panic!("expected ForkDetected error"), + } } #[test] From d042c0485e6e82a5a83f94411c0ff95e4130ab5d Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Thu, 1 Jul 2021 11:33:56 +0200 Subject: [PATCH 04/25] Use flex-error for light_client::predicates --- light-client/src/components/verifier.rs | 16 +- light-client/src/errors.rs | 103 +---- .../src/operations/commit_validator.rs | 23 +- light-client/src/operations/voting_power.rs | 41 +- light-client/src/predicates.rs | 424 ++++++++++-------- light-client/src/predicates/errors.rs | 268 ++++++----- 6 files changed, 444 insertions(+), 431 deletions(-) diff --git a/light-client/src/components/verifier.rs b/light-client/src/components/verifier.rs index fb37b1c81..32f1fc827 100644 --- a/light-client/src/components/verifier.rs +++ b/light-client/src/components/verifier.rs @@ -1,5 +1,6 @@ //! Provides an interface and default implementation of the `Verifier` component +use crate::operations::voting_power::VotingPowerTally; use crate::predicates as preds; use crate::{ errors::ErrorExt, @@ -10,7 +11,10 @@ use crate::{ }, types::{LightBlock, Time}, }; -use preds::{errors::VerificationError, ProdPredicates, VerificationPredicates}; +use preds::{ + errors::{VerificationError, VerificationErrorDetail}, + ProdPredicates, VerificationPredicates, +}; use serde::{Deserialize, Serialize}; /// Represents the result of the verification performed by the @@ -21,17 +25,19 @@ pub enum Verdict { Success, /// The minimum voting power threshold is not reached, /// the block cannot be trusted yet. - NotEnoughTrust(VerificationError), + NotEnoughTrust(VotingPowerTally), /// Verification failed, the block is invalid. - Invalid(VerificationError), + Invalid(VerificationErrorDetail), } impl From> for Verdict { fn from(result: Result<(), VerificationError>) -> Self { match result { Ok(()) => Self::Success, - Err(e) if e.not_enough_trust() => Self::NotEnoughTrust(e), - Err(e) => Self::Invalid(e), + Err(e) => match e.detail.not_enough_trust() { + Some(tally) => Self::NotEnoughTrust(tally), + _ => Self::Invalid(e.detail), + }, } } } diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index 3896f1241..fd292d3a9 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -2,16 +2,17 @@ use std::fmt::Debug; +use crate::operations::voting_power::VotingPowerTally; use crossbeam_channel as crossbeam; use serde::{Deserialize, Serialize}; use crate::{ components::io::IoError, light_client::Options, - predicates::errors::VerificationError, + predicates::errors::VerificationErrorDetail, types::{Hash, Height, LightBlock, PeerId, Status}, }; -use flex_error::{define_error, DisplayError, TraceClone, TraceError}; +use flex_error::{define_error, DisplayError, TraceError}; define_error! { #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] @@ -80,7 +81,7 @@ define_error! { }, InvalidLightBlock - [ TraceClone ] + [ DisplayError ] | _ | { "invalid light block" }, InvalidAdjacentHeaders @@ -114,101 +115,11 @@ define_error! { } } -// /// An error raised by this library -// pub type Error = anomaly::Error; - -// /// The various error kinds raised by this library -// #[derive(Debug, Clone, Error, PartialEq, Serialize, Deserialize)] -// pub enum ErrorKind { -// /// I/O error -// #[error("I/O error: {0}")] -// Io(#[from] IoError), - -// /// Store error -// #[error("store error")] -// Store, - -// /// No primary -// #[error("no primary")] -// NoPrimary, - -// /// No witnesses -// #[error("no witnesses")] -// NoWitnesses, - -// /// No witness left -// #[error("no witness left")] -// NoWitnessLeft, - -// /// A fork has been detected between some peers -// #[error("fork detected peers={0:?}")] -// ForkDetected(Vec), - -// /// No initial trusted state -// #[error("no initial trusted state")] -// NoInitialTrustedState, - -// /// No trusted state -// #[error("no trusted state")] -// NoTrustedState(Status), - -// /// Target height for the light client lower than latest trusted state height -// #[error("target height ({target_height}) is lower than trusted state ({trusted_height})")] -// TargetLowerThanTrustedState { -// /// Target height -// target_height: Height, -// /// Latest trusted state height -// trusted_height: Height, -// }, - -// /// The trusted state is outside of the trusting period -// #[error("trusted state outside of trusting period")] -// TrustedStateOutsideTrustingPeriod { -// /// Trusted state -// trusted_state: Box, -// /// Light client options -// options: Options, -// }, - -// /// Bisection failed when reached trusted state -// #[error("bisection for target at height {0} failed when reached trusted state at height -// {1}")] BisectionFailed(Height, Height), - -// /// Verification failed for a light block -// #[error("invalid light block: {0}")] -// InvalidLightBlock(#[source] VerificationError), - -// /// Hash mismatch between two adjacent headers -// #[error("hash mismatch between two adjacent headers: {h1} != {h2}")] -// InvalidAdjacentHeaders { -// /// Hash #1 -// h1: Hash, -// /// Hash #2 -// h2: Hash, -// }, - -// /// Missing last_block_id field for header at given height -// #[error("missing last_block_id for header at height {0}")] -// MissingLastBlockId(Height), - -// /// Internal channel disconnected -// #[error("internal channel disconnected")] -// ChannelDisconnected, -// } - -// impl ErrorKind { -// /// Add additional context (i.e. include a source error and capture a backtrace). -// /// You can convert the resulting `Context` into an `Error` by calling `.into()`. -// pub fn context(self, source: impl Into) -> Context { -// Context::new(self, Some(source.into())) -// } -// } - /// Extension methods for `ErrorKind` pub trait ErrorExt { /// Whether this error means that the light block /// cannot be trusted w.r.t. the latest trusted state. - fn not_enough_trust(&self) -> bool; + fn not_enough_trust(&self) -> Option; /// Whether this error means that the light block has expired, /// ie. it's outside of the trusting period. @@ -220,11 +131,11 @@ pub trait ErrorExt { } impl ErrorExt for ErrorDetail { - fn not_enough_trust(&self) -> bool { + fn not_enough_trust(&self) -> Option { if let Self::InvalidLightBlock(e) = self { e.source.not_enough_trust() } else { - false + None } } diff --git a/light-client/src/operations/commit_validator.rs b/light-client/src/operations/commit_validator.rs index b5a0f8158..416357aa7 100644 --- a/light-client/src/operations/commit_validator.rs +++ b/light-client/src/operations/commit_validator.rs @@ -1,9 +1,8 @@ //! Provides an interface and default implementation for the `CommitValidator` operation use crate::{ - bail, operations::{Hasher, ProdHasher}, - predicates::errors::VerificationError, + predicates::errors::{self as error, VerificationError}, types::{SignedHeader, ValidatorSet}, }; @@ -59,18 +58,15 @@ impl CommitValidator for ProdCommitValidator { // See https://github.com/informalsystems/tendermint-rs/issues/650 let has_present_signatures = signatures.iter().any(|cs| !cs.is_absent()); if !has_present_signatures { - bail!(VerificationError::ImplementationSpecific( - "no signatures for commit".to_string() - )); + return Err(error::no_signature_for_commit_error()); } // Check that that the number of signatures matches the number of validators. if signatures.len() != validator_set.validators().len() { - bail!(VerificationError::ImplementationSpecific(format!( - "pre-commit length: {} doesn't match validator length: {}", + return Err(error::mismatch_pre_commit_length_error( signatures.len(), - validator_set.validators().len() - ))); + validator_set.validators().len(), + )); } Ok(()) @@ -99,11 +95,10 @@ impl CommitValidator for ProdCommitValidator { }; if validator_set.validator(*validator_address) == None { - bail!(VerificationError::ImplementationSpecific(format!( - "Found a faulty signer ({}) not present in the validator set ({})", - validator_address, - self.hasher.hash_validator_set(validator_set) - ))); + return Err(error::faulty_signer_error( + validator_address.clone(), + self.hasher.hash_validator_set(validator_set), + )); } } diff --git a/light-client/src/operations/voting_power.rs b/light-client/src/operations/voting_power.rs index fafa0130c..685cf1a02 100644 --- a/light-client/src/operations/voting_power.rs +++ b/light-client/src/operations/voting_power.rs @@ -1,8 +1,7 @@ //! Provides an interface and default implementation for the `VotingPower` operation use crate::{ - bail, - predicates::errors::VerificationError, + predicates::errors::{self as error, VerificationError}, types::{Commit, SignedHeader, TrustThreshold, ValidatorSet}, }; @@ -62,7 +61,7 @@ pub trait VotingPowerCalculator: Send + Sync { if trust_threshold.is_enough_power(voting_power.tallied, voting_power.total) { Ok(()) } else { - Err(VerificationError::NotEnoughTrust(voting_power)) + Err(error::not_enough_trust_error(voting_power)) } } @@ -80,7 +79,7 @@ pub trait VotingPowerCalculator: Send + Sync { if trust_threshold.is_enough_power(voting_power.tallied, voting_power.total) { Ok(()) } else { - Err(VerificationError::InsufficientSignersOverlap(voting_power)) + Err(error::insufficient_signers_overlap_error(voting_power)) } } @@ -125,9 +124,7 @@ impl VotingPowerCalculator for ProdVotingPowerCalculator { for (signature, vote) in non_absent_votes { // Ensure we only count a validator's power once if seen_validators.contains(&vote.validator_address) { - bail!(VerificationError::DuplicateValidator( - vote.validator_address - )); + return Err(error::duplicate_validator_error(vote.validator_address)); } else { seen_validators.insert(vote.validator_address); } @@ -150,11 +147,11 @@ impl VotingPowerCalculator for ProdVotingPowerCalculator { .verify_signature(&sign_bytes, signed_vote.signature()) .is_err() { - bail!(VerificationError::InvalidSignature { - signature: signed_vote.signature().to_bytes(), - validator: Box::new(validator), + return Err(error::invalid_signature_error( + signed_vote.signature().to_bytes(), + Box::new(validator), sign_bytes, - }); + )); } // If the vote is neither absent nor nil, tally its power @@ -222,7 +219,9 @@ fn non_absent_vote( #[cfg(test)] mod tests { use super::*; + use crate::predicates::errors::VerificationErrorDetail; use crate::types::LightBlock; + use flex_error::ErrorReport; use tendermint::trust_threshold::TrustThresholdFraction; use tendermint_testgen::light_block::generate_signed_header; use tendermint_testgen::{ @@ -326,10 +325,12 @@ mod tests { trust_threshold, ); - let err = result_err.err().unwrap(); - match err { - VerificationError::InvalidSignature { .. } => {} - _ => panic!("unexpected error: {:?}", err), + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::InvalidSignature(_), + trace: _, + }) => {} + _ => panic!("expected InvalidSignature error"), } } @@ -349,10 +350,12 @@ mod tests { trust_threshold, ); - let err = result_err.err().unwrap(); - match err { - VerificationError::InvalidSignature { .. } => {} - _ => panic!("unexpected error: {:?}", err), + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::InvalidSignature(_), + trace: _, + }) => {} + _ => panic!("expected InvalidSignature error"), } } diff --git a/light-client/src/predicates.rs b/light-client/src/predicates.rs index a4794968e..981ec3dd7 100644 --- a/light-client/src/predicates.rs +++ b/light-client/src/predicates.rs @@ -1,13 +1,12 @@ //! Predicates for light block validation and verification. use crate::{ - ensure, light_client::Options, operations::{CommitValidator, Hasher, VotingPowerCalculator}, types::{Header, LightBlock, SignedHeader, Time, TrustThreshold, ValidatorSet}, }; -use errors::VerificationError; +use errors::{self as error, VerificationError}; use std::time::Duration; pub mod errors; @@ -34,15 +33,14 @@ pub trait VerificationPredicates: Send + Sync { ) -> Result<(), VerificationError> { let validators_hash = hasher.hash_validator_set(&light_block.validators); - ensure!( - light_block.signed_header.header.validators_hash == validators_hash, - VerificationError::InvalidValidatorSet { - header_validators_hash: light_block.signed_header.header.validators_hash, + if light_block.signed_header.header.validators_hash == validators_hash { + Ok(()) + } else { + Err(error::invalid_validator_set_error( + light_block.signed_header.header.validators_hash, validators_hash, - } - ); - - Ok(()) + )) + } } /// Check that the hash of the next validator set in the header match the actual one. @@ -53,15 +51,14 @@ pub trait VerificationPredicates: Send + Sync { ) -> Result<(), VerificationError> { let next_validators_hash = hasher.hash_validator_set(&light_block.next_validators); - ensure!( - light_block.signed_header.header.next_validators_hash == next_validators_hash, - VerificationError::InvalidNextValidatorSet { - header_next_validators_hash: light_block.signed_header.header.next_validators_hash, + if light_block.signed_header.header.next_validators_hash == next_validators_hash { + Ok(()) + } else { + Err(error::invalid_next_validator_set_error( + light_block.signed_header.header.next_validators_hash, next_validators_hash, - } - ); - - Ok(()) + )) + } } /// Check that the hash of the header in the commit matches the actual one. @@ -72,15 +69,14 @@ pub trait VerificationPredicates: Send + Sync { ) -> Result<(), VerificationError> { let header_hash = hasher.hash_header(&signed_header.header); - ensure!( - header_hash == signed_header.commit.block_id.hash, - VerificationError::InvalidCommitValue { + if header_hash == signed_header.commit.block_id.hash { + Ok(()) + } else { + Err(error::invalid_commit_value_error( header_hash, - commit_hash: signed_header.commit.block_id.hash, - } - ); - - Ok(()) + signed_header.commit.block_id.hash, + )) + } } /// Validate the commit using the given commit validator. @@ -104,12 +100,12 @@ pub trait VerificationPredicates: Send + Sync { now: Time, ) -> Result<(), VerificationError> { let expires_at = trusted_header.time + trusting_period; - ensure!( - expires_at > now, - VerificationError::NotWithinTrustPeriod { expires_at, now } - ); - Ok(()) + if expires_at > now { + Ok(()) + } else { + Err(error::not_within_trust_period_error(expires_at, now)) + } } /// Check that the untrusted header is from past. @@ -119,15 +115,14 @@ pub trait VerificationPredicates: Send + Sync { clock_drift: Duration, now: Time, ) -> Result<(), VerificationError> { - ensure!( - untrusted_header.time < now + clock_drift, - VerificationError::HeaderFromTheFuture { - header_time: untrusted_header.time, - now - } - ); - - Ok(()) + if untrusted_header.time < now + clock_drift { + Ok(()) + } else { + Err(error::header_from_the_future_error( + untrusted_header.time, + now, + )) + } } /// Check that time passed monotonically between the trusted header and the untrusted one. @@ -136,15 +131,14 @@ pub trait VerificationPredicates: Send + Sync { untrusted_header: &Header, trusted_header: &Header, ) -> Result<(), VerificationError> { - ensure!( - untrusted_header.time > trusted_header.time, - VerificationError::NonMonotonicBftTime { - header_bft_time: untrusted_header.time, - trusted_header_bft_time: trusted_header.time, - } - ); - - Ok(()) + if untrusted_header.time > trusted_header.time { + Ok(()) + } else { + Err(error::non_monotonic_bft_time_error( + untrusted_header.time, + trusted_header.time, + )) + } } /// Check that the height increased between the trusted header and the untrusted one. @@ -155,15 +149,14 @@ pub trait VerificationPredicates: Send + Sync { ) -> Result<(), VerificationError> { let trusted_height = trusted_header.height; - ensure!( - untrusted_header.height > trusted_header.height, - VerificationError::NonIncreasingHeight { - got: untrusted_header.height, - expected: trusted_height.increment(), - } - ); - - Ok(()) + if untrusted_header.height > trusted_header.height { + Ok(()) + } else { + Err(error::non_increasing_height_error( + untrusted_header.height, + trusted_height.increment(), + )) + } } /// Check that there is enough validators overlap between the trusted validator set @@ -198,16 +191,16 @@ pub trait VerificationPredicates: Send + Sync { light_block: &LightBlock, trusted_state: &LightBlock, ) -> Result<(), VerificationError> { - ensure!( - light_block.signed_header.header.validators_hash - == trusted_state.signed_header.header.next_validators_hash, - VerificationError::InvalidNextValidatorSet { - header_next_validators_hash: light_block.signed_header.header.validators_hash, - next_validators_hash: trusted_state.signed_header.header.next_validators_hash, - } - ); - - Ok(()) + if light_block.signed_header.header.validators_hash + == trusted_state.signed_header.header.next_validators_hash + { + Ok(()) + } else { + Err(error::invalid_next_validator_set_error( + light_block.signed_header.header.validators_hash, + trusted_state.signed_header.header.next_validators_hash, + )) + } } } @@ -297,6 +290,7 @@ pub fn verify( #[cfg(test)] mod tests { + use flex_error::ErrorReport; use std::ops::Sub; use std::time::Duration; use tendermint::Time; @@ -306,7 +300,9 @@ mod tests { Commit, Generator, Header, Validator, ValidatorSet, }; - use crate::predicates::{errors::VerificationError, ProdPredicates, VerificationPredicates}; + use crate::predicates::{ + errors::VerificationErrorDetail, ProdPredicates, VerificationPredicates, + }; use crate::operations::{ Hasher, ProdCommitValidator, ProdHasher, ProdVotingPowerCalculator, VotingPowerTally, @@ -340,14 +336,16 @@ mod tests { // 2. ensure header with non-monotonic bft time fails let result_err = vp.is_monotonic_bft_time(&header_one, &header_two); - assert!(result_err.is_err()); - - // 3. expect to error with: VerificationError::NonMonotonicBftTime - let error = VerificationError::NonMonotonicBftTime { - header_bft_time: header_one.time, - trusted_header_bft_time: header_two.time, - }; - assert_eq!(result_err.err().unwrap(), error); + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::NonMonotonicBftTime(e), + trace: _, + }) => { + assert_eq!(e.header_bft_time, header_one.time); + assert_eq!(e.trusted_header_bft_time, header_two.time); + } + _ => panic!("expected NonMonotonicBftTime error"), + } } #[test] @@ -364,14 +362,17 @@ mod tests { // 2. ensure header with non-monotonic height fails let result_err = vp.is_monotonic_height(&header_one, &header_two); - assert!(result_err.is_err()); - - // 3. expect to error with: VerificationError::NonMonotonicBftTime - let error = VerificationError::NonIncreasingHeight { - got: header_one.height, - expected: header_two.height.increment(), - }; - assert_eq!(result_err.err().unwrap(), error); + + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::NonIncreasingHeight(e), + trace: _, + }) => { + assert_eq!(e.got, header_one.height); + assert_eq!(e.expected, header_two.height.increment()); + } + _ => panic!("expected NonIncreasingHeight error"), + } } #[test] @@ -392,12 +393,18 @@ mod tests { trusting_period = Duration::new(0, 1); let result_err = vp.is_within_trust_period(&header, trusting_period, now); - assert!(result_err.is_err()); - // 3. ensure it fails with: VerificationError::NotWithinTrustPeriod let expires_at = header.time + trusting_period; - let error = VerificationError::NotWithinTrustPeriod { expires_at, now }; - assert_eq!(result_err.err().unwrap(), error); + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::NotWithinTrustPeriod(e), + trace: _, + }) => { + assert_eq!(e.expires_at, expires_at); + assert_eq!(e.now, now); + } + _ => panic!("expected NotWithinTrustPeriod error"), + } } #[test] @@ -417,15 +424,16 @@ mod tests { let now = Time::now().sub(one_second * 15); let result_err = vp.is_header_from_past(&header, one_second, now); - assert!(result_err.is_err()); - - // 3. ensure it fails with: VerificationError::HeaderFromTheFuture - let error = VerificationError::HeaderFromTheFuture { - header_time: header.time, - now, - }; - - assert_eq!(result_err.err().unwrap(), error); + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::HeaderFromTheFuture(e), + trace: _, + }) => { + assert_eq!(e.header_time, header.time); + assert_eq!(e.now, now); + } + _ => panic!("expected HeaderFromTheFuture error"), + } } #[test] @@ -456,31 +464,43 @@ mod tests { let val_sets_match_err = vp.validator_sets_match(&light_block, &hasher); - // ensure it fails - assert!(val_sets_match_err.is_err()); - - let val_set_error = VerificationError::InvalidValidatorSet { - header_validators_hash: light_block.signed_header.header.validators_hash, - validators_hash: hasher.hash_validator_set(&light_block.validators), - }; - - // ensure it fails with VerificationError::InvalidValidatorSet - assert_eq!(val_sets_match_err.err().unwrap(), val_set_error); + match val_sets_match_err { + Err(ErrorReport { + detail: VerificationErrorDetail::InvalidValidatorSet(e), + trace: _, + }) => { + assert_eq!( + e.header_validators_hash, + light_block.signed_header.header.validators_hash + ); + assert_eq!( + e.validators_hash, + hasher.hash_validator_set(&light_block.validators) + ); + } + _ => panic!("expected InvalidValidatorSet error"), + } // 2. For predicate: next_validator_sets_match light_block.next_validators = bad_validator_set; let next_val_sets_match_err = vp.next_validators_match(&light_block, &hasher); - // ensure it fails - assert!(next_val_sets_match_err.is_err()); - - let next_val_set_error = VerificationError::InvalidNextValidatorSet { - header_next_validators_hash: light_block.signed_header.header.next_validators_hash, - next_validators_hash: hasher.hash_validator_set(&light_block.next_validators), - }; - - // ensure it fails with VerificationError::InvalidNextValidatorSet - assert_eq!(next_val_sets_match_err.err().unwrap(), next_val_set_error); + match next_val_sets_match_err { + Err(ErrorReport { + detail: VerificationErrorDetail::InvalidNextValidatorSet(e), + trace: _, + }) => { + assert_eq!( + e.header_next_validators_hash, + light_block.signed_header.header.next_validators_hash + ); + assert_eq!( + e.next_validators_hash, + hasher.hash_validator_set(&light_block.next_validators) + ); + } + _ => panic!("expected InvalidNextValidatorSet error"), + } } #[test] @@ -505,16 +525,19 @@ mod tests { .unwrap(); let result_err = vp.header_matches_commit(&signed_header, &hasher); - assert!(result_err.is_err()); - // 3. ensure it fails with: VerificationError::InvalidCommitValue let header_hash = hasher.hash_header(&signed_header.header); - let error = VerificationError::InvalidCommitValue { - header_hash, - commit_hash: signed_header.commit.block_id.hash, - }; - assert_eq!(result_err.err().unwrap(), error); + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::InvalidCommitValue(e), + trace: _, + }) => { + assert_eq!(e.header_hash, header_hash); + assert_eq!(e.commit_hash, signed_header.commit.block_id.hash); + } + _ => panic!("expected InvalidCommitValue error"), + } } #[test] @@ -539,14 +562,14 @@ mod tests { signed_header.commit.signatures = vec![]; let mut result_err = vp.valid_commit(&signed_header, &val_set, &commit_validator); - assert!(result_err.is_err()); - let mut error = - VerificationError::ImplementationSpecific("no signatures for commit".to_string()); - - // ensure it fails with: - // VerificationError::ImplementationSpecific("no signatures for commit") - assert_eq!(result_err.err().unwrap(), error); + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::NoSignatureForCommit(_), + trace: _, + }) => {} + _ => panic!("expected ImplementationSpecific error"), + } // 3. commit.signatures.len() != validator_set.validators().len() // must return error @@ -554,16 +577,17 @@ mod tests { signed_header.commit.signatures = bad_sigs.clone(); result_err = vp.valid_commit(&signed_header, &val_set, &commit_validator); - assert!(result_err.is_err()); - - error = VerificationError::ImplementationSpecific(format!( - "pre-commit length: {} doesn't match validator length: {}", - signed_header.commit.signatures.len(), - val_set.validators().len() - )); - // ensure it fails with the expected error (as above) - assert_eq!(result_err.err().unwrap(), error); + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::MismatchPreCommitLength(e), + trace: _, + }) => { + assert_eq!(e.pre_commit_length, signed_header.commit.signatures.len()); + assert_eq!(e.validator_length, val_set.validators().len()); + } + _ => panic!("expected ImplementationSpecific error"), + } // 4. commit.BlockIdFlagAbsent - should be "Ok" bad_sigs.push(CommitSig::BlockIdFlagAbsent); @@ -589,23 +613,31 @@ mod tests { &val_set_with_faulty_signer, &commit_validator, ); - assert!(result_err.is_err()); - - error = VerificationError::ImplementationSpecific(format!( - "Found a faulty signer ({}) not present in the validator set ({})", - signed_header - .commit - .signatures - .iter() - .last() - .unwrap() - .validator_address() - .unwrap(), - hasher.hash_validator_set(&val_set_with_faulty_signer) - )); - // ensure it fails with the expected error (as above) - assert_eq!(result_err.err().unwrap(), error); + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::FaultySigner(e), + trace: _, + }) => { + assert_eq!( + e.signer, + signed_header + .commit + .signatures + .iter() + .last() + .unwrap() + .validator_address() + .unwrap() + ); + + assert_eq!( + e.validator_set, + hasher.hash_validator_set(&val_set_with_faulty_signer) + ); + } + _ => panic!("expected ImplementationSpecific error"), + } } #[test] @@ -635,15 +667,22 @@ mod tests { let result_err = vp.valid_next_validator_set(&light_block3, &light_block2); - assert!(result_err.is_err()); - - let error = VerificationError::InvalidNextValidatorSet { - header_next_validators_hash: light_block3.signed_header.header.validators_hash, - next_validators_hash: light_block2.signed_header.header.next_validators_hash, - }; - - // ensure it fails with the expected error (as above) - assert_eq!(result_err.err().unwrap(), error); + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::InvalidNextValidatorSet(e), + trace: _, + }) => { + assert_eq!( + e.header_next_validators_hash, + light_block3.signed_header.header.validators_hash + ); + assert_eq!( + e.next_validators_hash, + light_block2.signed_header.header.next_validators_hash + ); + } + _ => panic!("expected InvalidNextValidatorSet error"), + } } #[test] @@ -686,16 +725,22 @@ mod tests { &voting_power_calculator, ); - assert!(result_err.is_err()); - - let error = VerificationError::NotEnoughTrust(VotingPowerTally { - total: 200, - tallied: 100, - trust_threshold, - }); - - // ensure it fails with the expected error (as above) - assert_eq!(result_err.err().unwrap(), error); + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::NotEnoughTrust(e), + trace: _, + }) => { + assert_eq!( + e.tally, + VotingPowerTally { + total: 200, + tallied: 100, + trust_threshold, + } + ); + } + _ => panic!("expected NotEnoughTrust error"), + } } #[test] @@ -725,16 +770,23 @@ mod tests { &voting_power_calculator, ); - assert!(result_err.is_err()); - let trust_threshold = TrustThreshold::TWO_THIRDS; - let error = VerificationError::InsufficientSignersOverlap(VotingPowerTally { - total: 100, - tallied: 50, - trust_threshold, - }); - - // ensure it fails with the expected error (as above) - assert_eq!(result_err.err().unwrap(), error); + + match result_err { + Err(ErrorReport { + detail: VerificationErrorDetail::InsufficientSignersOverlap(e), + trace: _, + }) => { + assert_eq!( + e.tally, + VotingPowerTally { + total: 100, + tallied: 50, + trust_threshold, + } + ); + } + _ => panic!("expected InsufficientSignersOverlap error"), + } } } diff --git a/light-client/src/predicates/errors.rs b/light-client/src/predicates/errors.rs index 99c02fb34..f9b99c093 100644 --- a/light-client/src/predicates/errors.rs +++ b/light-client/src/predicates/errors.rs @@ -1,125 +1,171 @@ //! Errors which may be raised when verifying a `LightBlock` -use anomaly::{BoxError, Context}; +use flex_error::define_error; use serde::{Deserialize, Serialize}; -use thiserror::Error; use crate::errors::ErrorExt; use crate::operations::voting_power::VotingPowerTally; use crate::types::{Hash, Height, Time, Validator, ValidatorAddress}; +use tendermint::account::Id; + +define_error! { + #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] + VerificationError { + HeaderFromTheFuture + { + header_time: Time, + now: Time, + } + | e | { + format_args!("header from the future: header_time={0} now={1}", + e.header_time, e.now) + }, + + ImplementationSpecific + { + detail: String, + } + | e | { + format_args!("implementation specific: {0}", + e.detail) + }, + + NotEnoughTrust + { + tally: VotingPowerTally, + } + | e | { + format_args!("not enough trust because insufficient validators overlap: {0}", + e.tally) + }, + + InsufficientSignersOverlap + { + tally: VotingPowerTally, + } + | e | { + format_args!("insufficient signers overlap: {0}", + e.tally) + }, + + DuplicateValidator + { + address: ValidatorAddress, + } + | e | { + format_args!("duplicate validator with address {0}", + e.address) + }, + + InvalidSignature + { + signature: Vec, + validator: Box, + sign_bytes: Vec, + } + | e | { + format_args!("Couldn't verify signature `{:?}` with validator `{:?}` on sign_bytes `{:?}`", + e.signature, e.validator, e.sign_bytes) + }, + + InvalidCommitValue + { + header_hash: Hash, + commit_hash: Hash, + } + | e | { + format_args!("invalid commit value: header_hash={0} commit_hash={1}", + e.header_hash, e.commit_hash) + }, + + InvalidNextValidatorSet + { + header_next_validators_hash: Hash, + next_validators_hash: Hash, + } + | e | { + format_args!("invalid next validator set: header_next_validators_hash={0} next_validators_hash={1}", + e.header_next_validators_hash, e.next_validators_hash) + }, + + InvalidValidatorSet + { + header_validators_hash: Hash, + validators_hash: Hash, + } + | e | { + format_args!("invalid validator set: header_validators_hash={0} validators_hash={1}", + e.header_validators_hash, e.validators_hash) + }, + + NonIncreasingHeight + { + got: Height, + expected: Height, + } + | e | { + format_args!("non increasing height: got={0} expected={1}", + e.got, e.expected) + }, + + NonMonotonicBftTime + { + header_bft_time: Time, + trusted_header_bft_time: Time, + } + | e | { + format_args!("non monotonic BFT time: header_bft_time={0} trusted_header_bft_time={1}", + e.header_bft_time, e.trusted_header_bft_time) + }, + + NotWithinTrustPeriod + { + expires_at: Time, + now: Time, + } + | e | { + format_args!("not withing trusting period: expires_at={0} now={1}", + e.expires_at, e.now) + }, + + NoSignatureForCommit + | _ | { "no signatures for commit" }, + + MismatchPreCommitLength + { + pre_commit_length: usize, + validator_length: usize, + } + | e | { + format_args!( + "pre-commit length: {} doesn't match validator length: {}", + e.pre_commit_length, + e.validator_length + ) + }, + + FaultySigner + { + signer: Id, + validator_set: Hash + } + | e | { + format_args!( + "Found a faulty signer ({}) not present in the validator set ({})", + e.signer, + e.validator_set + ) + }, -/// The various errors which can be raised by the verifier component, -/// when validating or verifying a light block. -#[derive(Debug, Clone, Error, PartialEq, Serialize, Deserialize, Eq)] -pub enum VerificationError { - /// The header is from the future - #[error("header from the future: header_time={header_time} now={now}")] - HeaderFromTheFuture { - /// Time in the header - header_time: Time, - /// Current time - now: Time, - }, - - /// Implementation specific error, for the purpose of extensibility - #[error("implementation specific: {0}")] - ImplementationSpecific(String), - - /// Not enough trust because insufficient validators overlap - #[error("not enough trust because insufficient validators overlap: {0}")] - NotEnoughTrust(VotingPowerTally), - - /// Insufficient signers overlap - #[error("insufficient signers overlap: {0}")] - InsufficientSignersOverlap(VotingPowerTally), - - /// Duplicate validator in commit signatures - #[error("duplicate validator with address {0}")] - DuplicateValidator(ValidatorAddress), - - /// Invalid commit signature - #[error("Couldn't verify signature `{signature:?}` with validator `{validator:?}` on sign_bytes `{sign_bytes:?}`")] - InvalidSignature { - /// Signature as a byte array - signature: Vec, - /// Validator which provided the signature - validator: Box, - /// Bytes which were signed - sign_bytes: Vec, - }, - - /// Invalid commit - #[error("invalid commit value: header_hash={header_hash} commit_hash={commit_hash}")] - InvalidCommitValue { - /// Header hash - #[serde(with = "tendermint::serializers::hash")] - header_hash: Hash, - /// Commit hash - #[serde(with = "tendermint::serializers::hash")] - commit_hash: Hash, - }, - - /// Hash mismatch for the next validator set - #[error("invalid next validator set: header_next_validators_hash={header_next_validators_hash} next_validators_hash={next_validators_hash}")] - InvalidNextValidatorSet { - /// Next validator set hash - #[serde(with = "tendermint::serializers::hash")] - header_next_validators_hash: Hash, - /// Validator set hash - #[serde(with = "tendermint::serializers::hash")] - next_validators_hash: Hash, - }, - - /// Hash mismatch for the validator set - #[error("invalid validator set: header_validators_hash={header_validators_hash} validators_hash={validators_hash}")] - InvalidValidatorSet { - /// Hash of validator set stored in header - #[serde(with = "tendermint::serializers::hash")] - header_validators_hash: Hash, - /// Actual hash of validator set in header - #[serde(with = "tendermint::serializers::hash")] - validators_hash: Hash, - }, - - /// Unexpected header of non-increasing height compared to what was expected - #[error("non increasing height: got={got} expected={expected}")] - NonIncreasingHeight { - /// Actual height of header - got: Height, - /// Expected minimum height - expected: Height, - }, - - /// BFT Time between the trusted state and a header does not increase monotonically - #[error("non monotonic BFT time: header_bft_time={header_bft_time} trusted_header_bft_time={trusted_header_bft_time}")] - NonMonotonicBftTime { - /// BFT time of the untrusted header - header_bft_time: Time, - /// BFT time of the trusted header - trusted_header_bft_time: Time, - }, - - /// Trusted state not within the trusting period - #[error("not withing trusting period: expires_at={expires_at} now={now}")] - NotWithinTrustPeriod { - /// Expiration time of the header - expires_at: Time, - /// Current time - now: Time, - }, -} - -impl VerificationError { - /// Add additional context (i.e. include a source error and capture a backtrace). - /// You can convert the resulting `Context` into an `Error` by calling `.into()`. - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) } } -impl ErrorExt for VerificationError { - fn not_enough_trust(&self) -> bool { - matches!(self, Self::NotEnoughTrust { .. }) +impl ErrorExt for VerificationErrorDetail { + fn not_enough_trust(&self) -> Option { + match &self { + Self::NotEnoughTrust(e) => Some(e.tally.clone()), + _ => None, + } } fn has_expired(&self) -> bool { From c21edce7d89a8a021ad4304d6334640bd4a18bfe Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Thu, 1 Jul 2021 12:02:19 +0200 Subject: [PATCH 05/25] Fix lint --- light-client/src/operations/commit_validator.rs | 2 +- light-client/src/predicates/errors.rs | 2 +- light-client/src/supervisor.rs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/light-client/src/operations/commit_validator.rs b/light-client/src/operations/commit_validator.rs index 416357aa7..44dc728e7 100644 --- a/light-client/src/operations/commit_validator.rs +++ b/light-client/src/operations/commit_validator.rs @@ -96,7 +96,7 @@ impl CommitValidator for ProdCommitValidator { if validator_set.validator(*validator_address) == None { return Err(error::faulty_signer_error( - validator_address.clone(), + *validator_address, self.hasher.hash_validator_set(validator_set), )); } diff --git a/light-client/src/predicates/errors.rs b/light-client/src/predicates/errors.rs index f9b99c093..d726cdd39 100644 --- a/light-client/src/predicates/errors.rs +++ b/light-client/src/predicates/errors.rs @@ -163,7 +163,7 @@ define_error! { impl ErrorExt for VerificationErrorDetail { fn not_enough_trust(&self) -> Option { match &self { - Self::NotEnoughTrust(e) => Some(e.tally.clone()), + Self::NotEnoughTrust(e) => Some(e.tally), _ => None, } } diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index f825a4adc..ebfe70a66 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -390,7 +390,7 @@ impl Handle for SupervisorHandle { .send(HandleInput::LatestTrusted(sender)) .map_err(error::send_error)?; - Ok(receiver.recv().map_err(error::recv_error)?) + receiver.recv().map_err(error::recv_error) } fn latest_status(&self) -> Result { @@ -398,7 +398,7 @@ impl Handle for SupervisorHandle { self.sender .send(HandleInput::GetStatus(sender)) .map_err(error::send_error)?; - Ok(receiver.recv().map_err(error::recv_error)?) + receiver.recv().map_err(error::recv_error) } fn verify_to_highest(&self) -> Result { @@ -416,7 +416,7 @@ impl Handle for SupervisorHandle { .send(HandleInput::Terminate(sender)) .map_err(error::send_error)?; - Ok(receiver.recv().map_err(error::recv_error)?) + receiver.recv().map_err(error::recv_error) } } @@ -710,7 +710,7 @@ mod tests { None, ); - let peer_list = make_peer_list(Some(primary), Some(vec![witness.clone()]), get_time(11)); + let peer_list = make_peer_list(Some(primary), Some(vec![witness]), get_time(11)); let (result, _) = run_bisection_test(peer_list, 5); From 90a26cd82f2d1ba7768d87b4c99dabb1eb0b1795 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Thu, 1 Jul 2021 15:28:45 +0200 Subject: [PATCH 06/25] Use flex-error for builder and io errors --- light-client/src/builder/error.rs | 87 ++++++++++------------ light-client/src/builder/light_client.rs | 26 +++---- light-client/src/builder/supervisor.rs | 2 +- light-client/src/components/io.rs | 94 ++++++++++++++++-------- light-client/src/errors.rs | 12 +-- light-client/src/evidence.rs | 17 +++-- light-client/src/fork_detector.rs | 15 ++-- light-client/src/predicates/errors.rs | 5 +- light-client/src/supervisor.rs | 15 ++-- light-client/src/tests.rs | 4 +- light-client/src/utils/block_on.rs | 6 +- tendermint/src/error.rs | 8 +- 12 files changed, 155 insertions(+), 136 deletions(-) diff --git a/light-client/src/builder/error.rs b/light-client/src/builder/error.rs index a77a80885..66ee70fa9 100644 --- a/light-client/src/builder/error.rs +++ b/light-client/src/builder/error.rs @@ -1,58 +1,47 @@ //! Errors raised by the builder DSL -use anomaly::BoxError; -use anomaly::Context; +use flex_error::define_error; use tendermint::block::Height; use tendermint::Hash; -use thiserror::Error; use crate::components::io::IoError; +use crate::predicates::errors::VerificationError; + +define_error! { + Error { + Io + [ IoError ] + | _ | { "I/O error" }, + + HeightMismatch + { + given: Height, + found: Height, + } + | e | { + format_args!("height mismatch: given = {0}, found = {1}", + e.given, e.found) + }, + + HashMismatch + { + given: Hash, + found: Hash, + } + | e | { + format_args!("hash mismatch: given = {0}, found = {1}", + e.given, e.found) + }, + + InvalidLightBlock + [ VerificationError ] + | _ | { "invalid light block" }, + + NoTrustedStateInStore + | _ | { "no trusted state in store" }, + + EmptyWitnessList + | _ | { "empty witness list" }, -/// An error raised by the builder -pub type Error = anomaly::Error; - -/// The various error kinds raised by the builder -#[derive(Debug, Clone, Error, PartialEq)] -pub enum Kind { - /// I/O error - #[error("I/O error: {0}")] - Io(#[from] IoError), - - /// Height mismatch - #[error("height mismatch: given = {given}, found = {found}")] - HeightMismatch { - /// Height of trusted header - given: Height, - /// Height of fetched header - found: Height, - }, - - /// Hash mismatch - #[error("hash mismatch: given = {given}, found = {found}")] - HashMismatch { - /// Hash of trusted header - given: Hash, - /// hash of fetched header - found: Hash, - }, - - /// Invalid light block - #[error("invalid light block")] - InvalidLightBlock, - - /// No trusted state as found in the store - #[error("no trusted state in store")] - NoTrustedStateInStore, - - /// An empty witness list was given - #[error("empty witness list")] - EmptyWitnessList, -} - -impl Kind { - /// Add additional context (i.e. include a source error and capture a backtrace). - /// You can convert the resulting `Context` into an `Error` by calling `.into()`. - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) } } diff --git a/light-client/src/builder/light_client.rs b/light-client/src/builder/light_client.rs index eea8d5631..23ef5489f 100644 --- a/light-client/src/builder/light_client.rs +++ b/light-client/src/builder/light_client.rs @@ -2,7 +2,6 @@ use tendermint::{block::Height, Hash}; -use crate::bail; use crate::builder::error::{self, Error}; use crate::components::clock::Clock; use crate::components::io::{AtHeight, Io}; @@ -134,7 +133,7 @@ impl LightClientBuilder { let trusted_state = self .light_store .highest_trusted_or_verified() - .ok_or(error::Kind::NoTrustedStateInStore)?; + .ok_or_else(error::no_trusted_state_in_store_error)?; self.trust_light_block(trusted_state) } @@ -148,22 +147,19 @@ impl LightClientBuilder { let trusted_state = self .io .fetch_light_block(AtHeight::At(trusted_height)) - .map_err(error::Kind::Io)?; + .map_err(error::io_error)?; if trusted_state.height() != trusted_height { - bail!(error::Kind::HeightMismatch { - given: trusted_height, - found: trusted_state.height(), - }); + return Err(error::height_mismatch_error( + trusted_height, + trusted_state.height(), + )); } let header_hash = self.hasher.hash_header(&trusted_state.signed_header.header); if header_hash != trusted_hash { - bail!(error::Kind::HashMismatch { - given: trusted_hash, - found: header_hash, - }); + return Err(error::hash_mismatch_error(trusted_hash, header_hash)); } self.trust_light_block(trusted_state) @@ -175,19 +171,19 @@ impl LightClientBuilder { self.predicates .is_within_trust_period(header, self.options.trusting_period, now) - .map_err(|e| error::Kind::InvalidLightBlock.context(e))?; + .map_err(error::invalid_light_block_error)?; self.predicates .is_header_from_past(header, self.options.clock_drift, now) - .map_err(|e| error::Kind::InvalidLightBlock.context(e))?; + .map_err(error::invalid_light_block_error)?; self.predicates .validator_sets_match(light_block, &*self.hasher) - .map_err(|e| error::Kind::InvalidLightBlock.context(e))?; + .map_err(error::invalid_light_block_error)?; self.predicates .next_validators_match(light_block, &*self.hasher) - .map_err(|e| error::Kind::InvalidLightBlock.context(e))?; + .map_err(error::invalid_light_block_error)?; Ok(()) } diff --git a/light-client/src/builder/supervisor.rs b/light-client/src/builder/supervisor.rs index 0cb8ba647..abb36f2e1 100644 --- a/light-client/src/builder/supervisor.rs +++ b/light-client/src/builder/supervisor.rs @@ -95,7 +95,7 @@ impl SupervisorBuilder { ) -> Result, Error> { let mut iter = witnesses.into_iter().peekable(); if iter.peek().is_none() { - return Err(error::Kind::EmptyWitnessList.into()); + return Err(error::empty_witness_list_error()); } for (peer_id, address, instance) in iter { diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index e068edfa9..b2fdfb695 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -1,8 +1,7 @@ //! Provides an interface and a default implementation of the `Io` component -use serde::{Deserialize, Serialize}; +use flex_error::{define_error, DisplayOnly, TraceClone, TraceError}; use std::time::Duration; -use thiserror::Error; #[cfg(feature = "rpc-client")] use tendermint_rpc::Client; @@ -29,34 +28,68 @@ impl From for AtHeight { } } -/// I/O errors -#[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] -pub enum IoError { - /// Wrapper for a `tendermint::rpc::Error`. - #[error(transparent)] - RpcError(#[from] rpc::Error), +define_error! { + #[derive(Debug)] + IoError { + Rpc + [ TraceClone ] + | _ | { "rpc error" }, + + InvalidHeight + | _ | { + "invalid height: given height must be greater than 0" + }, + + InvalidValidatorSet + [ tendermint::Error ] + | _ | { "fetched validator set is invalid" }, + + Timeout + { duration: Duration } + [ DisplayOnly ] + | e | { + format_args!("task timed out after {} ms", + e.duration.as_millis()) + }, + + Runtime + [ TraceError ] + | _ | { "failed to initialize runtime" }, - /// Given height is invalid - #[error("invalid height: {0}")] - InvalidHeight(String), + } +} - /// Fetched validator set is invalid - #[error("fetched validator set is invalid: {0}")] - InvalidValidatorSet(String), +// /// I/O errors +// #[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] +// pub enum IoError { +// /// Wrapper for a `tendermint::rpc::Error`. +// #[error(transparent)] +// RpcError(#[from] rpc::Error), - /// Task timed out. - #[error("task timed out after {} ms", .0.as_millis())] - Timeout(Duration), +// /// Given height is invalid +// #[error("invalid height: {0}")] +// InvalidHeight(String), - /// Failed to initialize runtime - #[error("failed to initialize runtime")] - Runtime, -} +// /// Fetched validator set is invalid +// #[error("fetched validator set is invalid: {0}")] +// InvalidValidatorSet(String), + +// /// Task timed out. +// #[error("task timed out after {} ms", .0.as_millis())] +// Timeout(Duration), -impl IoError { +// /// Failed to initialize runtime +// #[error("failed to initialize runtime")] +// Runtime, +// } + +impl IoErrorDetail { /// Whether this error means that a timeout occured when querying a node. - pub fn is_timeout(&self) -> bool { - matches!(self, Self::Timeout(_)) + pub fn is_timeout(&self) -> Option { + match self { + Self::Timeout(e) => Some(e.duration), + _ => None, + } } } @@ -84,7 +117,6 @@ mod prod { use std::time::Duration; - use crate::bail; use crate::types::PeerId; use crate::utils::block_on; @@ -150,7 +182,7 @@ mod prod { match res { Ok(response) => Ok(response.signed_header), - Err(err) => Err(IoError::RpcError(err)), + Err(err) => Err(rpc_error(err)), } } @@ -160,9 +192,9 @@ mod prod { proposer_address: Option, ) -> Result { let height = match height { - AtHeight::Highest => bail!(IoError::InvalidHeight( - "given height must be greater than 0".to_string() - )), + AtHeight::Highest => { + return Err(invalid_height_error()); + } AtHeight::At(height) => height, }; @@ -170,12 +202,12 @@ mod prod { let response = block_on(self.timeout, async move { client.validators(height, Paging::All).await })? - .map_err(IoError::RpcError)?; + .map_err(rpc_error)?; let validator_set = match proposer_address { Some(proposer_address) => { TMValidatorSet::with_proposer(response.validators, proposer_address) - .map_err(|e| IoError::InvalidValidatorSet(e.to_string()))? + .map_err(invalid_validator_set_error)? } None => TMValidatorSet::without_proposer(response.validators), }; diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index fd292d3a9..714f450bb 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -1,10 +1,10 @@ //! Toplevel errors raised by the light client. use std::fmt::Debug; +use std::time::Duration; use crate::operations::voting_power::VotingPowerTally; use crossbeam_channel as crossbeam; -use serde::{Deserialize, Serialize}; use crate::{ components::io::IoError, @@ -15,10 +15,10 @@ use crate::{ use flex_error::{define_error, DisplayError, TraceError}; define_error! { - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] + #[derive(Debug)] Error { Io - [ DisplayError ] + [ IoError ] | _ | { "io error" }, Store @@ -127,7 +127,7 @@ pub trait ErrorExt { /// Whether this error means that a timeout occured when /// querying a node. - fn is_timeout(&self) -> bool; + fn is_timeout(&self) -> Option; } impl ErrorExt for ErrorDetail { @@ -148,11 +148,11 @@ impl ErrorExt for ErrorDetail { } /// Whether this error means that a timeout occured when querying a node. - fn is_timeout(&self) -> bool { + fn is_timeout(&self) -> Option { if let Self::Io(e) = self { e.source.is_timeout() } else { - false + None } } } diff --git a/light-client/src/evidence.rs b/light-client/src/evidence.rs index 2bfd7c411..ad3b74ffd 100644 --- a/light-client/src/evidence.rs +++ b/light-client/src/evidence.rs @@ -1,6 +1,9 @@ //! Fork evidence data structures and interfaces. -use crate::{components::io::IoError, types::PeerId}; +use crate::{ + components::io::{self, IoError}, + types::PeerId, +}; use tendermint::abci::transaction::Hash; @@ -44,15 +47,13 @@ mod prod { fn report(&self, e: Evidence, peer: PeerId) -> Result { let client = self.rpc_client_for(peer)?; - let res = block_on( + let response = block_on( self.timeout, async move { client.broadcast_evidence(e).await }, - )?; + )? + .map_err(io::rpc_error)?; - match res { - Ok(response) => Ok(response.hash), - Err(err) => Err(IoError::RpcError(err)), - } + Ok(response.hash) } } @@ -70,7 +71,7 @@ mod prod { #[pre(self.peer_map.contains_key(&peer))] fn rpc_client_for(&self, peer: PeerId) -> Result { let peer_addr = self.peer_map.get(&peer).unwrap().to_owned(); - Ok(rpc::HttpClient::new(peer_addr).map_err(IoError::from)?) + Ok(rpc::HttpClient::new(peer_addr).map_err(io::rpc_error)?) } } } diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index 0f8c71fce..ed3b83251 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -1,7 +1,5 @@ //! Fork detection data structures and implementation. -use serde::{Deserialize, Serialize}; - use crate::{ errors::{Error, ErrorDetail, ErrorExt}, operations::{Hasher, ProdHasher}, @@ -12,7 +10,7 @@ use crate::{ }; /// Result of fork detection -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Debug)] pub enum ForkDetection { /// One or more forks have been detected Detected(Vec), @@ -21,7 +19,7 @@ pub enum ForkDetection { } /// Types of fork -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Debug)] pub enum Fork { /// An actual fork was found for this `LightBlock` Forked { @@ -128,10 +126,13 @@ impl ForkDetector for ProdForkDetector { witness: witness_block, }); } - Err(e) if e.detail.is_timeout() => { - forks.push(Fork::Timeout(witness_block.provider, e.detail.clone())) + Err(e) => { + if e.detail.is_timeout().is_some() { + forks.push(Fork::Timeout(witness_block.provider, e.detail)) + } else { + forks.push(Fork::Faulty(witness_block, e.detail)) + } } - Err(e) => forks.push(Fork::Faulty(witness_block, e.detail.clone())), } } diff --git a/light-client/src/predicates/errors.rs b/light-client/src/predicates/errors.rs index d726cdd39..9ab9112fa 100644 --- a/light-client/src/predicates/errors.rs +++ b/light-client/src/predicates/errors.rs @@ -2,6 +2,7 @@ use flex_error::define_error; use serde::{Deserialize, Serialize}; +use std::time::Duration; use crate::errors::ErrorExt; use crate::operations::voting_power::VotingPowerTally; @@ -172,7 +173,7 @@ impl ErrorExt for VerificationErrorDetail { matches!(self, Self::NotWithinTrustPeriod { .. }) } - fn is_timeout(&self) -> bool { - false + fn is_timeout(&self) -> Option { + None } } diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index ebfe70a66..4798563fa 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -423,12 +423,11 @@ impl Handle for SupervisorHandle { #[cfg(test)] mod tests { use super::*; - use crate::components::io::IoError; use crate::light_client::Options; use crate::operations::ProdHasher; use crate::{ components::{ - io::{AtHeight, Io}, + io::{self, AtHeight, Io}, scheduler, verifier::ProdVerifier, }, @@ -650,12 +649,12 @@ mod tests { Err(ErrorReport { detail: error::ErrorDetail::Io(e), trace: _, - }) => { - assert_eq!( - e.source, - IoError::RpcError(rpc::Error::new(Code::InvalidRequest, None,)) - ); - } + }) => match e.source { + io::IoErrorDetail::Rpc(e) => { + assert_eq!(e.source, rpc::Error::new(Code::InvalidRequest, None)) + } + _ => panic!("expected Rpc error"), + }, _ => panic!("expected NoWitnesses error"), } } diff --git a/light-client/src/tests.rs b/light-client/src/tests.rs index 9cf6e30ca..1d8b740ae 100644 --- a/light-client/src/tests.rs +++ b/light-client/src/tests.rs @@ -7,7 +7,7 @@ use tendermint::abci::transaction::Hash; use tendermint_rpc as rpc; use crate::components::clock::Clock; -use crate::components::io::{AtHeight, Io, IoError}; +use crate::components::io::{self, AtHeight, Io, IoError}; use crate::components::verifier::{ProdVerifier, Verdict, Verifier}; use crate::errors::Error; use crate::evidence::EvidenceReporter; @@ -116,7 +116,7 @@ impl Io for MockIo { self.light_blocks .get(&height) .cloned() - .ok_or_else(|| rpc::Error::new((-32600).into(), None).into()) + .ok_or_else(|| io::rpc_error(rpc::Error::new((-32600).into(), None))) } } diff --git a/light-client/src/utils/block_on.rs b/light-client/src/utils/block_on.rs index 77a7f7fc9..b466830f1 100644 --- a/light-client/src/utils/block_on.rs +++ b/light-client/src/utils/block_on.rs @@ -1,6 +1,6 @@ use std::{future::Future, time::Duration}; -use crate::components::io::IoError; +use crate::components::io::{self, IoError}; /// Run a future to completion on a new thread, with the given timeout. /// @@ -14,11 +14,11 @@ where let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() - .map_err(|_| IoError::Runtime)?; + .map_err(io::runtime_error)?; if let Some(timeout) = timeout { let task = async { tokio::time::timeout(timeout, f).await }; - rt.block_on(task).map_err(|_| IoError::Timeout(timeout)) + rt.block_on(task).map_err(|e| io::timeout_error(timeout, e)) } else { Ok(rt.block_on(f)) } diff --git a/tendermint/src/error.rs b/tendermint/src/error.rs index 4e7678198..a062e41f4 100644 --- a/tendermint/src/error.rs +++ b/tendermint/src/error.rs @@ -4,7 +4,7 @@ use crate::account; use crate::vote; use alloc::string::String; use core::num::TryFromIntError; -use flex_error::{define_error, DisplayError}; +use flex_error::{define_error, DisplayError, DisplayOnly}; use std::io::Error as IoError; use time::OutOfRangeError; @@ -19,12 +19,12 @@ define_error! { |e| { format_args!("invalid key: {}", e) }, Io - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("I/O error") }, FileIo { path: String } - [ DisplayError ] + [ DisplayOnly ] |e| { format_args!("failed to open file: {}", e.path) }, Length @@ -184,7 +184,7 @@ define_error! { |_| { format_args!("subtle encoding error") }, SerdeJson - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("serde json error") }, Toml From c4a41dfa53470e0fcecb0cca1c1814f06520f3e7 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Thu, 1 Jul 2021 21:56:11 +0200 Subject: [PATCH 07/25] Use flex-error for rpc errors --- light-client/Cargo.toml | 2 - light-client/examples/light_client.rs | 5 +- light-client/src/components/io.rs | 4 +- light-client/src/supervisor.rs | 15 +- light-client/src/tests.rs | 9 +- rpc/Cargo.toml | 1 + rpc/src/client.rs | 7 +- rpc/src/client/sync.rs | 9 +- rpc/src/client/transport/http.rs | 41 ++- rpc/src/client/transport/mock.rs | 28 +- rpc/src/endpoint/consensus_state.rs | 36 +- rpc/src/error.rs | 453 ++++++++++---------------- rpc/src/lib.rs | 2 + rpc/src/method.rs | 4 +- rpc/src/order.rs | 4 +- rpc/src/paging.rs | 14 +- rpc/src/query.rs | 7 +- rpc/src/response.rs | 19 +- rpc/src/response_error.rs | 207 ++++++++++++ rpc/src/rpc_url.rs | 24 +- rpc/src/version.rs | 10 +- rpc/tests/kvstore_fixtures.rs | 78 +++-- rpc/tests/parse_response.rs | 26 +- 23 files changed, 561 insertions(+), 444 deletions(-) create mode 100644 rpc/src/response_error.rs diff --git a/light-client/Cargo.toml b/light-client/Cargo.toml index 1f8190b01..8c4fc0558 100644 --- a/light-client/Cargo.toml +++ b/light-client/Cargo.toml @@ -40,7 +40,6 @@ unstable = [] tendermint = { version = "0.20.0", path = "../tendermint" } tendermint-rpc = { version = "0.20.0", path = "../rpc", default-features = false } -anomaly = { version = "0.2.0", features = ["serializer"] } contracts = "0.4.0" crossbeam-channel = "0.4.2" derive_more = "0.99.5" @@ -50,7 +49,6 @@ serde_cbor = "0.11.1" serde_derive = "1.0.106" sled = { version = "0.34.3", optional = true } static_assertions = "1.1.0" -thiserror = "1.0.15" tokio = { version = "1.0", features = ["rt"], optional = true } flex-error = "0.2.1" diff --git a/light-client/examples/light_client.rs b/light-client/examples/light_client.rs index 9c3b7ce36..f2c1a3824 100644 --- a/light-client/examples/light_client.rs +++ b/light-client/examples/light_client.rs @@ -3,7 +3,6 @@ use std::{ time::Duration, }; -use anomaly::BoxError; use gumdrop::Options; use tendermint::Hash; @@ -84,7 +83,7 @@ fn make_instance( addr: tendermint_rpc::Url, db_path: impl AsRef, opts: &SyncOpts, -) -> Result { +) -> Result> { let light_store = SledStore::open(db_path)?; let rpc_client = rpc::HttpClient::new(addr).unwrap(); let options = light_client::Options { @@ -105,7 +104,7 @@ fn make_instance( Ok(builder.build()) } -fn sync_cmd(opts: SyncOpts) -> Result<(), BoxError> { +fn sync_cmd(opts: SyncOpts) -> Result<(), Box> { let primary: PeerId = "BADFADAD0BEFEEDC0C0ADEADBEEFC0FFEEFACADE".parse().unwrap(); let witness: PeerId = "CEFEEDBADFADAD0C0CEEFACADE0ADEADBEEFC0FF".parse().unwrap(); diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index b2fdfb695..9fe0d94ba 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -1,6 +1,6 @@ //! Provides an interface and a default implementation of the `Io` component -use flex_error::{define_error, DisplayOnly, TraceClone, TraceError}; +use flex_error::{define_error, DisplayOnly, TraceError}; use std::time::Duration; #[cfg(feature = "rpc-client")] @@ -32,7 +32,7 @@ define_error! { #[derive(Debug)] IoError { Rpc - [ TraceClone ] + [ rpc::Error ] | _ | { "rpc error" }, InvalidHeight diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 4798563fa..3da3f7818 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -441,8 +441,10 @@ mod tests { use tendermint::block::Height; use tendermint::evidence::Duration as DurationStr; use tendermint::trust_threshold::TrustThresholdFraction; - use tendermint_rpc as rpc; - use tendermint_rpc::error::Code; + use tendermint_rpc::{ + self as rpc, + response_error::{Code, ResponseError}, + }; use tendermint_testgen::helpers::get_time; use tendermint_testgen::{ Commit, Generator, Header, LightBlock as TestgenLightBlock, LightChain, ValidatorSet, @@ -650,9 +652,12 @@ mod tests { detail: error::ErrorDetail::Io(e), trace: _, }) => match e.source { - io::IoErrorDetail::Rpc(e) => { - assert_eq!(e.source, rpc::Error::new(Code::InvalidRequest, None)) - } + io::IoErrorDetail::Rpc(e) => match e.source { + rpc::error::ErrorDetail::Response(e) => { + assert_eq!(e.source, ResponseError::new(Code::InvalidRequest, None)) + } + _ => todo!(), + }, _ => panic!("expected Rpc error"), }, _ => panic!("expected NoWitnesses error"), diff --git a/light-client/src/tests.rs b/light-client/src/tests.rs index 1d8b740ae..138b150ec 100644 --- a/light-client/src/tests.rs +++ b/light-client/src/tests.rs @@ -113,10 +113,11 @@ impl Io for MockIo { AtHeight::At(height) => height, }; - self.light_blocks - .get(&height) - .cloned() - .ok_or_else(|| io::rpc_error(rpc::Error::new((-32600).into(), None))) + self.light_blocks.get(&height).cloned().ok_or_else(|| { + io::rpc_error(rpc::error::response_error( + rpc::response_error::ResponseError::new((-32600).into(), None), + )) + }) } } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 6960cd1a8..7b5fccaad 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -77,6 +77,7 @@ uuid = { version = "0.8", default-features = false } subtle-encoding = { version = "0.5", features = ["bech32-preview"] } url = "2.2" walkdir = "2.3" +flex-error = "0.2.1" async-trait = { version = "0.1", optional = true } async-tungstenite = { version = "0.12", features = ["tokio-runtime", "tokio-rustls"], optional = true } diff --git a/rpc/src/client.rs b/rpc/src/client.rs index 1e61980ae..5a1ef4da9 100644 --- a/rpc/src/client.rs +++ b/rpc/src/client.rs @@ -14,7 +14,7 @@ pub use transport::websocket::{WebSocketClient, WebSocketClientDriver, WebSocket use crate::endpoint::validators::DEFAULT_VALIDATORS_PER_PAGE; use crate::endpoint::*; -use crate::error::Error; +use crate::error; use crate::paging::Paging; use crate::query::Query; use crate::{Order, Result, SimpleRequest}; @@ -241,10 +241,7 @@ pub trait Client { while self.health().await.is_err() { if attempts_remaining == 0 { - return Err(Error::client_internal_error(format!( - "timed out waiting for healthy response after {}ms", - timeout.as_millis() - ))); + return Err(error::timeout_error(timeout)); } attempts_remaining -= 1; diff --git a/rpc/src/client/sync.rs b/rpc/src/client/sync.rs index 47900a32d..556cdd89b 100644 --- a/rpc/src/client/sync.rs +++ b/rpc/src/client/sync.rs @@ -11,7 +11,7 @@ use futures::Stream; use pin_project::pin_project; use tokio::sync::mpsc; -use crate::{Error, Result}; +use crate::{error, Result}; /// Constructor for an unbounded channel. pub fn unbounded() -> (ChannelTx, ChannelRx) { @@ -28,12 +28,7 @@ pub struct ChannelTx(mpsc::UnboundedSender); impl ChannelTx { pub fn send(&self, value: T) -> Result<()> { - self.0.send(value).map_err(|e| { - Error::client_internal_error(format!( - "failed to send message to internal channel: {}", - e - )) - }) + self.0.send(value).map_err(error::send_error) } } diff --git a/rpc/src/client/transport/http.rs b/rpc/src/client/transport/http.rs index 4513b3b0b..c6ac8d128 100644 --- a/rpc/src/client/transport/http.rs +++ b/rpc/src/client/transport/http.rs @@ -1,7 +1,7 @@ //! HTTP-based transport for Tendermint RPC Client. use crate::client::Client; -use crate::{Error, Result, Scheme, SimpleRequest, Url}; +use crate::{error, Error, Result, Scheme, SimpleRequest, Url}; use async_trait::async_trait; use std::convert::{TryFrom, TryInto}; use std::str::FromStr; @@ -101,10 +101,7 @@ impl TryFrom for HttpClientUrl { fn try_from(value: Url) -> Result { match value.scheme() { Scheme::Http | Scheme::Https => Ok(Self(value)), - _ => Err(Error::invalid_params(&format!( - "cannot use URL {} with HTTP clients", - value - ))), + _ => Err(error::invalid_url_error(value)), } } } @@ -136,9 +133,7 @@ impl TryFrom for HttpClientUrl { host, port, } => format!("http://{}:{}", host, port).parse(), - net::Address::Unix { .. } => Err(Error::invalid_params( - "only TCP-based node addresses are supported", - )), + net::Address::Unix { .. } => Err(error::invalid_network_address_error()), } } } @@ -153,12 +148,16 @@ impl TryFrom for hyper::Uri { type Error = Error; fn try_from(value: HttpClientUrl) -> Result { - Ok(value.0.to_string().parse()?) + value + .0 + .to_string() + .parse() + .map_err(error::invalid_uri_error) } } mod sealed { - use crate::{Error, Response, Result, SimpleRequest}; + use crate::{error, Response, Result, SimpleRequest}; use hyper::body::Buf; use hyper::client::connect::Connect; use hyper::client::HttpConnector; @@ -189,7 +188,11 @@ mod sealed { R: SimpleRequest, { let request = self.build_request(request)?; - let response = self.inner.request(request).await?; + let response = self + .inner + .request(request) + .await + .map_err(error::hyper_error)?; let response_body = response_to_string(response).await?; tracing::debug!("Incoming response: {}", response_body); R::Response::from_string(&response_body) @@ -207,7 +210,8 @@ mod sealed { let mut request = hyper::Request::builder() .method("POST") .uri(&self.uri) - .body(hyper::Body::from(request_body.into_bytes()))?; + .body(hyper::Body::from(request_body.into_bytes())) + .map_err(error::http_error)?; { let headers = request.headers_mut(); @@ -251,7 +255,8 @@ mod sealed { pub fn new_http_proxy(uri: Uri, proxy_uri: Uri) -> Result { let proxy = Proxy::new(Intercept::All, proxy_uri); - let proxy_connector = ProxyConnector::from_proxy(HttpConnector::new(), proxy)?; + let proxy_connector = + ProxyConnector::from_proxy(HttpConnector::new(), proxy).map_err(error::io_error)?; Ok(Self::HttpProxy(HyperClient::new( uri, hyper::Client::builder().build(proxy_connector), @@ -261,7 +266,9 @@ mod sealed { pub fn new_https_proxy(uri: Uri, proxy_uri: Uri) -> Result { let proxy = Proxy::new(Intercept::All, proxy_uri); let proxy_connector = - ProxyConnector::from_proxy(HttpsConnector::with_native_roots(), proxy)?; + ProxyConnector::from_proxy(HttpsConnector::with_native_roots(), proxy) + .map_err(error::io_error)?; + Ok(Self::HttpsProxy(HyperClient::new( uri, hyper::Client::builder().build(proxy_connector), @@ -284,10 +291,12 @@ mod sealed { async fn response_to_string(response: hyper::Response) -> Result { let mut response_body = String::new(); hyper::body::aggregate(response.into_body()) - .await? + .await + .map_err(error::hyper_error)? .reader() .read_to_string(&mut response_body) - .map_err(|_| Error::client_internal_error("failed to read response body to string"))?; + .map_err(error::io_error)?; + Ok(response_body) } } diff --git a/rpc/src/client/transport/mock.rs b/rpc/src/client/transport/mock.rs index 9bd46fc6c..c8ec4015f 100644 --- a/rpc/src/client/transport/mock.rs +++ b/rpc/src/client/transport/mock.rs @@ -6,7 +6,7 @@ use crate::client::transport::router::SubscriptionRouter; use crate::event::Event; use crate::query::Query; use crate::utils::uuid_str; -use crate::{Client, Error, Method, Request, Response, Result, Subscription, SubscriptionClient}; +use crate::{error, Client, Method, Request, Response, Result, Subscription, SubscriptionClient}; use async_trait::async_trait; use std::collections::HashMap; @@ -58,9 +58,9 @@ impl Client for MockClient { where R: Request, { - self.matcher.response_for(request).ok_or_else(|| { - Error::client_internal_error("no matching response for incoming request") - })? + self.matcher + .response_for(request) + .ok_or_else(error::mismatch_response_error)? } } @@ -199,9 +199,8 @@ pub trait MockRequestMatcher: Send + Sync { /// requests with specific methods to responses. /// /// [`MockRequestMatcher`]: trait.MockRequestMatcher.html -#[derive(Debug)] pub struct MockRequestMethodMatcher { - mappings: HashMap>, + mappings: HashMap Result + Send + Sync>>, } impl MockRequestMatcher for MockRequestMethodMatcher { @@ -209,9 +208,9 @@ impl MockRequestMatcher for MockRequestMethodMatcher { where R: Request, { - self.mappings.get(&request.method()).map(|res| match res { + self.mappings.get(&request.method()).map(|res| match res() { Ok(json) => R::Response::from_string(json), - Err(e) => Err(e.clone()), + Err(e) => Err(e), }) } } @@ -230,8 +229,12 @@ impl MockRequestMethodMatcher { /// /// Successful responses must be JSON-encoded. #[allow(dead_code)] - pub fn map(mut self, method: Method, response: Result) -> Self { - self.mappings.insert(method, response); + pub fn map( + mut self, + method: Method, + response: impl Fn() -> Result + Send + Sync + 'static, + ) -> Self { + self.mappings.insert(method, Box::new(response)); self } } @@ -260,9 +263,10 @@ mod test { async fn mock_client() { let abci_info_fixture = read_json_fixture("abci_info").await; let block_fixture = read_json_fixture("block").await; + let matcher = MockRequestMethodMatcher::default() - .map(Method::AbciInfo, Ok(abci_info_fixture)) - .map(Method::Block, Ok(block_fixture)); + .map(Method::AbciInfo, move || Ok(abci_info_fixture.clone())) + .map(Method::Block, move || Ok(block_fixture.clone())); let (client, driver) = MockClient::new(matcher); let driver_hdl = tokio::spawn(async move { driver.run().await }); diff --git a/rpc/src/endpoint/consensus_state.rs b/rpc/src/endpoint/consensus_state.rs index 663393e30..f71b48036 100644 --- a/rpc/src/endpoint/consensus_state.rs +++ b/rpc/src/endpoint/consensus_state.rs @@ -1,6 +1,6 @@ //! `/consensus_state` endpoint JSON-RPC wrapper -use crate::{Error, Method}; +use crate::{error, Error, Method}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; use std::str::FromStr; @@ -181,92 +181,92 @@ impl FromStr for VoteSummary { let parts: Vec<&str> = s .strip_prefix("Vote{") .ok_or_else(|| { - Self::Err::client_internal_error( - "invalid format for consensus state vote summary string", + error::client_internal_error( + "invalid format for consensus state vote summary string".to_string(), ) })? .strip_suffix('}') .ok_or_else(|| { - Self::Err::client_internal_error( - "invalid format for consensus state vote summary string", + error::client_internal_error( + "invalid format for consensus state vote summary string".to_string(), ) })? .split(' ') .collect(); if parts.len() != 6 { - return Err(Self::Err::client_internal_error(format!( + return Err(error::client_internal_error(format!( "expected 6 parts to a consensus state vote summary, but got {}", parts.len() ))); } let validator: Vec<&str> = parts[0].split(':').collect(); if validator.len() != 2 { - return Err(Self::Err::client_internal_error(format!( + return Err(error::client_internal_error(format!( "failed to parse validator info for consensus state vote summary: {}", parts[0], ))); } let height_round_type: Vec<&str> = parts[1].split('/').collect(); if height_round_type.len() != 3 { - return Err(Self::Err::client_internal_error(format!( + return Err(error::client_internal_error(format!( "failed to parse height/round/type for consensus state vote summary: {}", parts[1] ))); } let validator_index = i32::from_str(validator[0]).map_err(|e| { - Self::Err::client_internal_error(format!( + error::client_internal_error(format!( "failed to parse validator index from consensus state vote summary: {} ({})", e, validator[0], )) })?; let validator_address_fingerprint = Fingerprint::from_str(validator[1]).map_err(|e| { - Self::Err::client_internal_error(format!( + error::client_internal_error(format!( "failed to parse validator address fingerprint from consensus state vote summary: {}", e )) })?; let height = Height::from_str(height_round_type[0]).map_err(|e| { - Self::Err::client_internal_error(format!( + error::client_internal_error(format!( "failed to parse height from consensus state vote summary: {}", e )) })?; let round = Round::from_str(height_round_type[1]).map_err(|e| { - Self::Err::client_internal_error(format!( + error::client_internal_error(format!( "failed to parse round from consensus state vote summary: {}", e )) })?; let vote_type_parts: Vec<&str> = height_round_type[2].split('(').collect(); if vote_type_parts.len() != 2 { - return Err(Self::Err::client_internal_error(format!( + return Err(error::client_internal_error(format!( "invalid structure for vote type in consensus state vote summary: {}", height_round_type[2] ))); } let vote_type_str = vote_type_parts[1].trim_end_matches(')'); let vote_type = vote::Type::from_str(vote_type_str).map_err(|e| { - Self::Err::client_internal_error(format!( + error::client_internal_error(format!( "failed to parse vote type from consensus state vote summary: {} ({})", e, vote_type_str )) })?; let block_id_hash_fingerprint = Fingerprint::from_str(parts[2]).map_err(|e| { - Self::Err::client_internal_error(format!( + error::client_internal_error(format!( "failed to parse block ID hash fingerprint from consensus state vote summary: {}", e )) })?; let signature_fingerprint = Fingerprint::from_str(parts[3]).map_err(|e| { - Self::Err::client_internal_error(format!( + error::client_internal_error(format!( "failed to parse signature fingerprint from consensus state vote summary: {}", e )) })?; let timestamp = Time::parse_from_rfc3339(parts[5]).map_err(|e| { - Self::Err::client_internal_error(format!( + error::client_internal_error(format!( "failed to parse timestamp from consensus state vote summary: {}", e )) @@ -311,7 +311,7 @@ impl FromStr for Fingerprint { fn from_str(s: &str) -> Result { Ok(Self(hex::decode_upper(s).map_err(|e| { - Self::Err::client_internal_error(format!( + error::client_internal_error(format!( "failed to parse fingerprint as an uppercase hexadecimal string: {}", e )) diff --git a/rpc/src/error.rs b/rpc/src/error.rs index 7a9596ac4..bfa658d08 100644 --- a/rpc/src/error.rs +++ b/rpc/src/error.rs @@ -1,290 +1,169 @@ //! JSON-RPC error types -#[cfg(feature = "websocket-client")] -use async_tungstenite::tungstenite::Error as WSError; - -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::fmt::{self, Display}; -use thiserror::Error; - -// TODO(thane): Differentiate between RPC response errors and internal crate -// errors (e.g. domain type-related errors). -/// Tendermint RPC errors -#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] -pub struct Error { - /// Error code - code: Code, - - /// Error message - message: String, - - /// Additional data about the error - data: Option, -} -impl std::error::Error for Error {} - -impl Error { - /// Create a new RPC error - pub fn new(code: Code, data: Option) -> Error { - let message = code.to_string(); - - Error { - code, - message, - data, - } - } - - /// Create a low-level HTTP error - pub fn http_error(message: impl Into) -> Error { - Error { - code: Code::HttpError, - message: message.into(), - data: None, - } - } - - /// Create a new invalid parameter error - pub fn invalid_params(data: &str) -> Error { - Error::new(Code::InvalidParams, Some(data.to_string())) - } - - /// Create a new websocket error - pub fn websocket_error(cause: impl Into) -> Error { - Error::new(Code::WebSocketError, Some(cause.into())) - } - - /// Create a new method-not-found error - pub fn method_not_found(name: &str) -> Error { - Error::new(Code::MethodNotFound, Some(name.to_string())) - } - - /// Create a new parse error - pub fn parse_error(error: E) -> Error - where - E: Display, - { - Error::new(Code::ParseError, Some(error.to_string())) - } - - /// Create a new server error - pub fn server_error(data: D) -> Error - where - D: Display, - { - Error::new(Code::ServerError, Some(data.to_string())) - } - - /// An internal error occurred within the client. - pub fn client_internal_error(cause: impl Into) -> Error { - Error::new(Code::ClientInternalError, Some(cause.into())) - } - - /// Obtain the `rpc::error::Code` for this error - pub fn code(&self) -> Code { - self.code - } - - /// Borrow the error message (if available) - pub fn message(&self) -> &str { - &self.message - } - - /// Optional additional error message (if available) - pub fn data(&self) -> Option<&str> { - self.data.as_ref().map(AsRef::as_ref) - } -} - -impl Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &self.data { - Some(data) => write!( - f, - "{}: {} (code: {})", - self.message, - data, - self.code.value() - ), - None => write!(f, "{} (code: {})", self.message, self.code.value()), - } - } -} - -impl From for Error { - fn from(e: std::io::Error) -> Self { - Error::client_internal_error(e.to_string()) - } -} - -impl From for Error { - fn from(e: url::ParseError) -> Self { - Error::invalid_params(&e.to_string()) - } -} - -#[cfg(feature = "http-client")] -impl From for Error { - fn from(http_error: http::Error) -> Error { - Error::http_error(http_error.to_string()) - } -} - -#[cfg(feature = "http-client")] -impl From for Error { - fn from(hyper_error: hyper::Error) -> Error { - Error::http_error(hyper_error.to_string()) - } -} - -#[cfg(feature = "http-client")] -impl From for Error { - fn from(e: http::uri::InvalidUri) -> Self { - Error::http_error(e.to_string()) - } -} - -#[cfg(feature = "websocket-client")] -impl From for Error { - fn from(websocket_error: WSError) -> Error { - Error::websocket_error(websocket_error.to_string()) - } -} - -#[cfg(feature = "cli")] -impl From for Error { - fn from(e: serde_json::Error) -> Self { - Error::client_internal_error(e.to_string()) - } -} - -#[cfg(feature = "cli")] -impl From for Error { - fn from(e: tendermint::Error) -> Self { - Error::client_internal_error(e.to_string()) - } -} - -/// Tendermint RPC error codes. -/// -/// See `func RPC*Error()` definitions in: -/// -#[derive(Copy, Clone, Debug, Eq, Error, Hash, PartialEq, PartialOrd, Ord)] -pub enum Code { - /// Low-level HTTP error - #[error("HTTP error")] - HttpError, - - /// Low-level WebSocket error - #[error("WebSocket Error")] - WebSocketError, - - /// An internal error occurred within the client. - /// - /// This is an error unique to this client, and is not available in the - /// [Go client]. - /// - /// [Go client]: https://github.com/tendermint/tendermint/tree/master/rpc/jsonrpc/client - #[error("Client internal error")] - ClientInternalError, - - /// Parse error i.e. invalid JSON (-32700) - #[error("Parse error. Invalid JSON")] - ParseError, - - /// Invalid request (-32600) - #[error("Invalid Request")] - InvalidRequest, - - /// Method not found error (-32601) - #[error("Method not found")] - MethodNotFound, - - /// Invalid parameters (-32602) - #[error("Invalid params")] - InvalidParams, - - /// Internal RPC server error (-32603) - #[error("Internal error")] - InternalError, - - /// Server error (-32000) - #[error("Server error")] - ServerError, - - /// Other error types - #[error("Error (code: {})", 0)] - Other(i32), -} - -impl Code { - /// Get the integer error value for this code - pub fn value(self) -> i32 { - i32::from(self) - } -} - -impl From for Code { - fn from(value: i32) -> Code { - match value { - 0 => Code::HttpError, - 1 => Code::WebSocketError, - 2 => Code::ClientInternalError, - -32700 => Code::ParseError, - -32600 => Code::InvalidRequest, - -32601 => Code::MethodNotFound, - -32602 => Code::InvalidParams, - -32603 => Code::InternalError, - -32000 => Code::ServerError, - other => Code::Other(other), - } - } -} - -impl From for i32 { - fn from(code: Code) -> i32 { - match code { - Code::HttpError => 0, - Code::WebSocketError => 1, - Code::ClientInternalError => 2, - Code::ParseError => -32700, - Code::InvalidRequest => -32600, - Code::MethodNotFound => -32601, - Code::InvalidParams => -32602, - Code::InternalError => -32603, - Code::ServerError => -32000, - Code::Other(other) => other, - } - } -} - -impl<'de> Deserialize<'de> for Code { - fn deserialize>(deserializer: D) -> Result { - Ok(Code::from(i32::deserialize(deserializer)?)) - } -} - -impl Serialize for Code { - fn serialize(&self, serializer: S) -> Result { - self.value().serialize(serializer) - } -} - -#[cfg(test)] -mod tests { - use super::Code; - use super::Error; - - #[test] - fn test_serialize() { - let expect = - "{\"code\":-32700,\"message\":\"Parse error. Invalid JSON\",\"data\":\"hello world\"}"; - let pe = Error::parse_error("hello world"); - let pe_json = serde_json::to_string(&pe).expect("could not write JSON"); - assert_eq!(pe_json, expect); - let res: Error = serde_json::from_str(expect).expect("could not read JSON"); - assert_eq!(res.code, Code::ParseError); - assert_eq!(res.code.value(), -32700); - assert_eq!(res.data, Some("hello world".to_string())); - } +// #[cfg(feature = "websocket-client")] +// use async_tungstenite::tungstenite::Error as WSError; + +// use serde::{Deserialize, Deserializer, Serialize, Serializer}; +// use std::fmt::{self, Display}; +// use thiserror::Error; + +use flex_error::{define_error, DisplayError, DisplayOnly}; +use http::uri::InvalidUri; +use http::Error as HttpError; +use hyper::Error as HyperError; +use std::time::Duration; +use tokio::sync::mpsc::error::SendError; + +use crate::response_error::ResponseError; +use crate::rpc_url::Url; + +define_error! { + Error { + Response + [ DisplayError ] + | _ | { "response error" }, + + Io + [ DisplayOnly ] + | _ | { "I/O error" }, + + Http + [ DisplayOnly ] + | _ | { "HTTP error" }, + + Hyper + [ DisplayOnly ] + | _ | { "HTTP error" }, + + InvalidParams + { + message: String + } + | e | { + format_args!("invalid params error: {}", e.message) + }, + + WebSocket + { + message: String + } + | e | { + format_args!("web socket error: {}", e.message) + }, + + MethodNotFound + { + method: String + } + | e | { + format_args!("method not found: {}", e.method) + }, + + Parse + { + reason: String + } + | e | { + format_args!("parse error: {}", e.reason) + }, + + Server + { + reason: String + } + | e | { + format_args!("server error: {}", e.reason) + }, + + ClientInternal + { + reason: String + } + | e | { + format_args!("client internal error: {}", e.reason) + }, + + Timeout + { + duration: Duration + } + | e | { + format_args!( + "timed out waiting for healthy response after {}ms", + e.duration.as_millis() + ) + }, + + ChannelSend + | _ | { "failed to send message to internal channel" }, + + InvalidUrl + { url: Url } + | e | { + format_args!( + "cannot use URL {} with HTTP clients", + e.url + ) + }, + + InvalidUri + [ DisplayOnly ] + | _ | { "invalid URI" }, + + ParseInt + [ DisplayOnly ] + | _ | { "error parsing integer" }, + + OutOfRange + [ DisplayOnly ] + | _ | { "number out of range" }, + + InvalidNetworkAddress + | _ | { "only TCP-based node addresses are supported" }, + + MismatchResponse + | _ | { "no matching response for incoming request" }, + + UnrecognizedEventType + { + event_type: String + } + | e | { + format_args!("unrecognized event type: {}", e.event_type) + }, + + Serde + [ DisplayOnly ] + | _ | { "parse error" }, + + ParseUrl + [ DisplayOnly ] + | _ | { "parse error" }, + + MalformedJson + | _ | { "server returned malformatted JSON (no 'result' or 'error')" }, + + UnsupportedScheme + { + scheme: String + } + | e | { + format_args!("unsupported scheme: {}", e.scheme) + }, + + UnsupportedRpcVersion + { + version: String, + supported: String + } + | e | { + format_args!("server RPC version unsupported: '{}' (only '{}' supported)", + e.version, e.supported) + }, + + } +} + +pub fn send_error(_: SendError) -> Error { + channel_send_error() } diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index c3da8b640..6f12038a0 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -48,6 +48,7 @@ mod paging; pub mod query; pub mod request; pub mod response; +pub mod response_error; mod result; mod rpc_url; mod utils; @@ -60,6 +61,7 @@ pub use order::Order; pub use paging::{PageNumber, Paging, PerPage}; pub use request::{Request, SimpleRequest}; pub use response::Response; +pub use response_error::{Code, ResponseError}; pub use result::Result; pub use rpc_url::{Scheme, Url}; pub use version::Version; diff --git a/rpc/src/method.rs b/rpc/src/method.rs index bc44eb72e..5d23e1b84 100644 --- a/rpc/src/method.rs +++ b/rpc/src/method.rs @@ -1,6 +1,6 @@ //! JSON-RPC request methods -use super::Error; +use crate::{error, Error}; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use std::{ fmt::{self, Display}, @@ -126,7 +126,7 @@ impl FromStr for Method { "tx_search" => Method::TxSearch, "unsubscribe" => Method::Unsubscribe, "validators" => Method::Validators, - other => return Err(Error::method_not_found(other)), + other => return Err(error::method_not_found_error(other.to_string())), }) } } diff --git a/rpc/src/order.rs b/rpc/src/order.rs index 79ff77142..564074bb2 100644 --- a/rpc/src/order.rs +++ b/rpc/src/order.rs @@ -1,6 +1,6 @@ //! Ordering of paginated RPC responses. -use crate::Error; +use crate::{error, Error}; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -23,7 +23,7 @@ impl FromStr for Order { match s { "asc" => Ok(Self::Ascending), "desc" => Ok(Self::Descending), - _ => Err(Error::invalid_params(&format!( + _ => Err(error::invalid_params_error(format!( "invalid order type: {} (must be \"asc\" or \"desc\")", s ))), diff --git a/rpc/src/paging.rs b/rpc/src/paging.rs index 19f538d1d..e92dd8915 100644 --- a/rpc/src/paging.rs +++ b/rpc/src/paging.rs @@ -1,6 +1,6 @@ //! Pagination-related data structures for the Tendermint RPC. -use crate::Error; +use crate::{error, Error}; use serde::{Deserialize, Serialize}; use std::convert::TryInto; use std::str::FromStr; @@ -29,10 +29,8 @@ impl FromStr for PageNumber { type Err = Error; fn from_str(s: &str) -> Result { - let raw = i64::from_str(s).map_err(|e| Error::client_internal_error(e.to_string()))?; - let raw_usize: usize = raw.try_into().map_err(|_| { - Error::client_internal_error(format!("page number out of range: {}", raw)) - })?; + let raw = i64::from_str(s).map_err(error::parse_int_error)?; + let raw_usize: usize = raw.try_into().map_err(error::out_of_range_error)?; Ok(raw_usize.into()) } } @@ -57,10 +55,8 @@ impl FromStr for PerPage { type Err = Error; fn from_str(s: &str) -> Result { - let raw = i64::from_str(s).map_err(|e| Error::client_internal_error(e.to_string()))?; - let raw_u8: u8 = raw.try_into().map_err(|_| { - Error::client_internal_error(format!("items per page out of range: {}", raw)) - })?; + let raw = i64::from_str(s).map_err(error::parse_int_error)?; + let raw_u8: u8 = raw.try_into().map_err(error::out_of_range_error)?; Ok(raw_u8.into()) } } diff --git a/rpc/src/query.rs b/rpc/src/query.rs index 892e3e148..691f6b108 100644 --- a/rpc/src/query.rs +++ b/rpc/src/query.rs @@ -7,7 +7,7 @@ // TODO(thane): These warnings are generated by the PEG for some reason. Try to fix and remove. #![allow(clippy::redundant_closure_call, clippy::unit_arg)] -use crate::{Error, Result}; +use crate::{error, Error, Result}; use chrono::{Date, DateTime, FixedOffset, Utc}; use std::fmt; use std::str::FromStr; @@ -232,10 +232,7 @@ impl FromStr for EventType { match s { "NewBlock" => Ok(Self::NewBlock), "Tx" => Ok(Self::Tx), - invalid => Err(Error::invalid_params(&format!( - "unrecognized event type: {}", - invalid - ))), + invalid => Err(error::unrecognized_event_type_error(invalid.to_string())), } } } diff --git a/rpc/src/response.rs b/rpc/src/response.rs index a31c5bf58..3bcc14918 100644 --- a/rpc/src/response.rs +++ b/rpc/src/response.rs @@ -1,6 +1,7 @@ //! JSON-RPC response types -use super::{Error, Id, Version}; +use crate::response_error::ResponseError; +use crate::{error, Error, Id, Version}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::io::Read; @@ -9,13 +10,13 @@ pub trait Response: Serialize + DeserializeOwned + Sized { /// Parse a JSON-RPC response from a JSON string fn from_string(response: impl AsRef<[u8]>) -> Result { let wrapper: Wrapper = - serde_json::from_slice(response.as_ref()).map_err(Error::parse_error)?; + serde_json::from_slice(response.as_ref()).map_err(error::serde_error)?; wrapper.into_result() } /// Parse a JSON-RPC response from an `io::Reader` fn from_reader(reader: impl Read) -> Result { - let wrapper: Wrapper = serde_json::from_reader(reader).map_err(Error::parse_error)?; + let wrapper: Wrapper = serde_json::from_reader(reader).map_err(error::serde_error)?; wrapper.into_result() } } @@ -33,7 +34,7 @@ pub struct Wrapper { result: Option, /// Error message if unsuccessful - error: Option, + error: Option, } impl Wrapper @@ -56,19 +57,17 @@ where // Ensure we're using a supported RPC version self.version().ensure_supported()?; - if let Some(error) = self.error { - Err(error) + if let Some(e) = self.error { + Err(error::response_error(e)) } else if let Some(result) = self.result { Ok(result) } else { - Err(Error::server_error( - "server returned malformatted JSON (no 'result' or 'error')", - )) + Err(error::malformed_json_error()) } } #[cfg(test)] - pub fn new_with_id(id: Id, result: Option, error: Option) -> Self { + pub fn new_with_id(id: Id, result: Option, error: Option) -> Self { Self { jsonrpc: Version::current(), id, diff --git a/rpc/src/response_error.rs b/rpc/src/response_error.rs new file mode 100644 index 000000000..087218a83 --- /dev/null +++ b/rpc/src/response_error.rs @@ -0,0 +1,207 @@ +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt::Display; + +#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)] +pub struct ResponseError { + /// Error code + code: Code, + + /// Error message + message: String, + + /// Additional data about the error + data: Option, +} + +// /// Tendermint RPC error codes. +// /// +/// See `func RPC*Error()` definitions in: +/// +#[derive(Copy, Clone, Debug, Eq, thiserror::Error, Hash, PartialEq, PartialOrd, Ord)] +pub enum Code { + /// Low-level HTTP error + #[error("HTTP error")] + HttpError, + + /// Low-level WebSocket error + #[error("WebSocket Error")] + WebSocketError, + + /// An internal error occurred within the client. + /// + /// This is an error unique to this client, and is not available in the + /// [Go client]. + /// + /// [Go client]: https://github.com/tendermint/tendermint/tree/master/rpc/jsonrpc/client + #[error("Client internal error")] + ClientInternalError, + + /// Parse error i.e. invalid JSON (-32700) + #[error("Parse error. Invalid JSON")] + ParseError, + + /// Invalid request (-32600) + #[error("Invalid Request")] + InvalidRequest, + + /// Method not found error (-32601) + #[error("Method not found")] + MethodNotFound, + + /// Invalid parameters (-32602) + #[error("Invalid params")] + InvalidParams, + + /// Internal RPC server error (-32603) + #[error("Internal error")] + InternalError, + + /// Server error (-32000) + #[error("Server error")] + ServerError, + + /// Other error types + #[error("Error (code: {})", 0)] + Other(i32), +} + +impl Code { + /// Get the integer error value for this code + pub fn value(self) -> i32 { + i32::from(self) + } +} + +impl From for Code { + fn from(value: i32) -> Code { + match value { + 0 => Code::HttpError, + 1 => Code::WebSocketError, + 2 => Code::ClientInternalError, + -32700 => Code::ParseError, + -32600 => Code::InvalidRequest, + -32601 => Code::MethodNotFound, + -32602 => Code::InvalidParams, + -32603 => Code::InternalError, + -32000 => Code::ServerError, + other => Code::Other(other), + } + } +} + +impl From for i32 { + fn from(code: Code) -> i32 { + match code { + Code::HttpError => 0, + Code::WebSocketError => 1, + Code::ClientInternalError => 2, + Code::ParseError => -32700, + Code::InvalidRequest => -32600, + Code::MethodNotFound => -32601, + Code::InvalidParams => -32602, + Code::InternalError => -32603, + Code::ServerError => -32000, + Code::Other(other) => other, + } + } +} + +impl Display for ResponseError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.data { + Some(data) => write!( + f, + "{}: {} (code: {})", + self.message, + data, + self.code.value() + ), + None => write!(f, "{} (code: {})", self.message, self.code.value()), + } + } +} + +impl ResponseError { + /// Create a new RPC error + pub fn new(code: Code, data: Option) -> ResponseError { + let message = code.to_string(); + + ResponseError { + code, + message, + data, + } + } + + // / Create a low-level HTTP error + pub fn http_error(message: impl Into) -> ResponseError { + ResponseError { + code: Code::HttpError, + message: message.into(), + data: None, + } + } + + /// Create a new invalid parameter error + pub fn invalid_params(data: &str) -> ResponseError { + ResponseError::new(Code::InvalidParams, Some(data.to_string())) + } + + /// Create a new websocket error + pub fn websocket_error(cause: impl Into) -> ResponseError { + ResponseError::new(Code::WebSocketError, Some(cause.into())) + } + + /// Create a new method-not-found error + pub fn method_not_found(name: &str) -> ResponseError { + ResponseError::new(Code::MethodNotFound, Some(name.to_string())) + } + + /// Create a new parse error + pub fn parse_error(error: E) -> ResponseError + where + E: Display, + { + ResponseError::new(Code::ParseError, Some(error.to_string())) + } + + /// Create a new server error + pub fn server_error(data: D) -> ResponseError + where + D: Display, + { + ResponseError::new(Code::ServerError, Some(data.to_string())) + } + + /// An internal error occurred within the client. + pub fn client_internal_error(cause: impl Into) -> ResponseError { + ResponseError::new(Code::ClientInternalError, Some(cause.into())) + } + + /// Obtain the `rpc::error::Code` for this error + pub fn code(&self) -> Code { + self.code + } + + /// Borrow the error message (if available) + pub fn message(&self) -> &str { + &self.message + } + + /// Optional additional error message (if available) + pub fn data(&self) -> Option<&str> { + self.data.as_ref().map(AsRef::as_ref) + } +} + +impl<'de> Deserialize<'de> for Code { + fn deserialize>(deserializer: D) -> Result { + Ok(Code::from(i32::deserialize(deserializer)?)) + } +} + +impl Serialize for Code { + fn serialize(&self, serializer: S) -> Result { + self.value().serialize(serializer) + } +} diff --git a/rpc/src/rpc_url.rs b/rpc/src/rpc_url.rs index c0896df68..228adaa19 100644 --- a/rpc/src/rpc_url.rs +++ b/rpc/src/rpc_url.rs @@ -1,5 +1,6 @@ //! URL representation for RPC clients. +use crate::error; use serde::de::Error; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryFrom; @@ -35,12 +36,7 @@ impl FromStr for Scheme { "https" => Scheme::Https, "ws" => Scheme::WebSocket, "wss" => Scheme::SecureWebSocket, - _ => { - return Err(crate::Error::invalid_params(&format!( - "unsupported scheme: {}", - s - ))) - } + _ => return Err(error::unsupported_scheme_error(s.to_string())), }) } } @@ -59,22 +55,20 @@ pub struct Url { } impl FromStr for Url { - type Err = crate::Error; + type Err = error::Error; fn from_str(s: &str) -> Result { - let inner: url::Url = s.parse()?; + let inner: url::Url = s.parse().map_err(error::parse_url_error)?; + let scheme: Scheme = inner.scheme().parse()?; + let host = inner .host_str() - .ok_or_else(|| { - crate::Error::invalid_params(&format!("URL is missing its host: {}", s)) - })? + .ok_or_else(|| error::invalid_params_error(format!("URL is missing its host: {}", s)))? .to_owned(); + let port = inner.port_or_known_default().ok_or_else(|| { - crate::Error::invalid_params(&format!( - "cannot determine appropriate port for URL: {}", - s - )) + error::invalid_params_error(format!("cannot determine appropriate port for URL: {}", s)) })?; Ok(Self { inner, diff --git a/rpc/src/version.rs b/rpc/src/version.rs index 79c310d66..d5bff1722 100644 --- a/rpc/src/version.rs +++ b/rpc/src/version.rs @@ -1,6 +1,6 @@ //! JSON-RPC versions -use super::error::Error; +use super::error::{self, Error}; use serde::{Deserialize, Serialize}; use std::{ fmt::{self, Display}, @@ -31,10 +31,10 @@ impl Version { if self.is_supported() { Ok(()) } else { - Err(Error::server_error(&format!( - "server RPC version unsupported: '{}' (only '{}' supported)", - self.0, SUPPORTED_VERSION - ))) + Err(error::unsupported_rpc_version_error( + self.0.to_string(), + SUPPORTED_VERSION.to_string(), + )) } } } diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index 0f61ed8e9..e0f6d7d2b 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -1,9 +1,12 @@ //! Tendermint kvstore RPC endpoint testing. +use flex_error::ErrorReport; use std::str::FromStr; use std::{fs, path::PathBuf}; use subtle_encoding::{base64, hex}; -use tendermint_rpc::{endpoint, error::Code, request::Wrapper as RequestWrapper, Order, Response}; +use tendermint_rpc::{ + endpoint, error::ErrorDetail, request::Wrapper as RequestWrapper, Code, Order, Response, +}; use walkdir::WalkDir; const CHAIN_ID: &str = "dockerchain"; @@ -306,15 +309,23 @@ fn incoming_fixtures() { assert!(result.response.value.is_empty()); } "block_at_height_0" => { - let error = endpoint::block::Response::from_string(&content) - .err() - .unwrap(); - assert_eq!(error.code(), Code::InternalError); - assert_eq!(error.message(), "Internal error"); - assert_eq!( - error.data(), - Some("height must be greater than 0, but got 0") - ); + let res = endpoint::block::Response::from_string(&content); + + match res { + Err(ErrorReport { + detail: ErrorDetail::Response(e), + trace: _, + }) => { + let response = e.source; + assert_eq!(response.code(), Code::InternalError); + assert_eq!(response.message(), "Internal error"); + assert_eq!( + response.data(), + Some("height must be greater than 0, but got 0") + ); + } + _ => panic!("expected Response error"), + } } "block_at_height_1" => { let result = endpoint::block::Response::from_string(content).unwrap(); @@ -720,23 +731,40 @@ fn incoming_fixtures() { assert_eq!(result.validator_info.power.value(), 10); } "subscribe_malformed" => { - let result = endpoint::subscribe::Response::from_string(content) - .err() - .unwrap(); - assert_eq!(result.code(), Code::InternalError); - assert_eq!(result.message(), "Internal error"); - assert_eq!(result.data().unwrap(),"failed to parse query: \nparse error near PegText (line 1 symbol 2 - line 1 symbol 11):\n\"malformed\"\n"); + let result = endpoint::subscribe::Response::from_string(content); + + match result { + Err(ErrorReport { + detail: ErrorDetail::Response(e), + trace: _, + }) => { + let response = e.source; + + assert_eq!(response.code(), Code::InternalError); + assert_eq!(response.message(), "Internal error"); + assert_eq!(response.data().unwrap(),"failed to parse query: \nparse error near PegText (line 1 symbol 2 - line 1 symbol 11):\n\"malformed\"\n"); + } + _ => panic!("expected Response error"), + } } "subscribe_newblock" => { - let result = endpoint::subscribe::Response::from_string(content) - .err() - .unwrap(); - assert_eq!(result.code(), Code::ParseError); - assert_eq!(result.message(), "Parse error. Invalid JSON"); - assert_eq!( - result.data().unwrap(), - "missing field `jsonrpc` at line 1 column 2" - ); + let result = endpoint::subscribe::Response::from_string(content); + + match result { + Err(ErrorReport { + detail: ErrorDetail::Response(e), + trace: _, + }) => { + let response = e.source; + assert_eq!(response.code(), Code::ParseError); + assert_eq!(response.message(), "Parse error. Invalid JSON"); + assert_eq!( + response.data().unwrap(), + "missing field `jsonrpc` at line 1 column 2" + ); + } + _ => panic!("expected Response error"), + } } "subscribe_newblock_0" => { let result = tendermint_rpc::event::Event::from_string(content).unwrap(); diff --git a/rpc/tests/parse_response.rs b/rpc/tests/parse_response.rs index a96499b01..acd0e3703 100644 --- a/rpc/tests/parse_response.rs +++ b/rpc/tests/parse_response.rs @@ -3,10 +3,11 @@ use std::{fs, path::PathBuf}; use tendermint::abci::Code; +use flex_error::ErrorReport; use std::str::FromStr; use tendermint::vote; use tendermint_rpc::endpoint::consensus_state::RoundVote; -use tendermint_rpc::{self as rpc, endpoint, Response}; +use tendermint_rpc::{endpoint, error::ErrorDetail, Code as RpcCode, Response}; const EXAMPLE_APP: &str = "GaiaApp"; const EXAMPLE_CHAIN: &str = "cosmoshub-2"; @@ -294,15 +295,20 @@ fn validators() { fn jsonrpc_error() { let result = endpoint::blockchain::Response::from_string(&read_json_fixture("error")); - if let Err(err) = result { - assert_eq!(err.code(), rpc::error::Code::InternalError); - assert_eq!(err.message(), "Internal error"); - assert_eq!( - err.data().unwrap(), - "min height 321 can't be greater than max height 123" - ); - } else { - panic!("expected error, got {:?}", result) + match result { + Err(ErrorReport { + detail: ErrorDetail::Response(e), + trace: _, + }) => { + let response = e.source; + assert_eq!(response.code(), RpcCode::InternalError); + assert_eq!(response.message(), "Internal error"); + assert_eq!( + response.data().unwrap(), + "min height 321 can't be greater than max height 123" + ); + } + _ => panic!("expected Response error"), } } From 668beb3720cafa03aa5be7b370a120186b52c92c Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 2 Jul 2021 10:45:17 +0200 Subject: [PATCH 08/25] Use flex-error for protobuf errors --- proto/Cargo.toml | 1 + proto/src/error.rs | 57 +++++++++++++++++++++++----------------------- proto/src/lib.rs | 29 ++++++++++++----------- 3 files changed, 44 insertions(+), 43 deletions(-) diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 37e83189c..64e03bf8d 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -28,6 +28,7 @@ serde_bytes = "0.11" num-traits = "0.2" num-derive = "0.3" chrono = { version = "0.4", features = ["serde"] } +flex-error = "0.2.1" [dev-dependencies] serde_json = "1.0" diff --git a/proto/src/error.rs b/proto/src/error.rs index da6ec7970..3bdb5bce3 100644 --- a/proto/src/error.rs +++ b/proto/src/error.rs @@ -1,37 +1,38 @@ //! This module defines the various errors that be raised during Protobuf conversions. -use anomaly::{BoxError, Context}; -use thiserror::Error; +use flex_error::{define_error, DisplayOnly}; +use prost::{DecodeError, EncodeError}; +use std::convert::TryFrom; +use std::fmt::Display; +use std::num::TryFromIntError; -/// An error that can be raised by the Protobuf conversions. -pub type Error = anomaly::Error; +define_error! { + Error { + TryFromProtobuf + { reason: String } + | e | { + format!("error converting message type into domain type: {}", + e.reason) + }, -/// Various kinds of errors that can be raised. -#[derive(Clone, Debug, Error)] -pub enum Kind { - /// TryFrom Prost Message failed during decoding - #[error("error converting message type into domain type")] - TryFromProtobuf, + EncodeMessage + [ DisplayOnly ] + | _ | { "error encoding message into buffer" }, - /// encoding prost Message into buffer failed - #[error("error encoding message into buffer")] - EncodeMessage, + DecodeMessage + [ DisplayOnly ] + | _ | { "error decoding buffer into message" }, - /// decoding buffer into prost Message failed - #[error("error decoding buffer into message")] - DecodeMessage, + ParseLength + [ DisplayOnly ] + | _ | { "error parsing encoded length" }, + } } -impl Kind { - /// Add a given source error as context for this error kind - /// - /// This is typically use with `map_err` as follows: - /// - /// ```ignore - /// let x = self.something.do_stuff() - /// .map_err(|e| error::Kind::Config.context(e))?; - /// ``` - pub fn context(self, source: impl Into) -> Context { - Context::new(self, Some(source.into())) - } +pub fn try_from_error(e: E) -> Error +where + E: Display, + T: TryFrom, +{ + try_from_protobuf_error(format!("{}", e)) } diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 22233a8b6..73bef76b4 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -15,17 +15,18 @@ pub mod google { } } +mod error; #[allow(warnings)] mod tendermint; + +pub use error::Error; pub use tendermint::*; -mod error; -use anomaly::BoxError; use bytes::{Buf, BufMut}; -pub use error::{Error, Kind}; use prost::encoding::encoded_len_varint; use prost::Message; use std::convert::{TryFrom, TryInto}; +use std::fmt::Display; pub mod serializers; @@ -109,7 +110,7 @@ pub mod serializers; pub trait Protobuf + Default> where Self: Sized + Clone + TryFrom, - >::Error: Into, + >::Error: Display, { /// Encode into a buffer in Protobuf format. /// @@ -120,7 +121,7 @@ where fn encode(&self, buf: &mut B) -> Result<(), Error> { T::from(self.clone()) .encode(buf) - .map_err(|e| Kind::EncodeMessage.context(e).into()) + .map_err(error::encode_message_error) } /// Encode with a length-delimiter to a buffer in Protobuf format. @@ -134,7 +135,7 @@ where fn encode_length_delimited(&self, buf: &mut B) -> Result<(), Error> { T::from(self.clone()) .encode_length_delimited(buf) - .map_err(|e| Kind::EncodeMessage.context(e).into()) + .map_err(error::encode_message_error) } /// Constructor that attempts to decode an instance from a buffer. @@ -146,10 +147,9 @@ where /// /// [`prost::Message::decode`]: https://docs.rs/prost/*/prost/trait.Message.html#method.decode fn decode(buf: B) -> Result { - T::decode(buf).map_or_else( - |e| Err(Kind::DecodeMessage.context(e).into()), - |t| Self::try_from(t).map_err(|e| Kind::TryFromProtobuf.context(e).into()), - ) + let raw = T::decode(buf).map_err(error::decode_message_error)?; + + Self::try_from(raw).map_err(error::try_from_error::) } /// Constructor that attempts to decode a length-delimited instance from @@ -162,10 +162,9 @@ where /// /// [`prost::Message::decode_length_delimited`]: https://docs.rs/prost/*/prost/trait.Message.html#method.decode_length_delimited fn decode_length_delimited(buf: B) -> Result { - T::decode_length_delimited(buf).map_or_else( - |e| Err(Kind::DecodeMessage.context(e).into()), - |t| Self::try_from(t).map_err(|e| Kind::TryFromProtobuf.context(e).into()), - ) + let raw = T::decode_length_delimited(buf).map_err(error::decode_message_error)?; + + Self::try_from(raw).map_err(error::try_from_error::) } /// Returns the encoded length of the message without a length delimiter. @@ -193,7 +192,7 @@ where /// Encode with a length-delimiter to a `Vec` Protobuf-encoded message. fn encode_length_delimited_vec(&self) -> Result, Error> { let len = self.encoded_len(); - let lenu64 = len.try_into().map_err(|e| Kind::EncodeMessage.context(e))?; + let lenu64 = len.try_into().map_err(error::parse_length_error)?; let mut wire = Vec::with_capacity(len + encoded_len_varint(lenu64)); self.encode_length_delimited(&mut wire).map(|_| wire) } From 98a161ad779cebda618693d20bae113e7c234048 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 2 Jul 2021 11:41:32 +0200 Subject: [PATCH 09/25] Use flex-error for abci --- abci/Cargo.toml | 2 +- abci/src/error.rs | 36 ++++++++++++++++++++++-------------- p2p/Cargo.toml | 1 - proto/Cargo.toml | 2 -- rpc/src/error.rs | 7 ------- tendermint/Cargo.toml | 2 -- test/Cargo.toml | 1 - 7 files changed, 23 insertions(+), 28 deletions(-) diff --git a/abci/Cargo.toml b/abci/Cargo.toml index 0d40662cb..c17dfac04 100644 --- a/abci/Cargo.toml +++ b/abci/Cargo.toml @@ -31,8 +31,8 @@ bytes = "1.0" eyre = "0.6" prost = "0.7" tendermint-proto = { version = "0.20.0", path = "../proto" } -thiserror = "1.0" tracing = "0.1" +flex-error = "0.2.1" structopt = { version = "0.3", optional = true } tracing-subscriber = { version = "0.2", optional = true } diff --git a/abci/src/error.rs b/abci/src/error.rs index a66021cd5..59a3f35e6 100644 --- a/abci/src/error.rs +++ b/abci/src/error.rs @@ -1,22 +1,30 @@ //! tendermint-abci errors -use thiserror::Error; +use flex_error::define_error; +use tendermint_proto::abci::response::Value; -/// Errors that can be produced by tendermint-abci. -#[derive(Debug, Error)] -pub enum Error { - #[error("server connection terminated")] - ServerConnectionTerminated, +define_error! { + Error { + ServerConnectionTerminated + | _ | { "server connection terminated" }, - #[error("malformed server response")] - MalformedServerResponse, + MalformedServerResponse + | _ | { "malformed server response" }, - #[error("unexpected server response type: expected {0}, but got {1:?}")] - UnexpectedServerResponseType(String, tendermint_proto::abci::response::Value), + UnexpectedServerResponseType + { + expected: String, + got: Value, + } + | e | { + format_args!("unexpected server response type: expected {0}, but got {1:?}", + e.expected, e.got) + }, - #[error("channel send error: {0}")] - ChannelSend(String), + ChannelSend + | _ | { "channel send error" }, - #[error("channel receive error: {0}")] - ChannelRecv(String), + ChannelRecv + | _ | { "channel recv error" }, + } } diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index da78b8f61..b24227db1 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -35,7 +35,6 @@ prost = "0.7" rand_core = { version = "0.5", features = ["std"] } sha2 = "0.9" subtle = "2" -thiserror = "1" x25519-dalek = "1.1" zeroize = "1" signature = "1.3.0" diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 64e03bf8d..8a963369d 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -20,8 +20,6 @@ all-features = true prost = "0.7" prost-types = "0.7" bytes = "1.0" -anomaly = "0.2" -thiserror = "1.0" serde = { version = "1.0", features = ["derive"] } subtle-encoding = "0.5" serde_bytes = "0.11" diff --git a/rpc/src/error.rs b/rpc/src/error.rs index bfa658d08..517ad7be9 100644 --- a/rpc/src/error.rs +++ b/rpc/src/error.rs @@ -1,12 +1,5 @@ //! JSON-RPC error types -// #[cfg(feature = "websocket-client")] -// use async_tungstenite::tungstenite::Error as WSError; - -// use serde::{Deserialize, Deserializer, Serialize, Serializer}; -// use std::fmt::{self, Display}; -// use thiserror::Error; - use flex_error::{define_error, DisplayError, DisplayOnly}; use http::uri::InvalidUri; use http::Error as HttpError; diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index 01316ab8e..a5600a29d 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -33,7 +33,6 @@ rustdoc-args = ["--cfg", "docsrs"] crate-type = ["cdylib", "rlib"] [dependencies] -anomaly = "0.2" async-trait = "0.1" bytes = "1.0" chrono = { version = "0.4.19", features = ["serde"] } @@ -52,7 +51,6 @@ sha2 = { version = "0.9", default-features = false } signature = "1.2" subtle = "2" subtle-encoding = { version = "0.5", features = ["bech32-preview"] } -thiserror = "1" tendermint-proto = { version = "0.20.0", path = "../proto" } toml = { version = "0.5" } url = { version = "2.2" } diff --git a/test/Cargo.toml b/test/Cargo.toml index 83471d1a7..5090731fe 100644 --- a/test/Cargo.toml +++ b/test/Cargo.toml @@ -20,7 +20,6 @@ flume = "0.10" rand_core = { version = "0.5", features = ["std"] } readwrite = "^0.1.1" subtle-encoding = { version = "0.5" } -thiserror = "1" x25519-dalek = "1.1" tendermint = { path = "../tendermint" } From 266e0cc2bad5be31be79018500f644ef82519e9b Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 6 Jul 2021 10:11:27 +0200 Subject: [PATCH 10/25] Fix test_bisection_no_witness_left --- light-client/src/supervisor.rs | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 3da3f7818..dcfe8c60d 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -679,12 +679,27 @@ mod tests { let (result, _) = run_bisection_test(peer_list, 10); + // FIXME: currently this test does not test what it is supposed to test, + // because MockIo returns an InvalidRequest error. This was previously + // treated as a NoWitnessLeft error, which was misclassified. match result { Err(ErrorReport { - detail: error::ErrorDetail::NoWitnessesLeft(_), + detail: error::ErrorDetail::Io(e), trace: _, - }) => {} - _ => panic!("expected NoWitnessesLeft error"), + }) => match e.source { + crate::components::io::IoErrorDetail::Rpc(e) => match e.source { + rpc::error::ErrorDetail::Response(e) => { + assert_eq!(e.source.code(), rpc::Code::InvalidRequest) + } + _ => { + panic!("expected Response error, instead got {:?}", e) + } + }, + _ => { + panic!("expected Rpc error, instead got {:?}", e) + } + }, + _ => panic!("expected Io error, instead got {:?}", result), } } From a9bb71322f6fc078d13f62ffbe60701735708515 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 6 Jul 2021 20:54:03 +0200 Subject: [PATCH 11/25] Fix build errors in all-features --- abci/Cargo.toml | 2 +- abci/src/application/kvstore.rs | 25 ++- abci/src/client.rs | 54 ++++--- abci/src/codec.rs | 40 +++-- abci/src/error.rs | 19 ++- abci/src/lib.rs | 5 +- abci/src/server.rs | 15 +- light-client/Cargo.toml | 2 +- light-client/src/components/io.rs | 24 --- light-client/src/components/verifier.rs | 5 +- light-client/src/fork_detector.rs | 12 +- light-client/src/light_client.rs | 20 +-- light-client/src/operations/voting_power.rs | 10 +- light-client/src/peer_list.rs | 15 +- light-client/src/predicates.rs | 65 ++------ light-client/src/supervisor.rs | 22 +-- p2p/Cargo.toml | 2 +- proto/Cargo.toml | 2 +- rpc/Cargo.toml | 3 +- rpc/src/client.rs | 57 ++++--- rpc/src/client/bin/main.rs | 84 ++++++---- rpc/src/client/subscription.rs | 14 +- rpc/src/client/sync.rs | 4 +- rpc/src/client/transport/http.rs | 32 ++-- rpc/src/client/transport/mock.rs | 32 ++-- rpc/src/client/transport/websocket.rs | 160 ++++++++++---------- rpc/src/error.rs | 23 +++ rpc/src/lib.rs | 2 - rpc/src/query.rs | 4 +- rpc/src/result.rs | 4 - rpc/tests/kvstore_fixtures.rs | 15 +- rpc/tests/parse_response.rs | 5 +- tendermint/Cargo.toml | 2 +- tendermint/src/chain/id.rs | 4 +- tendermint/src/error.rs | 30 ++-- tendermint/src/timeout.rs | 2 +- 36 files changed, 397 insertions(+), 419 deletions(-) delete mode 100644 rpc/src/result.rs diff --git a/abci/Cargo.toml b/abci/Cargo.toml index c17dfac04..d92fd7648 100644 --- a/abci/Cargo.toml +++ b/abci/Cargo.toml @@ -32,7 +32,7 @@ eyre = "0.6" prost = "0.7" tendermint-proto = { version = "0.20.0", path = "../proto" } tracing = "0.1" -flex-error = "0.2.1" +flex-error = "0.3.0" structopt = { version = "0.3", optional = true } tracing-subscriber = { version = "0.2", optional = true } diff --git a/abci/src/application/kvstore.rs b/abci/src/application/kvstore.rs index 6a1cf5097..7df30841b 100644 --- a/abci/src/application/kvstore.rs +++ b/abci/src/application/kvstore.rs @@ -1,7 +1,7 @@ //! In-memory key/value store ABCI application. use crate::codec::{encode_varint, MAX_VARINT_LENGTH}; -use crate::{Application, Error, Result}; +use crate::{error, Application, Error}; use bytes::BytesMut; use std::collections::HashMap; use std::sync::mpsc::{channel, Receiver, Sender}; @@ -28,7 +28,7 @@ impl KeyValueStoreApp { } /// Attempt to retrieve the value associated with the given key. - pub fn get>(&self, key: K) -> Result<(i64, Option)> { + pub fn get>(&self, key: K) -> Result<(i64, Option), Error> { let (result_tx, result_rx) = channel(); channel_send( &self.cmd_tx, @@ -44,7 +44,7 @@ impl KeyValueStoreApp { /// /// Optionally returns any pre-existing value associated with the given /// key. - pub fn set(&self, key: K, value: V) -> Result> + pub fn set(&self, key: K, value: V) -> Result, Error> where K: AsRef, V: AsRef, @@ -202,12 +202,9 @@ impl KeyValueStoreDriver { } /// Run the driver in the current thread (blocking). - pub fn run(mut self) -> Result<()> { + pub fn run(mut self) -> Result<(), Error> { loop { - let cmd = self - .cmd_rx - .recv() - .map_err(|e| Error::ChannelRecv(e.to_string()))?; + let cmd = self.cmd_rx.recv().map_err(error::channel_recv_error)?; match cmd { Command::GetInfo { result_tx } => { channel_send(&result_tx, (self.height, self.app_hash.clone()))? @@ -232,7 +229,7 @@ impl KeyValueStoreDriver { } } - fn commit(&mut self, result_tx: Sender<(i64, Vec)>) -> Result<()> { + fn commit(&mut self, result_tx: Sender<(i64, Vec)>) -> Result<(), Error> { // As in the Go-based key/value store, simply encode the number of // items as the "app hash" let mut app_hash = BytesMut::with_capacity(MAX_VARINT_LENGTH); @@ -263,12 +260,10 @@ enum Command { Commit { result_tx: Sender<(i64, Vec)> }, } -fn channel_send(tx: &Sender, value: T) -> Result<()> { - tx.send(value) - .map_err(|e| Error::ChannelSend(e.to_string()).into()) +fn channel_send(tx: &Sender, value: T) -> Result<(), Error> { + tx.send(value).map_err(error::send_error) } -fn channel_recv(rx: &Receiver) -> Result { - rx.recv() - .map_err(|e| Error::ChannelRecv(e.to_string()).into()) +fn channel_recv(rx: &Receiver) -> Result { + rx.recv().map_err(error::channel_recv_error) } diff --git a/abci/src/client.rs b/abci/src/client.rs index d8cf0db23..f3dc0eae0 100644 --- a/abci/src/client.rs +++ b/abci/src/client.rs @@ -1,7 +1,7 @@ //! Blocking ABCI client. use crate::codec::ClientCodec; -use crate::{Error, Result}; +use crate::{error, Error}; use std::net::{TcpStream, ToSocketAddrs}; use tendermint_proto::abci::{ request, response, RequestApplySnapshotChunk, RequestBeginBlock, RequestCheckTx, RequestCommit, @@ -31,8 +31,8 @@ impl ClientBuilder { /// Client constructor that attempts to connect to the given network /// address. - pub fn connect(self, addr: A) -> Result { - let stream = TcpStream::connect(addr)?; + pub fn connect(self, addr: A) -> Result { + let stream = TcpStream::connect(addr).map_err(error::io_error)?; Ok(Client { codec: ClientCodec::new(stream, self.read_buf_size), }) @@ -56,73 +56,80 @@ macro_rules! perform { ($self:expr, $type:ident, $req:expr) => { match $self.perform(request::Value::$type($req))? { response::Value::$type(r) => Ok(r), - r => Err(Error::UnexpectedServerResponseType(stringify!($type).to_string(), r).into()), + r => Err(error::unexpected_server_response_type_error( + stringify!($type).to_string(), + r, + ) + .into()), } }; } impl Client { /// Ask the ABCI server to echo back a message. - pub fn echo(&mut self, req: RequestEcho) -> Result { + pub fn echo(&mut self, req: RequestEcho) -> Result { perform!(self, Echo, req) } /// Request information about the ABCI application. - pub fn info(&mut self, req: RequestInfo) -> Result { + pub fn info(&mut self, req: RequestInfo) -> Result { perform!(self, Info, req) } /// To be called once upon genesis. - pub fn init_chain(&mut self, req: RequestInitChain) -> Result { + pub fn init_chain(&mut self, req: RequestInitChain) -> Result { perform!(self, InitChain, req) } /// Query the application for data at the current or past height. - pub fn query(&mut self, req: RequestQuery) -> Result { + pub fn query(&mut self, req: RequestQuery) -> Result { perform!(self, Query, req) } /// Check the given transaction before putting it into the local mempool. - pub fn check_tx(&mut self, req: RequestCheckTx) -> Result { + pub fn check_tx(&mut self, req: RequestCheckTx) -> Result { perform!(self, CheckTx, req) } /// Signal the beginning of a new block, prior to any `DeliverTx` calls. - pub fn begin_block(&mut self, req: RequestBeginBlock) -> Result { + pub fn begin_block(&mut self, req: RequestBeginBlock) -> Result { perform!(self, BeginBlock, req) } /// Apply a transaction to the application's state. - pub fn deliver_tx(&mut self, req: RequestDeliverTx) -> Result { + pub fn deliver_tx(&mut self, req: RequestDeliverTx) -> Result { perform!(self, DeliverTx, req) } /// Signal the end of a block. - pub fn end_block(&mut self, req: RequestEndBlock) -> Result { + pub fn end_block(&mut self, req: RequestEndBlock) -> Result { perform!(self, EndBlock, req) } - pub fn flush(&mut self) -> Result { + pub fn flush(&mut self) -> Result { perform!(self, Flush, RequestFlush {}) } /// Commit the current state at the current height. - pub fn commit(&mut self) -> Result { + pub fn commit(&mut self) -> Result { perform!(self, Commit, RequestCommit {}) } /// Request that the application set an option to a particular value. - pub fn set_option(&mut self, req: RequestSetOption) -> Result { + pub fn set_option(&mut self, req: RequestSetOption) -> Result { perform!(self, SetOption, req) } /// Used during state sync to discover available snapshots on peers. - pub fn list_snapshots(&mut self) -> Result { + pub fn list_snapshots(&mut self) -> Result { perform!(self, ListSnapshots, RequestListSnapshots {}) } /// Called when bootstrapping the node using state sync. - pub fn offer_snapshot(&mut self, req: RequestOfferSnapshot) -> Result { + pub fn offer_snapshot( + &mut self, + req: RequestOfferSnapshot, + ) -> Result { perform!(self, OfferSnapshot, req) } @@ -130,7 +137,7 @@ impl Client { pub fn load_snapshot_chunk( &mut self, req: RequestLoadSnapshotChunk, - ) -> Result { + ) -> Result { perform!(self, LoadSnapshotChunk, req) } @@ -138,19 +145,16 @@ impl Client { pub fn apply_snapshot_chunk( &mut self, req: RequestApplySnapshotChunk, - ) -> Result { + ) -> Result { perform!(self, ApplySnapshotChunk, req) } - fn perform(&mut self, req: request::Value) -> Result { + fn perform(&mut self, req: request::Value) -> Result { self.codec.send(Request { value: Some(req) })?; let res = self .codec .next() - .ok_or(Error::ServerConnectionTerminated)??; - match res.value { - Some(value) => Ok(value), - None => Err(Error::MalformedServerResponse.into()), - } + .ok_or_else(error::server_connection_terminated_error)??; + res.value.ok_or_else(error::malformed_server_response_error) } } diff --git a/abci/src/codec.rs b/abci/src/codec.rs index 919e2e798..ddd19ddc1 100644 --- a/abci/src/codec.rs +++ b/abci/src/codec.rs @@ -4,13 +4,14 @@ //! //! [tsp]: https://docs.tendermint.com/master/spec/abci/client-server.html#tsp -use crate::Result; use bytes::{Buf, BufMut, BytesMut}; use prost::Message; use std::io::{Read, Write}; use std::marker::PhantomData; use tendermint_proto::abci::{Request, Response}; +use crate::error::{self, Error}; + /// The maximum number of bytes we expect in a varint. We use this to check if /// we're encountering a decoding error for a varint. pub const MAX_VARINT_LENGTH: usize = 16; @@ -60,7 +61,7 @@ where S: Read, I: Message + Default, { - type Item = Result; + type Item = Result; fn next(&mut self) -> Option { loop { @@ -75,7 +76,7 @@ where // more let bytes_read = match self.stream.read(self.read_window.as_mut()) { Ok(br) => br, - Err(e) => return Some(Err(e.into())), + Err(e) => return Some(Err(error::io_error(e))), }; if bytes_read == 0 { // The underlying stream terminated @@ -93,31 +94,38 @@ where O: Message, { /// Send a message using this codec. - pub fn send(&mut self, message: O) -> Result<()> { + pub fn send(&mut self, message: O) -> Result<(), Error> { encode_length_delimited(message, &mut self.write_buf)?; while !self.write_buf.is_empty() { - let bytes_written = self.stream.write(self.write_buf.as_ref())?; + let bytes_written = self + .stream + .write(self.write_buf.as_ref()) + .map_err(error::io_error)?; + if bytes_written == 0 { - return Err(std::io::Error::new( + return Err(error::io_error(std::io::Error::new( std::io::ErrorKind::WriteZero, "failed to write to underlying stream", - ) - .into()); + ))); } self.write_buf.advance(bytes_written); } - Ok(self.stream.flush()?) + + self.stream.flush().map_err(error::io_error)?; + + Ok(()) } } /// Encode the given message with a length prefix. -pub fn encode_length_delimited(message: M, mut dst: &mut B) -> Result<()> +pub fn encode_length_delimited(message: M, mut dst: &mut B) -> Result<(), Error> where M: Message, B: BufMut, { let mut buf = BytesMut::new(); - message.encode(&mut buf)?; + message.encode(&mut buf).map_err(error::encode_error)?; + let buf = buf.freeze(); encode_varint(buf.len() as u64, &mut dst); dst.put(buf); @@ -125,7 +133,7 @@ where } /// Attempt to decode a message of type `M` from the given source buffer. -pub fn decode_length_delimited(src: &mut BytesMut) -> Result> +pub fn decode_length_delimited(src: &mut BytesMut) -> Result, Error> where M: Message + Default, { @@ -148,7 +156,9 @@ where src.advance(delim_len + (encoded_len as usize)); let mut result_bytes = BytesMut::from(tmp.split_to(encoded_len as usize).as_ref()); - Ok(Some(M::decode(&mut result_bytes)?)) + let res = M::decode(&mut result_bytes).map_err(error::decode_error)?; + + Ok(Some(res)) } } @@ -158,7 +168,7 @@ pub fn encode_varint(val: u64, mut buf: &mut B) { prost::encoding::encode_varint(val << 1, &mut buf); } -pub fn decode_varint(mut buf: &mut B) -> Result { - let len = prost::encoding::decode_varint(&mut buf)?; +pub fn decode_varint(mut buf: &mut B) -> Result { + let len = prost::encoding::decode_varint(&mut buf).map_err(error::decode_error)?; Ok(len >> 1) } diff --git a/abci/src/error.rs b/abci/src/error.rs index 59a3f35e6..19e4589a7 100644 --- a/abci/src/error.rs +++ b/abci/src/error.rs @@ -1,10 +1,22 @@ //! tendermint-abci errors -use flex_error::define_error; +use flex_error::{define_error, DisplayError}; use tendermint_proto::abci::response::Value; define_error! { Error { + Io + [ DisplayError ] + | _ | { "I/O error" }, + + Encode + [ DisplayError ] + | _ | { "error encoding protocol buffer" }, + + Decode + [ DisplayError ] + | _ | { "error encoding protocol buffer" }, + ServerConnectionTerminated | _ | { "server connection terminated" }, @@ -25,6 +37,11 @@ define_error! { | _ | { "channel send error" }, ChannelRecv + [ DisplayError ] | _ | { "channel recv error" }, } } + +pub fn send_error(_e: std::sync::mpsc::SendError) -> Error { + channel_send_error() +} diff --git a/abci/src/lib.rs b/abci/src/lib.rs index 356574372..2d6d602bb 100644 --- a/abci/src/lib.rs +++ b/abci/src/lib.rs @@ -57,12 +57,9 @@ mod application; #[cfg(feature = "client")] mod client; mod codec; -mod error; +pub mod error; mod server; -// Re-exported -pub use eyre::Result; - // Common exports pub use application::Application; #[cfg(feature = "client")] diff --git a/abci/src/server.rs b/abci/src/server.rs index b9e6dc51b..91eccc727 100644 --- a/abci/src/server.rs +++ b/abci/src/server.rs @@ -2,7 +2,10 @@ use crate::application::RequestDispatcher; use crate::codec::ServerCodec; -use crate::{Application, Result}; +use crate::{ + error::{self, Error}, + Application, +}; use std::net::{TcpListener, TcpStream, ToSocketAddrs}; use std::thread; use tracing::{error, info}; @@ -31,13 +34,13 @@ impl ServerBuilder { /// Binds the server to the given address. You must subsequently call the /// [`Server::listen`] method in order for incoming connections' requests /// to be routed to the specified ABCI application. - pub fn bind(self, addr: Addr, app: App) -> Result> + pub fn bind(self, addr: Addr, app: App) -> Result, Error> where Addr: ToSocketAddrs, App: Application, { - let listener = TcpListener::bind(addr)?; - let local_addr = listener.local_addr()?.to_string(); + let listener = TcpListener::bind(addr).map_err(error::io_error)?; + let local_addr = listener.local_addr().map_err(error::io_error)?.to_string(); info!("ABCI server running at {}", local_addr); Ok(Server { app, @@ -71,9 +74,9 @@ pub struct Server { impl Server { /// Initiate a blocking listener for incoming connections. - pub fn listen(self) -> Result<()> { + pub fn listen(self) -> Result<(), Error> { loop { - let (stream, addr) = self.listener.accept()?; + let (stream, addr) = self.listener.accept().map_err(error::io_error)?; let addr = addr.to_string(); info!("Incoming connection from: {}", addr); self.spawn_client_handler(stream, addr); diff --git a/light-client/Cargo.toml b/light-client/Cargo.toml index 8c4fc0558..8df937600 100644 --- a/light-client/Cargo.toml +++ b/light-client/Cargo.toml @@ -50,7 +50,7 @@ serde_derive = "1.0.106" sled = { version = "0.34.3", optional = true } static_assertions = "1.1.0" tokio = { version = "1.0", features = ["rt"], optional = true } -flex-error = "0.2.1" +flex-error = "0.3.0" [dev-dependencies] tendermint-testgen = { path = "../testgen" } diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index 9fe0d94ba..3f076c10e 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -59,30 +59,6 @@ define_error! { } } -// /// I/O errors -// #[derive(Clone, Debug, Error, PartialEq, Serialize, Deserialize)] -// pub enum IoError { -// /// Wrapper for a `tendermint::rpc::Error`. -// #[error(transparent)] -// RpcError(#[from] rpc::Error), - -// /// Given height is invalid -// #[error("invalid height: {0}")] -// InvalidHeight(String), - -// /// Fetched validator set is invalid -// #[error("fetched validator set is invalid: {0}")] -// InvalidValidatorSet(String), - -// /// Task timed out. -// #[error("task timed out after {} ms", .0.as_millis())] -// Timeout(Duration), - -// /// Failed to initialize runtime -// #[error("failed to initialize runtime")] -// Runtime, -// } - impl IoErrorDetail { /// Whether this error means that a timeout occured when querying a node. pub fn is_timeout(&self) -> Option { diff --git a/light-client/src/components/verifier.rs b/light-client/src/components/verifier.rs index 32f1fc827..d19e549b6 100644 --- a/light-client/src/components/verifier.rs +++ b/light-client/src/components/verifier.rs @@ -11,6 +11,7 @@ use crate::{ }, types::{LightBlock, Time}, }; +use flex_error::ErrorReport; use preds::{ errors::{VerificationError, VerificationErrorDetail}, ProdPredicates, VerificationPredicates, @@ -34,9 +35,9 @@ impl From> for Verdict { fn from(result: Result<(), VerificationError>) -> Self { match result { Ok(()) => Self::Success, - Err(e) => match e.detail.not_enough_trust() { + Err(ErrorReport(e, _)) => match e.not_enough_trust() { Some(tally) => Self::NotEnoughTrust(tally), - _ => Self::Invalid(e.detail), + _ => Self::Invalid(e), }, } } diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index ed3b83251..bd4c8caea 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -9,6 +9,8 @@ use crate::{ types::{LightBlock, PeerId, Status}, }; +use flex_error::ErrorReport; + /// Result of fork detection #[derive(Debug)] pub enum ForkDetection { @@ -120,17 +122,17 @@ impl ForkDetector for ProdForkDetector { primary: verified_block.clone(), witness: witness_block, }), - Err(e) if e.detail.has_expired() => { + Err(ErrorReport(e, _)) if e.has_expired() => { forks.push(Fork::Forked { primary: verified_block.clone(), witness: witness_block, }); } - Err(e) => { - if e.detail.is_timeout().is_some() { - forks.push(Fork::Timeout(witness_block.provider, e.detail)) + Err(ErrorReport(e, _)) => { + if e.is_timeout().is_some() { + forks.push(Fork::Timeout(witness_block.provider, e)) } else { - forks.push(Fork::Faulty(witness_block, e.detail)) + forks.push(Fork::Faulty(witness_block, e)) } } } diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index 6b438b541..4d2056dea 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -318,16 +318,16 @@ impl LightClient { let root = state .light_store .highest_trusted_or_verified() - .ok_or(ErrorKind::NoInitialTrustedState)?; + .ok_or_else(error::no_initial_trusted_state_error)?; assert!(root.height() >= target_height); // Check invariant [LCV-INV-TP.1] if !is_within_trust_period(&root, self.options.trusting_period, self.clock.now()) { - bail!(ErrorKind::TrustedStateOutsideTrustingPeriod { - trusted_state: Box::new(root), - options: self.options, - }); + return Err(error::trusted_state_outside_trusting_period_error( + Box::new(root), + self.options, + )); } // Compute a range of `Height`s from `trusted_height - 1` to `target_height`, inclusive. @@ -343,15 +343,15 @@ impl LightClient { .signed_header .header .last_block_id - .ok_or_else(|| ErrorKind::MissingLastBlockId(latest.height()))?; + .ok_or_else(|| error::missing_last_block_id_error(latest.height()))?; let current_hash = self.hasher.hash_header(¤t.signed_header.header); if current_hash != latest_last_block_id.hash { - bail!(ErrorKind::InvalidAdjacentHeaders { - h1: current_hash, - h2: latest_last_block_id.hash - }); + return Err(error::invalid_adjacent_headers_error( + current_hash, + latest_last_block_id.hash, + )); } // `latest` and `current` are linked together by `last_block_id`, diff --git a/light-client/src/operations/voting_power.rs b/light-client/src/operations/voting_power.rs index 685cf1a02..4365f10e7 100644 --- a/light-client/src/operations/voting_power.rs +++ b/light-client/src/operations/voting_power.rs @@ -326,10 +326,7 @@ mod tests { ); match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::InvalidSignature(_), - trace: _, - }) => {} + Err(ErrorReport(VerificationErrorDetail::InvalidSignature(_), _)) => {} _ => panic!("expected InvalidSignature error"), } } @@ -351,10 +348,7 @@ mod tests { ); match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::InvalidSignature(_), - trace: _, - }) => {} + Err(ErrorReport(VerificationErrorDetail::InvalidSignature(_), _)) => {} _ => panic!("expected InvalidSignature error"), } } diff --git a/light-client/src/peer_list.rs b/light-client/src/peer_list.rs index 187cecf60..c3c26da25 100644 --- a/light-client/src/peer_list.rs +++ b/light-client/src/peer_list.rs @@ -239,6 +239,7 @@ impl PeerListBuilder { mod tests { use super::*; use crate::errors::ErrorDetail; + use flex_error::ErrorReport; trait BTreeSetExt { fn to_vec(&self) -> Vec; @@ -308,16 +309,12 @@ mod tests { let _ = peer_list.replace_faulty_primary(None).unwrap(); let new_primary = peer_list.replace_faulty_primary(None); match new_primary { - Err(e) => match e.detail { - ErrorDetail::NoWitnessesLeft(_) => {} - _ => panic!("expected NoWitnessesLeft error"), - }, - _ => panic!("expected NoWitnessesLeft error"), + Err(ErrorReport(ErrorDetail::NoWitnessesLeft(_), _)) => {} + _ => panic!( + "expected NoWitnessesLeft error, instead got {:?}", + new_primary + ), } - // assert_eq!( - // new_primary.err().map(|e| e.kind().clone()), - // Some(error::no_witnesses_left_error()) - // ); } #[test] diff --git a/light-client/src/predicates.rs b/light-client/src/predicates.rs index 981ec3dd7..b397e354d 100644 --- a/light-client/src/predicates.rs +++ b/light-client/src/predicates.rs @@ -337,10 +337,7 @@ mod tests { // 2. ensure header with non-monotonic bft time fails let result_err = vp.is_monotonic_bft_time(&header_one, &header_two); match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::NonMonotonicBftTime(e), - trace: _, - }) => { + Err(ErrorReport(VerificationErrorDetail::NonMonotonicBftTime(e), _)) => { assert_eq!(e.header_bft_time, header_one.time); assert_eq!(e.trusted_header_bft_time, header_two.time); } @@ -364,10 +361,7 @@ mod tests { let result_err = vp.is_monotonic_height(&header_one, &header_two); match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::NonIncreasingHeight(e), - trace: _, - }) => { + Err(ErrorReport(VerificationErrorDetail::NonIncreasingHeight(e), _)) => { assert_eq!(e.got, header_one.height); assert_eq!(e.expected, header_two.height.increment()); } @@ -396,10 +390,7 @@ mod tests { let expires_at = header.time + trusting_period; match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::NotWithinTrustPeriod(e), - trace: _, - }) => { + Err(ErrorReport(VerificationErrorDetail::NotWithinTrustPeriod(e), _)) => { assert_eq!(e.expires_at, expires_at); assert_eq!(e.now, now); } @@ -425,10 +416,7 @@ mod tests { let result_err = vp.is_header_from_past(&header, one_second, now); match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::HeaderFromTheFuture(e), - trace: _, - }) => { + Err(ErrorReport(VerificationErrorDetail::HeaderFromTheFuture(e), _)) => { assert_eq!(e.header_time, header.time); assert_eq!(e.now, now); } @@ -465,10 +453,7 @@ mod tests { let val_sets_match_err = vp.validator_sets_match(&light_block, &hasher); match val_sets_match_err { - Err(ErrorReport { - detail: VerificationErrorDetail::InvalidValidatorSet(e), - trace: _, - }) => { + Err(ErrorReport(VerificationErrorDetail::InvalidValidatorSet(e), _)) => { assert_eq!( e.header_validators_hash, light_block.signed_header.header.validators_hash @@ -486,10 +471,7 @@ mod tests { let next_val_sets_match_err = vp.next_validators_match(&light_block, &hasher); match next_val_sets_match_err { - Err(ErrorReport { - detail: VerificationErrorDetail::InvalidNextValidatorSet(e), - trace: _, - }) => { + Err(ErrorReport(VerificationErrorDetail::InvalidNextValidatorSet(e), _)) => { assert_eq!( e.header_next_validators_hash, light_block.signed_header.header.next_validators_hash @@ -529,10 +511,7 @@ mod tests { let header_hash = hasher.hash_header(&signed_header.header); match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::InvalidCommitValue(e), - trace: _, - }) => { + Err(ErrorReport(VerificationErrorDetail::InvalidCommitValue(e), _)) => { assert_eq!(e.header_hash, header_hash); assert_eq!(e.commit_hash, signed_header.commit.block_id.hash); } @@ -564,10 +543,7 @@ mod tests { let mut result_err = vp.valid_commit(&signed_header, &val_set, &commit_validator); match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::NoSignatureForCommit(_), - trace: _, - }) => {} + Err(ErrorReport(VerificationErrorDetail::NoSignatureForCommit(_), _)) => {} _ => panic!("expected ImplementationSpecific error"), } @@ -579,10 +555,7 @@ mod tests { result_err = vp.valid_commit(&signed_header, &val_set, &commit_validator); match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::MismatchPreCommitLength(e), - trace: _, - }) => { + Err(ErrorReport(VerificationErrorDetail::MismatchPreCommitLength(e), _)) => { assert_eq!(e.pre_commit_length, signed_header.commit.signatures.len()); assert_eq!(e.validator_length, val_set.validators().len()); } @@ -615,10 +588,7 @@ mod tests { ); match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::FaultySigner(e), - trace: _, - }) => { + Err(ErrorReport(VerificationErrorDetail::FaultySigner(e), _)) => { assert_eq!( e.signer, signed_header @@ -668,10 +638,7 @@ mod tests { let result_err = vp.valid_next_validator_set(&light_block3, &light_block2); match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::InvalidNextValidatorSet(e), - trace: _, - }) => { + Err(ErrorReport(VerificationErrorDetail::InvalidNextValidatorSet(e), _)) => { assert_eq!( e.header_next_validators_hash, light_block3.signed_header.header.validators_hash @@ -726,10 +693,7 @@ mod tests { ); match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::NotEnoughTrust(e), - trace: _, - }) => { + Err(ErrorReport(VerificationErrorDetail::NotEnoughTrust(e), _)) => { assert_eq!( e.tally, VotingPowerTally { @@ -773,10 +737,7 @@ mod tests { let trust_threshold = TrustThreshold::TWO_THIRDS; match result_err { - Err(ErrorReport { - detail: VerificationErrorDetail::InsufficientSignersOverlap(e), - trace: _, - }) => { + Err(ErrorReport(VerificationErrorDetail::InsufficientSignersOverlap(e), _)) => { assert_eq!( e.tally, VotingPowerTally { diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index dcfe8c60d..6ff09dcd3 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -622,11 +622,8 @@ mod tests { let (result, _) = run_bisection_test(peer_list, 10); match result { - Err(ErrorReport { - detail: error::ErrorDetail::NoWitnesses(_), - trace: _, - }) => {} - _ => panic!("expected NoWitnesses error"), + Err(ErrorReport(error::ErrorDetail::NoWitnesses(_), _)) => {} + _ => panic!("expected NoWitnesses error, instead got {:?}", result), } } @@ -648,10 +645,7 @@ mod tests { let (result, _) = run_bisection_test(peer_list, 10); match result { - Err(ErrorReport { - detail: error::ErrorDetail::Io(e), - trace: _, - }) => match e.source { + Err(ErrorReport(error::ErrorDetail::Io(e), _)) => match e.source { io::IoErrorDetail::Rpc(e) => match e.source { rpc::error::ErrorDetail::Response(e) => { assert_eq!(e.source, ResponseError::new(Code::InvalidRequest, None)) @@ -683,10 +677,7 @@ mod tests { // because MockIo returns an InvalidRequest error. This was previously // treated as a NoWitnessLeft error, which was misclassified. match result { - Err(ErrorReport { - detail: error::ErrorDetail::Io(e), - trace: _, - }) => match e.source { + Err(ErrorReport(error::ErrorDetail::Io(e), _)) => match e.source { crate::components::io::IoErrorDetail::Rpc(e) => match e.source { rpc::error::ErrorDetail::Response(e) => { assert_eq!(e.source.code(), rpc::Code::InvalidRequest) @@ -734,10 +725,7 @@ mod tests { let (result, _) = run_bisection_test(peer_list, 5); match result { - Err(ErrorReport { - detail: error::ErrorDetail::ForkDetected(_), - trace: _, - }) => {} + Err(ErrorReport(error::ErrorDetail::ForkDetected(_), _)) => {} _ => panic!("expected ForkDetected error"), } } diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index b24227db1..0f684ed0c 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -39,7 +39,7 @@ x25519-dalek = "1.1" zeroize = "1" signature = "1.3.0" aead = "0.4.1" -flex-error = "0.2.1" +flex-error = "0.3.0" # path dependencies tendermint = { path = "../tendermint", version = "0.20.0" } diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 8a963369d..0ead449ee 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -26,7 +26,7 @@ serde_bytes = "0.11" num-traits = "0.2" num-derive = "0.3" chrono = { version = "0.4", features = ["serde"] } -flex-error = "0.2.1" +flex-error = "0.3.0" [dev-dependencies] serde_json = "1.0" diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 7b5fccaad..570339d0b 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -77,7 +77,8 @@ uuid = { version = "0.8", default-features = false } subtle-encoding = { version = "0.5", features = ["bech32-preview"] } url = "2.2" walkdir = "2.3" -flex-error = "0.2.1" +flex-error = "0.3.0" +tungstenite = "0.12.0" async-trait = { version = "0.1", optional = true } async-tungstenite = { version = "0.12", features = ["tokio-runtime", "tokio-rustls"], optional = true } diff --git a/rpc/src/client.rs b/rpc/src/client.rs index 5a1ef4da9..58dcf1e20 100644 --- a/rpc/src/client.rs +++ b/rpc/src/client.rs @@ -17,7 +17,7 @@ use crate::endpoint::*; use crate::error; use crate::paging::Paging; use crate::query::Query; -use crate::{Order, Result, SimpleRequest}; +use crate::{Error, Order, SimpleRequest}; use async_trait::async_trait; use std::time::Duration; use tendermint::abci::{self, Transaction}; @@ -36,7 +36,7 @@ use tokio::time; #[async_trait] pub trait Client { /// `/abci_info`: get information about the ABCI application. - async fn abci_info(&self) -> Result { + async fn abci_info(&self) -> Result { Ok(self.perform(abci_info::Request).await?.response) } @@ -47,7 +47,7 @@ pub trait Client { data: V, height: Option, prove: bool, - ) -> Result + ) -> Result where V: Into> + Send, { @@ -58,7 +58,7 @@ pub trait Client { } /// `/block`: get block at a given height. - async fn block(&self, height: H) -> Result + async fn block(&self, height: H) -> Result where H: Into + Send, { @@ -66,12 +66,12 @@ pub trait Client { } /// `/block`: get the latest block. - async fn latest_block(&self) -> Result { + async fn latest_block(&self) -> Result { self.perform(block::Request::default()).await } /// `/block_results`: get ABCI results for a block at a particular height. - async fn block_results(&self, height: H) -> Result + async fn block_results(&self, height: H) -> Result where H: Into + Send, { @@ -80,7 +80,7 @@ pub trait Client { } /// `/block_results`: get ABCI results for the latest block. - async fn latest_block_results(&self) -> Result { + async fn latest_block_results(&self) -> Result { self.perform(block_results::Request::default()).await } @@ -89,7 +89,7 @@ pub trait Client { /// Block headers are returned in descending order (highest first). /// /// Returns at most 20 items. - async fn blockchain(&self, min: H, max: H) -> Result + async fn blockchain(&self, min: H, max: H) -> Result where H: Into + Send, { @@ -99,24 +99,33 @@ pub trait Client { } /// `/broadcast_tx_async`: broadcast a transaction, returning immediately. - async fn broadcast_tx_async(&self, tx: Transaction) -> Result { + async fn broadcast_tx_async( + &self, + tx: Transaction, + ) -> Result { self.perform(broadcast::tx_async::Request::new(tx)).await } /// `/broadcast_tx_sync`: broadcast a transaction, returning the response /// from `CheckTx`. - async fn broadcast_tx_sync(&self, tx: Transaction) -> Result { + async fn broadcast_tx_sync( + &self, + tx: Transaction, + ) -> Result { self.perform(broadcast::tx_sync::Request::new(tx)).await } /// `/broadcast_tx_commit`: broadcast a transaction, returning the response /// from `DeliverTx`. - async fn broadcast_tx_commit(&self, tx: Transaction) -> Result { + async fn broadcast_tx_commit( + &self, + tx: Transaction, + ) -> Result { self.perform(broadcast::tx_commit::Request::new(tx)).await } /// `/commit`: get block commit at a given height. - async fn commit(&self, height: H) -> Result + async fn commit(&self, height: H) -> Result where H: Into + Send, { @@ -124,13 +133,13 @@ pub trait Client { } /// `/consensus_state`: get current consensus state - async fn consensus_state(&self) -> Result { + async fn consensus_state(&self) -> Result { self.perform(consensus_state::Request::new()).await } // TODO(thane): Simplify once validators endpoint removes pagination. /// `/validators`: get validators a given height. - async fn validators(&self, height: H, paging: Paging) -> Result + async fn validators(&self, height: H, paging: Paging) -> Result where H: Into + Send, { @@ -178,41 +187,41 @@ pub trait Client { } /// `/commit`: get the latest block commit - async fn latest_commit(&self) -> Result { + async fn latest_commit(&self) -> Result { self.perform(commit::Request::default()).await } /// `/health`: get node health. /// /// Returns empty result (200 OK) on success, no response in case of an error. - async fn health(&self) -> Result<()> { + async fn health(&self) -> Result<(), Error> { self.perform(health::Request).await?; Ok(()) } /// `/genesis`: get genesis file. - async fn genesis(&self) -> Result { + async fn genesis(&self) -> Result { Ok(self.perform(genesis::Request).await?.genesis) } /// `/net_info`: obtain information about P2P and other network connections. - async fn net_info(&self) -> Result { + async fn net_info(&self) -> Result { self.perform(net_info::Request).await } /// `/status`: get Tendermint status including node info, pubkey, latest /// block hash, app hash, block height and time. - async fn status(&self) -> Result { + async fn status(&self) -> Result { self.perform(status::Request).await } /// `/broadcast_evidence`: broadcast an evidence. - async fn broadcast_evidence(&self, e: Evidence) -> Result { + async fn broadcast_evidence(&self, e: Evidence) -> Result { self.perform(evidence::Request::new(e)).await } /// `/tx`: find transaction by hash. - async fn tx(&self, hash: abci::transaction::Hash, prove: bool) -> Result { + async fn tx(&self, hash: abci::transaction::Hash, prove: bool) -> Result { self.perform(tx::Request::new(hash, prove)).await } @@ -224,14 +233,14 @@ pub trait Client { page: u32, per_page: u8, order: Order, - ) -> Result { + ) -> Result { self.perform(tx_search::Request::new(query, prove, page, per_page, order)) .await } /// Poll the `/health` endpoint until it returns a successful result or /// the given `timeout` has elapsed. - async fn wait_until_healthy(&self, timeout: T) -> Result<()> + async fn wait_until_healthy(&self, timeout: T) -> Result<(), Error> where T: Into + Send, { @@ -252,7 +261,7 @@ pub trait Client { } /// Perform a request against the RPC endpoint - async fn perform(&self, request: R) -> Result + async fn perform(&self, request: R) -> Result where R: SimpleRequest; } diff --git a/rpc/src/client/bin/main.rs b/rpc/src/client/bin/main.rs index 058897ee0..8c155e271 100644 --- a/rpc/src/client/bin/main.rs +++ b/rpc/src/client/bin/main.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use structopt::StructOpt; use tendermint::abci::{Path, Transaction}; use tendermint_rpc::{ - Client, Error, HttpClient, Paging, Result, Scheme, SubscriptionClient, Url, WebSocketClient, + error, Client, Error, HttpClient, Paging, Scheme, SubscriptionClient, Url, WebSocketClient, }; use tracing::level_filters::LevelFilter; use tracing::{error, info, warn}; @@ -153,8 +153,8 @@ async fn main() { let result = match opt.url.scheme() { Scheme::Http | Scheme::Https => http_request(opt.url, proxy_url, opt.req).await, Scheme::WebSocket | Scheme::SecureWebSocket => match opt.proxy_url { - Some(_) => Err(Error::invalid_params( - "proxies are only supported for use with HTTP clients at present", + Some(_) => Err(error::invalid_params_error( + "proxies are only supported for use with HTTP clients at present".to_string(), )), None => websocket_request(opt.url, opt.req).await, }, @@ -169,7 +169,7 @@ async fn main() { // 1. If supplied, that's the proxy URL used. // 2. If not supplied, but environment variable HTTP_PROXY or HTTPS_PROXY are // supplied, then use the appropriate variable for the URL in question. -fn get_http_proxy_url(url_scheme: Scheme, proxy_url: Option) -> Result> { +fn get_http_proxy_url(url_scheme: Scheme, proxy_url: Option) -> Result, Error> { match proxy_url { Some(u) => Ok(Some(u)), None => match url_scheme { @@ -191,7 +191,7 @@ fn get_http_proxy_url(url_scheme: Scheme, proxy_url: Option) -> Result, req: Request) -> Result<()> { +async fn http_request(url: Url, proxy_url: Option, req: Request) -> Result<(), Error> { let client = match proxy_url { Some(proxy_url) => { info!( @@ -211,7 +211,7 @@ async fn http_request(url: Url, proxy_url: Option, req: Request) -> Result< } } -async fn websocket_request(url: Url, req: Request) -> Result<()> { +async fn websocket_request(url: Url, req: Request) -> Result<(), Error> { info!("Using WebSocket client to submit request to: {}", url); let (client, driver) = WebSocketClient::new(url).await?; let driver_hdl = tokio::spawn(async move { driver.run().await }); @@ -221,18 +221,18 @@ async fn websocket_request(url: Url, req: Request) -> Result<()> { }; client.close()?; - driver_hdl - .await - .map_err(|e| Error::client_internal_error(e.to_string()))??; + driver_hdl.await.map_err(error::join_error)??; result } -async fn client_request(client: &C, req: ClientRequest) -> Result<()> +async fn client_request(client: &C, req: ClientRequest) -> Result<(), Error> where C: Client + Sync, { let result = match req { - ClientRequest::AbciInfo => serde_json::to_string_pretty(&client.abci_info().await?)?, + ClientRequest::AbciInfo => { + serde_json::to_string_pretty(&client.abci_info().await?).map_err(error::serde_error)? + } ClientRequest::AbciQuery { path, data, @@ -241,54 +241,74 @@ where } => serde_json::to_string_pretty( &client .abci_query( - path.map(|s| Path::from_str(&s)).transpose()?, + path.map(|s| Path::from_str(&s)) + .transpose() + .map_err(error::tendermint_error)?, data, height.map(Into::into), prove, ) .await?, - )?, + ) + .map_err(error::serde_error)?, ClientRequest::Block { height } => { - serde_json::to_string_pretty(&client.block(height).await?)? + serde_json::to_string_pretty(&client.block(height).await?) + .map_err(error::serde_error)? } ClientRequest::Blockchain { min, max } => { - serde_json::to_string_pretty(&client.blockchain(min, max).await?)? + serde_json::to_string_pretty(&client.blockchain(min, max).await?) + .map_err(error::serde_error)? } ClientRequest::BlockResults { height } => { - serde_json::to_string_pretty(&client.block_results(height).await?)? + serde_json::to_string_pretty(&client.block_results(height).await?) + .map_err(error::serde_error)? } ClientRequest::BroadcastTxAsync { tx } => serde_json::to_string_pretty( &client .broadcast_tx_async(Transaction::from(tx.into_bytes())) .await?, - )?, + ) + .map_err(error::serde_error)?, ClientRequest::BroadcastTxCommit { tx } => serde_json::to_string_pretty( &client .broadcast_tx_commit(Transaction::from(tx.into_bytes())) .await?, - )?, + ) + .map_err(error::serde_error)?, ClientRequest::BroadcastTxSync { tx } => serde_json::to_string_pretty( &client .broadcast_tx_sync(Transaction::from(tx.into_bytes())) .await?, - )?, + ) + .map_err(error::serde_error)?, ClientRequest::Commit { height } => { - serde_json::to_string_pretty(&client.commit(height).await?)? + serde_json::to_string_pretty(&client.commit(height).await?) + .map_err(error::serde_error)? } - ClientRequest::LatestBlock => serde_json::to_string_pretty(&client.latest_block().await?)?, + ClientRequest::LatestBlock => serde_json::to_string_pretty(&client.latest_block().await?) + .map_err(error::serde_error)?, ClientRequest::LatestBlockResults => { - serde_json::to_string_pretty(&client.latest_block_results().await?)? - } - ClientRequest::LatestCommit => { - serde_json::to_string_pretty(&client.latest_commit().await?)? + serde_json::to_string_pretty(&client.latest_block_results().await?) + .map_err(error::serde_error)? } + ClientRequest::LatestCommit => serde_json::to_string_pretty(&client.latest_commit().await?) + .map_err(error::serde_error)?, ClientRequest::ConsensusState => { - serde_json::to_string_pretty(&client.consensus_state().await?)? + serde_json::to_string_pretty(&client.consensus_state().await?) + .map_err(error::serde_error)? + } + ClientRequest::Genesis => { + serde_json::to_string_pretty(&client.genesis().await?).map_err(error::serde_error)? + } + ClientRequest::Health => { + serde_json::to_string_pretty(&client.health().await?).map_err(error::serde_error)? + } + ClientRequest::NetInfo => { + serde_json::to_string_pretty(&client.net_info().await?).map_err(error::serde_error)? + } + ClientRequest::Status => { + serde_json::to_string_pretty(&client.status().await?).map_err(error::serde_error)? } - ClientRequest::Genesis => serde_json::to_string_pretty(&client.genesis().await?)?, - ClientRequest::Health => serde_json::to_string_pretty(&client.health().await?)?, - ClientRequest::NetInfo => serde_json::to_string_pretty(&client.net_info().await?)?, - ClientRequest::Status => serde_json::to_string_pretty(&client.status().await?)?, ClientRequest::Validators { height, all, @@ -306,9 +326,11 @@ where None => Paging::Default, } }; - serde_json::to_string_pretty(&client.validators(height, paging).await?)? + serde_json::to_string_pretty(&client.validators(height, paging).await?) + .map_err(error::serde_error)? } }; + println!("{}", result); Ok(()) } diff --git a/rpc/src/client/subscription.rs b/rpc/src/client/subscription.rs index 4d69d601d..8b913dcc5 100644 --- a/rpc/src/client/subscription.rs +++ b/rpc/src/client/subscription.rs @@ -3,7 +3,7 @@ use crate::client::sync::{ChannelRx, ChannelTx}; use crate::event::Event; use crate::query::Query; -use crate::Result; +use crate::Error; use async_trait::async_trait; use futures::task::{Context, Poll}; use futures::Stream; @@ -15,7 +15,7 @@ use std::pin::Pin; #[async_trait] pub trait SubscriptionClient { /// `/subscribe`: subscribe to receive events produced by the given query. - async fn subscribe(&self, query: Query) -> Result; + async fn subscribe(&self, query: Query) -> Result; /// `/unsubscribe`: unsubscribe from events relating to the given query. /// @@ -26,15 +26,15 @@ pub trait SubscriptionClient { /// terminate them separately. /// /// [`select_all`]: https://docs.rs/futures/*/futures/stream/fn.select_all.html - async fn unsubscribe(&self, query: Query) -> Result<()>; + async fn unsubscribe(&self, query: Query) -> Result<(), Error>; /// Subscription clients will usually have long-running underlying /// transports that will need to be closed at some point. - fn close(self) -> Result<()>; + fn close(self) -> Result<(), Error>; } -pub(crate) type SubscriptionTx = ChannelTx>; -pub(crate) type SubscriptionRx = ChannelRx>; +pub(crate) type SubscriptionTx = ChannelTx>; +pub(crate) type SubscriptionRx = ChannelRx>; /// An interface that can be used to asynchronously receive [`Event`]s for a /// particular subscription. @@ -74,7 +74,7 @@ pub struct Subscription { } impl Stream for Subscription { - type Item = Result; + type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().rx.poll_next(cx) diff --git a/rpc/src/client/sync.rs b/rpc/src/client/sync.rs index 556cdd89b..3fa48ad69 100644 --- a/rpc/src/client/sync.rs +++ b/rpc/src/client/sync.rs @@ -11,7 +11,7 @@ use futures::Stream; use pin_project::pin_project; use tokio::sync::mpsc; -use crate::{error, Result}; +use crate::{error, Error}; /// Constructor for an unbounded channel. pub fn unbounded() -> (ChannelTx, ChannelRx) { @@ -27,7 +27,7 @@ pub fn unbounded() -> (ChannelTx, ChannelRx) { pub struct ChannelTx(mpsc::UnboundedSender); impl ChannelTx { - pub fn send(&self, value: T) -> Result<()> { + pub fn send(&self, value: T) -> Result<(), Error> { self.0.send(value).map_err(error::send_error) } } diff --git a/rpc/src/client/transport/http.rs b/rpc/src/client/transport/http.rs index c6ac8d128..148b1b80e 100644 --- a/rpc/src/client/transport/http.rs +++ b/rpc/src/client/transport/http.rs @@ -1,7 +1,7 @@ //! HTTP-based transport for Tendermint RPC Client. use crate::client::Client; -use crate::{error, Error, Result, Scheme, SimpleRequest, Url}; +use crate::{error, Error, Scheme, SimpleRequest, Url}; use async_trait::async_trait; use std::convert::{TryFrom, TryInto}; use std::str::FromStr; @@ -41,7 +41,7 @@ pub struct HttpClient { impl HttpClient { /// Construct a new Tendermint RPC HTTP/S client connecting to the given /// URL. - pub fn new(url: U) -> Result + pub fn new(url: U) -> Result where U: TryInto, { @@ -62,7 +62,7 @@ impl HttpClient { /// attempt to connect using the [HTTP CONNECT] method. /// /// [HTTP CONNECT]: https://en.wikipedia.org/wiki/HTTP_tunnel - pub fn new_with_proxy(url: U, proxy_url: P) -> Result + pub fn new_with_proxy(url: U, proxy_url: P) -> Result where U: TryInto, P: TryInto, @@ -81,7 +81,7 @@ impl HttpClient { #[async_trait] impl Client for HttpClient { - async fn perform(&self, request: R) -> Result + async fn perform(&self, request: R) -> Result where R: SimpleRequest, { @@ -98,7 +98,7 @@ pub struct HttpClientUrl(Url); impl TryFrom for HttpClientUrl { type Error = Error; - fn try_from(value: Url) -> Result { + fn try_from(value: Url) -> Result { match value.scheme() { Scheme::Http | Scheme::Https => Ok(Self(value)), _ => Err(error::invalid_url_error(value)), @@ -109,7 +109,7 @@ impl TryFrom for HttpClientUrl { impl FromStr for HttpClientUrl { type Err = Error; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { let url: Url = s.parse()?; url.try_into() } @@ -118,7 +118,7 @@ impl FromStr for HttpClientUrl { impl TryFrom<&str> for HttpClientUrl { type Error = Error; - fn try_from(value: &str) -> Result { + fn try_from(value: &str) -> Result { value.parse() } } @@ -126,7 +126,7 @@ impl TryFrom<&str> for HttpClientUrl { impl TryFrom for HttpClientUrl { type Error = Error; - fn try_from(value: net::Address) -> Result { + fn try_from(value: net::Address) -> Result { match value { net::Address::Tcp { peer_id: _, @@ -147,7 +147,7 @@ impl From for Url { impl TryFrom for hyper::Uri { type Error = Error; - fn try_from(value: HttpClientUrl) -> Result { + fn try_from(value: HttpClientUrl) -> Result { value .0 .to_string() @@ -157,7 +157,7 @@ impl TryFrom for hyper::Uri { } mod sealed { - use crate::{error, Response, Result, SimpleRequest}; + use crate::{error, Error, Response, SimpleRequest}; use hyper::body::Buf; use hyper::client::connect::Connect; use hyper::client::HttpConnector; @@ -183,7 +183,7 @@ mod sealed { where C: Connect + Clone + Send + Sync + 'static, { - pub async fn perform(&self, request: R) -> Result + pub async fn perform(&self, request: R) -> Result where R: SimpleRequest, { @@ -204,7 +204,7 @@ mod sealed { pub fn build_request( &self, request: R, - ) -> Result> { + ) -> Result, Error> { let request_body = request.into_json(); let mut request = hyper::Request::builder() @@ -253,7 +253,7 @@ mod sealed { )) } - pub fn new_http_proxy(uri: Uri, proxy_uri: Uri) -> Result { + pub fn new_http_proxy(uri: Uri, proxy_uri: Uri) -> Result { let proxy = Proxy::new(Intercept::All, proxy_uri); let proxy_connector = ProxyConnector::from_proxy(HttpConnector::new(), proxy).map_err(error::io_error)?; @@ -263,7 +263,7 @@ mod sealed { ))) } - pub fn new_https_proxy(uri: Uri, proxy_uri: Uri) -> Result { + pub fn new_https_proxy(uri: Uri, proxy_uri: Uri) -> Result { let proxy = Proxy::new(Intercept::All, proxy_uri); let proxy_connector = ProxyConnector::from_proxy(HttpsConnector::with_native_roots(), proxy) @@ -275,7 +275,7 @@ mod sealed { ))) } - pub async fn perform(&self, request: R) -> Result + pub async fn perform(&self, request: R) -> Result where R: SimpleRequest, { @@ -288,7 +288,7 @@ mod sealed { } } - async fn response_to_string(response: hyper::Response) -> Result { + async fn response_to_string(response: hyper::Response) -> Result { let mut response_body = String::new(); hyper::body::aggregate(response.into_body()) .await diff --git a/rpc/src/client/transport/mock.rs b/rpc/src/client/transport/mock.rs index c8ec4015f..19faa545c 100644 --- a/rpc/src/client/transport/mock.rs +++ b/rpc/src/client/transport/mock.rs @@ -6,7 +6,7 @@ use crate::client::transport::router::SubscriptionRouter; use crate::event::Event; use crate::query::Query; use crate::utils::uuid_str; -use crate::{error, Client, Method, Request, Response, Result, Subscription, SubscriptionClient}; +use crate::{error, Client, Error, Method, Request, Response, Subscription, SubscriptionClient}; use async_trait::async_trait; use std::collections::HashMap; @@ -54,7 +54,7 @@ pub struct MockClient { #[async_trait] impl Client for MockClient { - async fn perform(&self, request: R) -> Result + async fn perform(&self, request: R) -> Result where R: Request, { @@ -90,7 +90,7 @@ impl MockClient { #[async_trait] impl SubscriptionClient for MockClient { - async fn subscribe(&self, query: Query) -> Result { + async fn subscribe(&self, query: Query) -> Result { let id = uuid_str(); let (subs_tx, subs_rx) = unbounded(); let (result_tx, mut result_rx) = unbounded(); @@ -104,14 +104,14 @@ impl SubscriptionClient for MockClient { Ok(Subscription::new(id, query, subs_rx)) } - async fn unsubscribe(&self, query: Query) -> Result<()> { + async fn unsubscribe(&self, query: Query) -> Result<(), Error> { let (result_tx, mut result_rx) = unbounded(); self.driver_tx .send(DriverCommand::Unsubscribe { query, result_tx })?; result_rx.recv().await.unwrap() } - fn close(self) -> Result<()> { + fn close(self) -> Result<(), Error> { Ok(()) } } @@ -122,11 +122,11 @@ pub enum DriverCommand { id: String, query: Query, subscription_tx: SubscriptionTx, - result_tx: ChannelTx>, + result_tx: ChannelTx>, }, Unsubscribe { query: Query, - result_tx: ChannelTx>, + result_tx: ChannelTx>, }, Publish(Box), Terminate, @@ -146,7 +146,7 @@ impl MockClientDriver { } } - pub async fn run(mut self) -> Result<()> { + pub async fn run(mut self) -> Result<(), Error> { loop { tokio::select! { Some(cmd) = self.rx.recv() => match cmd { @@ -168,13 +168,13 @@ impl MockClientDriver { id: String, query: Query, subscription_tx: SubscriptionTx, - result_tx: ChannelTx>, + result_tx: ChannelTx>, ) { self.router.add(id, query, subscription_tx); result_tx.send(Ok(())).unwrap(); } - fn unsubscribe(&mut self, query: Query, result_tx: ChannelTx>) { + fn unsubscribe(&mut self, query: Query, result_tx: ChannelTx>) { self.router.remove_by_query(query); result_tx.send(Ok(())).unwrap(); } @@ -190,7 +190,7 @@ impl MockClientDriver { /// [`MockClient`]: struct.MockClient.html pub trait MockRequestMatcher: Send + Sync { /// Provide the corresponding response for the given request (if any). - fn response_for(&self, request: R) -> Option> + fn response_for(&self, request: R) -> Option> where R: Request; } @@ -200,11 +200,11 @@ pub trait MockRequestMatcher: Send + Sync { /// /// [`MockRequestMatcher`]: trait.MockRequestMatcher.html pub struct MockRequestMethodMatcher { - mappings: HashMap Result + Send + Sync>>, + mappings: HashMap Result + Send + Sync>>, } impl MockRequestMatcher for MockRequestMethodMatcher { - fn response_for(&self, request: R) -> Option> + fn response_for(&self, request: R) -> Option> where R: Request, { @@ -232,7 +232,7 @@ impl MockRequestMethodMatcher { pub fn map( mut self, method: Method, - response: impl Fn() -> Result + Send + Sync + 'static, + response: impl Fn() -> Result + Send + Sync + 'static, ) -> Self { self.mappings.insert(method, Box::new(response)); self @@ -305,8 +305,8 @@ mod test { } // Here each subscription's channel is drained. - let subs1_events = subs1_events.collect::>>().await; - let subs2_events = subs2_events.collect::>>().await; + let subs1_events = subs1_events.collect::>>().await; + let subs2_events = subs2_events.collect::>>().await; assert_eq!(3, subs1_events.len()); assert_eq!(3, subs2_events.len()); diff --git a/rpc/src/client/transport/websocket.rs b/rpc/src/client/transport/websocket.rs index dc0256358..f9bdd8550 100644 --- a/rpc/src/client/transport/websocket.rs +++ b/rpc/src/client/transport/websocket.rs @@ -8,7 +8,8 @@ use crate::event::Event; use crate::query::Query; use crate::request::Wrapper; use crate::{ - response, Client, Error, Id, Request, Response, Result, Scheme, SimpleRequest, Subscription, + error::{self, Error}, + response, Client, Id, Request, Response, Scheme, SimpleRequest, Subscription, SubscriptionClient, Url, }; use async_trait::async_trait; @@ -120,7 +121,7 @@ const PING_INTERVAL: Duration = Duration::from_secs((RECV_TIMEOUT_SECONDS * 9) / /// ``` /// /// [tendermint-websocket-ping]: https://github.com/tendermint/tendermint/blob/309e29c245a01825fc9630103311fd04de99fa5e/rpc/jsonrpc/server/ws_handler.go#L28 -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct WebSocketClient { inner: sealed::WebSocketClient, } @@ -130,7 +131,7 @@ impl WebSocketClient { /// Tendermint node's RPC endpoint. /// /// Supports both `ws://` and `wss://` protocols. - pub async fn new(url: U) -> Result<(Self, WebSocketClientDriver)> + pub async fn new(url: U) -> Result<(Self, WebSocketClientDriver), Error> where U: TryInto, { @@ -146,7 +147,7 @@ impl WebSocketClient { #[async_trait] impl Client for WebSocketClient { - async fn perform(&self, request: R) -> Result<::Response> + async fn perform(&self, request: R) -> Result<::Response, Error> where R: SimpleRequest, { @@ -156,15 +157,15 @@ impl Client for WebSocketClient { #[async_trait] impl SubscriptionClient for WebSocketClient { - async fn subscribe(&self, query: Query) -> Result { + async fn subscribe(&self, query: Query) -> Result { self.inner.subscribe(query).await } - async fn unsubscribe(&self, query: Query) -> Result<()> { + async fn unsubscribe(&self, query: Query) -> Result<(), Error> { self.inner.unsubscribe(query).await } - fn close(self) -> Result<()> { + fn close(self) -> Result<(), Error> { self.inner.close() } } @@ -178,10 +179,10 @@ pub struct WebSocketClientUrl(Url); impl TryFrom for WebSocketClientUrl { type Error = Error; - fn try_from(value: Url) -> Result { + fn try_from(value: Url) -> Result { match value.scheme() { Scheme::WebSocket | Scheme::SecureWebSocket => Ok(Self(value)), - _ => Err(Error::invalid_params(&format!( + _ => Err(error::invalid_params_error(format!( "cannot use URL {} with WebSocket clients", value ))), @@ -192,7 +193,7 @@ impl TryFrom for WebSocketClientUrl { impl FromStr for WebSocketClientUrl { type Err = Error; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { let url: Url = s.parse()?; url.try_into() } @@ -201,7 +202,7 @@ impl FromStr for WebSocketClientUrl { impl TryFrom<&str> for WebSocketClientUrl { type Error = Error; - fn try_from(value: &str) -> Result { + fn try_from(value: &str) -> Result { value.parse() } } @@ -209,15 +210,15 @@ impl TryFrom<&str> for WebSocketClientUrl { impl TryFrom for WebSocketClientUrl { type Error = Error; - fn try_from(value: net::Address) -> Result { + fn try_from(value: net::Address) -> Result { match value { net::Address::Tcp { peer_id: _, host, port, } => format!("ws://{}:{}/websocket", host, port).parse(), - net::Address::Unix { .. } => Err(Error::invalid_params( - "only TCP-based node addresses are supported", + net::Address::Unix { .. } => Err(error::invalid_params_error( + "only TCP-based node addresses are supported".to_string(), )), } } @@ -238,7 +239,7 @@ mod sealed { use crate::query::Query; use crate::request::Wrapper; use crate::utils::uuid_str; - use crate::{Error, Response, Result, SimpleRequest, Subscription, Url}; + use crate::{error, Error, Response, SimpleRequest, Subscription, Url}; use async_tungstenite::tokio::{connect_async, connect_async_with_tls_connector}; use tracing::debug; @@ -258,7 +259,7 @@ mod sealed { /// different variants of this type. /// /// [`async-tungstenite`]: https://crates.io/crates/async-tungstenite - #[derive(Debug, Clone)] + #[derive(Debug)] pub struct AsyncTungsteniteClient { cmd_tx: ChannelTx, _client_type: std::marker::PhantomData, @@ -273,10 +274,10 @@ mod sealed { /// this driver becomes the responsibility of the client owner, and must be /// executed in a separate asynchronous context to the client to ensure it /// doesn't block the client. - pub async fn new(url: Url) -> Result<(Self, WebSocketClientDriver)> { + pub async fn new(url: Url) -> Result<(Self, WebSocketClientDriver), Error> { let url = url.to_string(); debug!("Connecting to unsecure WebSocket endpoint: {}", url); - let (stream, _response) = connect_async(url).await?; + let (stream, _response) = connect_async(url).await.map_err(error::tungstenite_error)?; let (cmd_tx, cmd_rx) = unbounded(); let driver = WebSocketClientDriver::new(stream, cmd_rx); Ok(( @@ -299,12 +300,14 @@ mod sealed { /// this driver becomes the responsibility of the client owner, and must be /// executed in a separate asynchronous context to the client to ensure it /// doesn't block the client. - pub async fn new(url: Url) -> Result<(Self, WebSocketClientDriver)> { + pub async fn new(url: Url) -> Result<(Self, WebSocketClientDriver), Error> { let url = url.to_string(); debug!("Connecting to secure WebSocket endpoint: {}", url); // Not supplying a connector means async_tungstenite will create the // connector for us. - let (stream, _response) = connect_async_with_tls_connector(url, None).await?; + let (stream, _response) = connect_async_with_tls_connector(url, None) + .await + .map_err(error::tungstenite_error)?; let (cmd_tx, cmd_rx) = unbounded(); let driver = WebSocketClientDriver::new(stream, cmd_rx); Ok(( @@ -318,16 +321,11 @@ mod sealed { } impl AsyncTungsteniteClient { - fn send_cmd(&self, cmd: DriverCommand) -> Result<()> { - self.cmd_tx.send(cmd).map_err(|e| { - Error::client_internal_error(format!( - "failed to send command to client driver: {}", - e - )) - }) + fn send_cmd(&self, cmd: DriverCommand) -> Result<(), Error> { + self.cmd_tx.send(cmd) } - pub async fn perform(&self, request: R) -> Result + pub async fn perform(&self, request: R) -> Result where R: SimpleRequest, { @@ -341,7 +339,7 @@ mod sealed { response_tx, }))?; let response = response_rx.recv().await.ok_or_else(|| { - Error::client_internal_error( + error::client_internal_error( "failed to hear back from WebSocket driver".to_string(), ) })??; @@ -349,7 +347,7 @@ mod sealed { R::Response::from_string(response) } - pub async fn subscribe(&self, query: Query) -> Result { + pub async fn subscribe(&self, query: Query) -> Result { let (subscription_tx, subscription_rx) = unbounded(); let (response_tx, mut response_rx) = unbounded(); // By default we use UUIDs to differentiate subscriptions @@ -362,21 +360,21 @@ mod sealed { }))?; // Make sure our subscription request went through successfully. let _ = response_rx.recv().await.ok_or_else(|| { - Error::client_internal_error( + error::client_internal_error( "failed to hear back from WebSocket driver".to_string(), ) })??; Ok(Subscription::new(id, query, subscription_rx)) } - pub async fn unsubscribe(&self, query: Query) -> Result<()> { + pub async fn unsubscribe(&self, query: Query) -> Result<(), Error> { let (response_tx, mut response_rx) = unbounded(); self.send_cmd(DriverCommand::Unsubscribe(UnsubscribeCommand { query: query.to_string(), response_tx, }))?; let _ = response_rx.recv().await.ok_or_else(|| { - Error::client_internal_error( + error::client_internal_error( "failed to hear back from WebSocket driver".to_string(), ) })??; @@ -384,31 +382,31 @@ mod sealed { } /// Signals to the driver that it must terminate. - pub fn close(self) -> Result<()> { + pub fn close(self) -> Result<(), Error> { self.send_cmd(DriverCommand::Terminate) } } /// Allows us to erase the type signatures associated with the different /// WebSocket client variants. - #[derive(Debug, Clone)] + #[derive(Debug)] pub enum WebSocketClient { Unsecure(AsyncTungsteniteClient), Secure(AsyncTungsteniteClient), } impl WebSocketClient { - pub async fn new_unsecure(url: Url) -> Result<(Self, WebSocketClientDriver)> { + pub async fn new_unsecure(url: Url) -> Result<(Self, WebSocketClientDriver), Error> { let (client, driver) = AsyncTungsteniteClient::::new(url).await?; Ok((Self::Unsecure(client), driver)) } - pub async fn new_secure(url: Url) -> Result<(Self, WebSocketClientDriver)> { + pub async fn new_secure(url: Url) -> Result<(Self, WebSocketClientDriver), Error> { let (client, driver) = AsyncTungsteniteClient::::new(url).await?; Ok((Self::Secure(client), driver)) } - pub async fn perform(&self, request: R) -> Result + pub async fn perform(&self, request: R) -> Result where R: SimpleRequest, { @@ -418,21 +416,21 @@ mod sealed { } } - pub async fn subscribe(&self, query: Query) -> Result { + pub async fn subscribe(&self, query: Query) -> Result { match self { WebSocketClient::Unsecure(c) => c.subscribe(query).await, WebSocketClient::Secure(c) => c.subscribe(query).await, } } - pub async fn unsubscribe(&self, query: Query) -> Result<()> { + pub async fn unsubscribe(&self, query: Query) -> Result<(), Error> { match self { WebSocketClient::Unsecure(c) => c.unsubscribe(query).await, WebSocketClient::Secure(c) => c.unsubscribe(query).await, } } - pub fn close(self) -> Result<()> { + pub fn close(self) -> Result<(), Error> { match self { WebSocketClient::Unsecure(c) => c.close(), WebSocketClient::Secure(c) => c.close(), @@ -443,7 +441,7 @@ mod sealed { // The different types of commands that can be sent from the WebSocketClient to // the driver. -#[derive(Debug, Clone)] +#[derive(Debug)] enum DriverCommand { // Initiate a subscription request. Subscribe(SubscribeCommand), @@ -454,7 +452,7 @@ enum DriverCommand { Terminate, } -#[derive(Debug, Clone)] +#[derive(Debug)] struct SubscribeCommand { // The desired ID for the outgoing JSON-RPC request. id: String, @@ -463,18 +461,18 @@ struct SubscribeCommand { // Where to send subscription events. subscription_tx: SubscriptionTx, // Where to send the result of the subscription request. - response_tx: ChannelTx>, + response_tx: ChannelTx>, } -#[derive(Debug, Clone)] +#[derive(Debug)] struct UnsubscribeCommand { // The query from which to unsubscribe. query: String, // Where to send the result of the unsubscribe request. - response_tx: ChannelTx>, + response_tx: ChannelTx>, } -#[derive(Debug, Clone)] +#[derive(Debug)] struct SimpleRequestCommand { // The desired ID for the outgoing JSON-RPC request. Technically we // could extract this from the wrapped request, but that would mean @@ -483,7 +481,7 @@ struct SimpleRequestCommand { // The wrapped and serialized JSON-RPC request. wrapped_request: String, // Where to send the result of the simple request. - response_tx: ChannelTx>, + response_tx: ChannelTx>, } #[derive(Serialize, Deserialize, Debug, Clone)] @@ -519,7 +517,7 @@ impl WebSocketClientDriver { /// Executes the WebSocket driver, which manages the underlying WebSocket /// transport. - pub async fn run(mut self) -> Result<()> { + pub async fn run(mut self) -> Result<(), Error> { let mut ping_interval = tokio::time::interval_at(Instant::now().add(PING_INTERVAL), PING_INTERVAL); @@ -536,8 +534,9 @@ impl WebSocketClientDriver { self.handle_incoming_msg(msg).await? }, Err(e) => return Err( - Error::websocket_error( - format!("failed to read from WebSocket connection: {}", e), + error::web_socket_error( + "failed to read from WebSocket connection".to_string(), + e ), ), }, @@ -549,22 +548,19 @@ impl WebSocketClientDriver { }, _ = ping_interval.tick() => self.ping().await?, _ = &mut recv_timeout => { - return Err(Error::websocket_error(format!( - "reading from WebSocket connection timed out after {} seconds", - RECV_TIMEOUT.as_secs() - ))); + return Err(error::web_socket_timeout_error(RECV_TIMEOUT)); } } } } - async fn send_msg(&mut self, msg: Message) -> Result<()> { + async fn send_msg(&mut self, msg: Message) -> Result<(), Error> { self.stream.send(msg).await.map_err(|e| { - Error::websocket_error(format!("failed to write to WebSocket connection: {}", e)) + error::web_socket_error("failed to write to WebSocket connection".to_string(), e) }) } - async fn send_request(&mut self, wrapper: Wrapper) -> Result<()> + async fn send_request(&mut self, wrapper: Wrapper) -> Result<(), Error> where R: Request, { @@ -574,7 +570,7 @@ impl WebSocketClientDriver { .await } - async fn subscribe(&mut self, cmd: SubscribeCommand) -> Result<()> { + async fn subscribe(&mut self, cmd: SubscribeCommand) -> Result<(), Error> { // If we already have an active subscription for the given query, // there's no need to initiate another one. Just add this subscription // to the router. @@ -599,7 +595,7 @@ impl WebSocketClientDriver { Ok(()) } - async fn unsubscribe(&mut self, cmd: UnsubscribeCommand) -> Result<()> { + async fn unsubscribe(&mut self, cmd: UnsubscribeCommand) -> Result<(), Error> { // Terminate all subscriptions for this query immediately. This // prioritizes acknowledgement of the caller's wishes over networking // problems. @@ -623,7 +619,7 @@ impl WebSocketClientDriver { Ok(()) } - async fn simple_request(&mut self, cmd: SimpleRequestCommand) -> Result<()> { + async fn simple_request(&mut self, cmd: SimpleRequestCommand) -> Result<(), Error> { if let Err(e) = self .send_msg(Message::Text(cmd.wrapped_request.clone())) .await @@ -636,7 +632,7 @@ impl WebSocketClientDriver { Ok(()) } - async fn handle_incoming_msg(&mut self, msg: Message) -> Result<()> { + async fn handle_incoming_msg(&mut self, msg: Message) -> Result<(), Error> { match msg { Message::Text(s) => self.handle_text_msg(s).await, Message::Ping(v) => self.pong(v).await, @@ -644,7 +640,7 @@ impl WebSocketClientDriver { } } - async fn handle_text_msg(&mut self, msg: String) -> Result<()> { + async fn handle_text_msg(&mut self, msg: String) -> Result<(), Error> { if let Ok(ev) = Event::from_string(&msg) { self.publish_event(ev).await; return Ok(()); @@ -693,7 +689,7 @@ impl WebSocketClientDriver { &mut self, pending_cmd: DriverCommand, response: String, - ) -> Result<()> { + ) -> Result<(), Error> { match pending_cmd { DriverCommand::Subscribe(cmd) => { let (id, query, subscription_tx, response_tx) = @@ -707,15 +703,15 @@ impl WebSocketClientDriver { } } - async fn pong(&mut self, v: Vec) -> Result<()> { + async fn pong(&mut self, v: Vec) -> Result<(), Error> { self.send_msg(Message::Pong(v)).await } - async fn ping(&mut self) -> Result<()> { + async fn ping(&mut self) -> Result<(), Error> { self.send_msg(Message::Ping(Vec::new())).await } - async fn close(mut self) -> Result<()> { + async fn close(mut self) -> Result<(), Error> { self.send_msg(Message::Close(Some(CloseFrame { code: CloseCode::Normal, reason: Cow::from("client closed WebSocket connection"), @@ -750,8 +746,8 @@ mod test { // Interface to a driver that manages all incoming WebSocket connections. struct TestServer { node_addr: net::Address, - driver_hdl: JoinHandle>, - terminate_tx: ChannelTx>, + driver_hdl: JoinHandle>, + terminate_tx: ChannelTx>, event_tx: ChannelTx, } @@ -776,11 +772,11 @@ mod test { } } - fn publish_event(&mut self, ev: Event) -> Result<()> { + fn publish_event(&mut self, ev: Event) -> Result<(), Error> { self.event_tx.send(ev) } - async fn terminate(self) -> Result<()> { + async fn terminate(self) -> Result<(), Error> { self.terminate_tx.send(Ok(())).unwrap(); self.driver_hdl.await.unwrap() } @@ -790,7 +786,7 @@ mod test { struct TestServerDriver { listener: TcpListener, event_rx: ChannelRx, - terminate_rx: ChannelRx>, + terminate_rx: ChannelRx>, handlers: Vec, } @@ -798,7 +794,7 @@ mod test { fn new( listener: TcpListener, event_rx: ChannelRx, - terminate_rx: ChannelRx>, + terminate_rx: ChannelRx>, ) -> Self { Self { listener, @@ -808,7 +804,7 @@ mod test { } } - async fn run(mut self) -> Result<()> { + async fn run(mut self) -> Result<(), Error> { loop { tokio::select! { Some(ev) = self.event_rx.recv() => self.publish_event(ev), @@ -850,8 +846,8 @@ mod test { // Interface to a driver that manages a single incoming WebSocket // connection. struct TestServerHandler { - driver_hdl: JoinHandle>, - terminate_tx: ChannelTx>, + driver_hdl: JoinHandle>, + terminate_tx: ChannelTx>, event_tx: ChannelTx, } @@ -874,7 +870,7 @@ mod test { let _ = self.event_tx.send(ev); } - async fn terminate(self) -> Result<()> { + async fn terminate(self) -> Result<(), Error> { self.terminate_tx.send(Ok(()))?; self.driver_hdl.await.unwrap() } @@ -884,7 +880,7 @@ mod test { struct TestServerHandlerDriver { conn: WebSocketStream>, event_rx: ChannelRx, - terminate_rx: ChannelRx>, + terminate_rx: ChannelRx>, // A mapping of subscription queries to subscription IDs for this // connection. subscriptions: HashMap, @@ -894,7 +890,7 @@ mod test { fn new( conn: WebSocketStream>, event_rx: ChannelRx, - terminate_rx: ChannelRx>, + terminate_rx: ChannelRx>, ) -> Self { Self { conn, @@ -904,7 +900,7 @@ mod test { } } - async fn run(mut self) -> Result<()> { + async fn run(mut self) -> Result<(), Error> { loop { tokio::select! { Some(msg) = self.conn.next() => { @@ -929,7 +925,7 @@ mod test { let _ = self.send(Id::Str(subs_id), ev).await; } - async fn handle_incoming_msg(&mut self, msg: Message) -> Option> { + async fn handle_incoming_msg(&mut self, msg: Message) -> Option> { match msg { Message::Text(s) => self.handle_incoming_text_msg(s).await, Message::Ping(v) => { @@ -944,7 +940,7 @@ mod test { } } - async fn handle_incoming_text_msg(&mut self, msg: String) -> Option> { + async fn handle_incoming_text_msg(&mut self, msg: String) -> Option> { match serde_json::from_str::(&msg) { Ok(json_msg) => { if let Some(json_method) = json_msg.get("method") { diff --git a/rpc/src/error.rs b/rpc/src/error.rs index 517ad7be9..ae98c5b18 100644 --- a/rpc/src/error.rs +++ b/rpc/src/error.rs @@ -11,6 +11,7 @@ use crate::response_error::ResponseError; use crate::rpc_url::Url; define_error! { + #[derive(Debug, Clone)] Error { Response [ DisplayError ] @@ -40,10 +41,20 @@ define_error! { { message: String } + [ DisplayOnly ] | e | { format_args!("web socket error: {}", e.message) }, + WebSocketTimeout + { + timeout: Duration + } + | e | { + format_args!("reading from WebSocket connection timed out after {} seconds", + e.timeout.as_secs()) + }, + MethodNotFound { method: String @@ -103,6 +114,10 @@ define_error! { [ DisplayOnly ] | _ | { "invalid URI" }, + Tendermint + [ tendermint::Error ] + | _ | { "tendermint error" }, + ParseInt [ DisplayOnly ] | _ | { "error parsing integer" }, @@ -133,6 +148,14 @@ define_error! { [ DisplayOnly ] | _ | { "parse error" }, + Tungstenite + [ DisplayOnly ] + | _ | { "tungstenite error" }, + + Join + [ DisplayOnly ] + | _ | { "join error" }, + MalformedJson | _ | { "server returned malformatted JSON (no 'result' or 'error')" }, diff --git a/rpc/src/lib.rs b/rpc/src/lib.rs index 6f12038a0..b55977c96 100644 --- a/rpc/src/lib.rs +++ b/rpc/src/lib.rs @@ -49,7 +49,6 @@ pub mod query; pub mod request; pub mod response; pub mod response_error; -mod result; mod rpc_url; mod utils; mod version; @@ -62,6 +61,5 @@ pub use paging::{PageNumber, Paging, PerPage}; pub use request::{Request, SimpleRequest}; pub use response::Response; pub use response_error::{Code, ResponseError}; -pub use result::Result; pub use rpc_url::{Scheme, Url}; pub use version::Version; diff --git a/rpc/src/query.rs b/rpc/src/query.rs index 691f6b108..37871da2f 100644 --- a/rpc/src/query.rs +++ b/rpc/src/query.rs @@ -7,7 +7,7 @@ // TODO(thane): These warnings are generated by the PEG for some reason. Try to fix and remove. #![allow(clippy::redundant_closure_call, clippy::unit_arg)] -use crate::{error, Error, Result}; +use crate::{error, Error}; use chrono::{Date, DateTime, FixedOffset, Utc}; use std::fmt; use std::str::FromStr; @@ -228,7 +228,7 @@ impl fmt::Display for EventType { impl FromStr for EventType { type Err = Error; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { match s { "NewBlock" => Ok(Self::NewBlock), "Tx" => Ok(Self::Tx), diff --git a/rpc/src/result.rs b/rpc/src/result.rs deleted file mode 100644 index 48bf5873e..000000000 --- a/rpc/src/result.rs +++ /dev/null @@ -1,4 +0,0 @@ -use crate::Error; - -/// RPC client-related result type alias. -pub type Result = std::result::Result; diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index e0f6d7d2b..799cfad45 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -312,10 +312,7 @@ fn incoming_fixtures() { let res = endpoint::block::Response::from_string(&content); match res { - Err(ErrorReport { - detail: ErrorDetail::Response(e), - trace: _, - }) => { + Err(ErrorReport(ErrorDetail::Response(e), _)) => { let response = e.source; assert_eq!(response.code(), Code::InternalError); assert_eq!(response.message(), "Internal error"); @@ -734,10 +731,7 @@ fn incoming_fixtures() { let result = endpoint::subscribe::Response::from_string(content); match result { - Err(ErrorReport { - detail: ErrorDetail::Response(e), - trace: _, - }) => { + Err(ErrorReport(ErrorDetail::Response(e), _)) => { let response = e.source; assert_eq!(response.code(), Code::InternalError); @@ -751,10 +745,7 @@ fn incoming_fixtures() { let result = endpoint::subscribe::Response::from_string(content); match result { - Err(ErrorReport { - detail: ErrorDetail::Response(e), - trace: _, - }) => { + Err(ErrorReport(ErrorDetail::Response(e), _)) => { let response = e.source; assert_eq!(response.code(), Code::ParseError); assert_eq!(response.message(), "Parse error. Invalid JSON"); diff --git a/rpc/tests/parse_response.rs b/rpc/tests/parse_response.rs index acd0e3703..7ebc2d018 100644 --- a/rpc/tests/parse_response.rs +++ b/rpc/tests/parse_response.rs @@ -296,10 +296,7 @@ fn jsonrpc_error() { let result = endpoint::blockchain::Response::from_string(&read_json_fixture("error")); match result { - Err(ErrorReport { - detail: ErrorDetail::Response(e), - trace: _, - }) => { + Err(ErrorReport(ErrorDetail::Response(e), _)) => { let response = e.source; assert_eq!(response.code(), RpcCode::InternalError); assert_eq!(response.message(), "Internal error"); diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index a5600a29d..fbd00f7f2 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -55,7 +55,7 @@ tendermint-proto = { version = "0.20.0", path = "../proto" } toml = { version = "0.5" } url = { version = "2.2" } zeroize = { version = "1.1", features = ["zeroize_derive"] } -flex-error = "0.2.1" +flex-error = "0.3.0" time = "0.1.40" k256 = { version = "0.9", optional = true, features = ["ecdsa"] } diff --git a/tendermint/src/chain/id.rs b/tendermint/src/chain/id.rs index 246b0d84d..3221441ae 100644 --- a/tendermint/src/chain/id.rs +++ b/tendermint/src/chain/id.rs @@ -151,7 +151,7 @@ mod tests { #[test] fn rejects_empty_chain_ids() { - match "".parse::().unwrap_err().detail { + match "".parse::().unwrap_err().detail() { error::ErrorDetail::Length(_) => {} _ => panic!("expected length error"), } @@ -160,7 +160,7 @@ mod tests { #[test] fn rejects_overlength_chain_ids() { let overlong_id = String::from_utf8(vec![b'x'; MAX_LENGTH + 1]).unwrap(); - match overlong_id.parse::().unwrap_err().detail { + match overlong_id.parse::().unwrap_err().detail() { error::ErrorDetail::Length(_) => {} _ => panic!("expected length error"), } diff --git a/tendermint/src/error.rs b/tendermint/src/error.rs index a062e41f4..0ee9bbb2a 100644 --- a/tendermint/src/error.rs +++ b/tendermint/src/error.rs @@ -4,12 +4,12 @@ use crate::account; use crate::vote; use alloc::string::String; use core::num::TryFromIntError; -use flex_error::{define_error, DisplayError, DisplayOnly}; +use flex_error::{define_error, DisplayOnly}; use std::io::Error as IoError; use time::OutOfRangeError; define_error! { - #[derive(Debug)] + #[derive(Debug, Clone)] Error { Crypto |_| { format_args!("cryptographic error") }, @@ -36,11 +36,11 @@ define_error! { ParseInt { data: String } - [ DisplayError] + [ DisplayOnly] | e | { format_args!("error parsing int data: {}", e.data) }, ParseUrl - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("error parsing url error") }, Protocol @@ -48,7 +48,7 @@ define_error! { |_| { format_args!("protocol error") }, OutOfRange - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("value out of range") }, SignatureInvalid @@ -59,18 +59,18 @@ define_error! { |_| { format_args!("invalid message type") }, NegativeHeight - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("negative height") }, NegativeRound - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("negative round") }, NegativePolRound |_| { format_args!("negative POL round") }, NegativeValidatorIndex - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("negative validator index") }, InvalidHashSize @@ -86,7 +86,7 @@ define_error! { |_| { format_args!("invalid signature ID length") }, IntegerOverflow - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("integer overflow") }, NoVoteFound @@ -145,7 +145,7 @@ define_error! { |_| { format_args!("invalid block id flag") }, NegativePower - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("negative power") }, UnsupportedKeyType @@ -165,7 +165,7 @@ define_error! { |_| { format_args!("invalid version parameters") }, NegativeMaxAgeNum - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("negative max_age_num_blocks") }, MissingMaxAgeDuration @@ -176,11 +176,11 @@ define_error! { |e| { format_args!("proposer with address '{0}' no found in validator set", e.account) }, ChronoParse - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("chrono parse error") }, SubtleEncoding - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("subtle encoding error") }, SerdeJson @@ -188,11 +188,11 @@ define_error! { |_| { format_args!("serde json error") }, Toml - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("toml de error") }, Signature - [ DisplayError ] + [ DisplayOnly ] |_| { format_args!("signature error") }, } } diff --git a/tendermint/src/timeout.rs b/tendermint/src/timeout.rs index 0d6319595..7a4dbef19 100644 --- a/tendermint/src/timeout.rs +++ b/tendermint/src/timeout.rs @@ -99,7 +99,7 @@ mod tests { #[test] fn reject_no_units() { - match "123".parse::().unwrap_err().detail { + match "123".parse::().unwrap_err().detail() { error::ErrorDetail::Parse(_) => {} _ => panic!("expected parse error to be returned"), } From 45f95ad5b437ec4def351087b02a44cc81b8876c Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 9 Jul 2021 15:42:21 +0200 Subject: [PATCH 12/25] Fix failing tests --- rpc/src/client/transport/websocket.rs | 14 +++++++------- tools/kvstore-test/src/lib.rs | 5 ++--- tools/kvstore-test/tests/light-client.rs | 1 - tools/kvstore-test/tests/tendermint.rs | 1 - tools/proto-compiler/src/functions.rs | 4 ++-- tools/rpc-probe/src/client.rs | 1 - 6 files changed, 11 insertions(+), 15 deletions(-) diff --git a/rpc/src/client/transport/websocket.rs b/rpc/src/client/transport/websocket.rs index f9bdd8550..31c6a8227 100644 --- a/rpc/src/client/transport/websocket.rs +++ b/rpc/src/client/transport/websocket.rs @@ -121,7 +121,7 @@ const PING_INTERVAL: Duration = Duration::from_secs((RECV_TIMEOUT_SECONDS * 9) / /// ``` /// /// [tendermint-websocket-ping]: https://github.com/tendermint/tendermint/blob/309e29c245a01825fc9630103311fd04de99fa5e/rpc/jsonrpc/server/ws_handler.go#L28 -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct WebSocketClient { inner: sealed::WebSocketClient, } @@ -259,7 +259,7 @@ mod sealed { /// different variants of this type. /// /// [`async-tungstenite`]: https://crates.io/crates/async-tungstenite - #[derive(Debug)] + #[derive(Debug, Clone)] pub struct AsyncTungsteniteClient { cmd_tx: ChannelTx, _client_type: std::marker::PhantomData, @@ -389,7 +389,7 @@ mod sealed { /// Allows us to erase the type signatures associated with the different /// WebSocket client variants. - #[derive(Debug)] + #[derive(Debug, Clone)] pub enum WebSocketClient { Unsecure(AsyncTungsteniteClient), Secure(AsyncTungsteniteClient), @@ -441,7 +441,7 @@ mod sealed { // The different types of commands that can be sent from the WebSocketClient to // the driver. -#[derive(Debug)] +#[derive(Debug, Clone)] enum DriverCommand { // Initiate a subscription request. Subscribe(SubscribeCommand), @@ -452,7 +452,7 @@ enum DriverCommand { Terminate, } -#[derive(Debug)] +#[derive(Debug, Clone)] struct SubscribeCommand { // The desired ID for the outgoing JSON-RPC request. id: String, @@ -464,7 +464,7 @@ struct SubscribeCommand { response_tx: ChannelTx>, } -#[derive(Debug)] +#[derive(Debug, Clone)] struct UnsubscribeCommand { // The query from which to unsubscribe. query: String, @@ -472,7 +472,7 @@ struct UnsubscribeCommand { response_tx: ChannelTx>, } -#[derive(Debug)] +#[derive(Debug, Clone)] struct SimpleRequestCommand { // The desired ID for the outgoing JSON-RPC request. Technically we // could extract this from the wrapped request, but that would mean diff --git a/tools/kvstore-test/src/lib.rs b/tools/kvstore-test/src/lib.rs index f1a7e29e0..eaed3171b 100644 --- a/tools/kvstore-test/src/lib.rs +++ b/tools/kvstore-test/src/lib.rs @@ -5,11 +5,10 @@ //! cargo test //! This will execute all integration tests against the RPC endpoint. //! -//! Option 2: the docker daemon is installed and accessible on the machine where the test will happen -//! for example: on a developer machine +//! Option 2: the docker daemon is installed and accessible on the machine where the test will +//! happen for example: on a developer machine //! Run: //! cargo make //! This will start a docker container with Tendermint and attach port 26657 to the host machine. //! Then it will run all tests against the freshly created endpoint. //! Make sure you installed cargo-make by running `cargo install cargo-make` first. -//! diff --git a/tools/kvstore-test/tests/light-client.rs b/tools/kvstore-test/tests/light-client.rs index ac57bc2c5..fe46707a5 100644 --- a/tools/kvstore-test/tests/light-client.rs +++ b/tools/kvstore-test/tests/light-client.rs @@ -11,7 +11,6 @@ //! cargo make //! //! (Make sure you install cargo-make using `cargo install cargo-make` first.) -//! use tendermint_light_client::{ builder::{LightClientBuilder, SupervisorBuilder}, diff --git a/tools/kvstore-test/tests/tendermint.rs b/tools/kvstore-test/tests/tendermint.rs index 2f47416bd..dc2d3cbb6 100644 --- a/tools/kvstore-test/tests/tendermint.rs +++ b/tools/kvstore-test/tests/tendermint.rs @@ -13,7 +13,6 @@ /// cargo make /// /// (Make sure you install cargo-make using `cargo install cargo-make` first.) -/// mod rpc { use std::cmp::min; diff --git a/tools/proto-compiler/src/functions.rs b/tools/proto-compiler/src/functions.rs index c5a05bffa..a3e992049 100644 --- a/tools/proto-compiler/src/functions.rs +++ b/tools/proto-compiler/src/functions.rs @@ -122,8 +122,8 @@ fn find_reference_or_commit<'a>( tried_origin = true; if try_reference.is_err() { // Remote branch not found, last chance: try as a commit ID - // Note: Oid::from_str() currently does an incorrect conversion and cuts the second half of the ID. - // We are falling back on Oid::from_bytes() for now. + // Note: Oid::from_str() currently does an incorrect conversion and cuts the second half + // of the ID. We are falling back on Oid::from_bytes() for now. let commitish_vec = hex::decode(commitish).unwrap_or_else(|_| hex::decode_upper(commitish).unwrap()); return ( diff --git a/tools/rpc-probe/src/client.rs b/tools/rpc-probe/src/client.rs index 96cb2282e..905cbe5e2 100644 --- a/tools/rpc-probe/src/client.rs +++ b/tools/rpc-probe/src/client.rs @@ -132,7 +132,6 @@ enum DriverCommand { Terminate, } -#[derive(Debug)] pub struct ClientDriver { stream: WebSocketStream, cmd_rx: DriverCommandRx, From f79236f983b297807d45c0e3e99fcf5aeab8854c Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 9 Jul 2021 14:57:27 +0000 Subject: [PATCH 13/25] Fix more failures --- light-client/src/components/io.rs | 10 +++++-- light-client/src/errors.rs | 12 +++++---- light-client/src/evidence.rs | 9 +++---- p2p/Cargo.toml | 1 - p2p/src/error.rs | 9 +++++-- p2p/src/secret_connection/protocol.rs | 2 +- rpc/src/client/transport/mock.rs | 20 ++++++-------- rpc/src/error.rs | 39 ++++++++++++++++++++------- tendermint/Cargo.toml | 1 - 9 files changed, 64 insertions(+), 39 deletions(-) diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index 3f076c10e..e43712414 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -1,6 +1,6 @@ //! Provides an interface and a default implementation of the `Io` component -use flex_error::{define_error, DisplayOnly, TraceError}; +use flex_error::{define_error, TraceError}; use std::time::Duration; #[cfg(feature = "rpc-client")] @@ -10,6 +10,12 @@ use tendermint_rpc as rpc; use crate::types::{Height, LightBlock}; +#[cfg(feature = "tokio")] +type TimeoutError = flex_error::DisplayOnly; + +#[cfg(not(feature = "tokio"))] +type TimeoutError = flex_error::NoSource; + /// Type for selecting either a specific height or the latest one pub enum AtHeight { /// A specific height @@ -46,7 +52,7 @@ define_error! { Timeout { duration: Duration } - [ DisplayOnly ] + [ TimeoutError ] | e | { format_args!("task timed out after {} ms", e.duration.as_millis()) diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index 714f450bb..085f4c712 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -14,6 +14,12 @@ use crate::{ }; use flex_error::{define_error, DisplayError, TraceError}; +#[cfg(feature = "sled")] +type SledError = TraceError; + +#[cfg(not(feature = "sled"))] +type SledError = flex_error::NoSource; + define_error! { #[derive(Debug)] Error { @@ -21,10 +27,6 @@ define_error! { [ IoError ] | _ | { "io error" }, - Store - [ TraceError ] - | _ | { "store error" }, - NoPrimary | _ | { "no primary" }, @@ -105,7 +107,7 @@ define_error! { | _ | { "internal channel disconnected" }, Sled - [ TraceError ] + [ SledError ] | _ | { "sled error" }, SerdeCbor diff --git a/light-client/src/evidence.rs b/light-client/src/evidence.rs index ad3b74ffd..20f32b1c0 100644 --- a/light-client/src/evidence.rs +++ b/light-client/src/evidence.rs @@ -1,9 +1,6 @@ //! Fork evidence data structures and interfaces. -use crate::{ - components::io::{self, IoError}, - types::PeerId, -}; +use crate::{components::io::IoError, types::PeerId}; use tendermint::abci::transaction::Hash; @@ -51,7 +48,7 @@ mod prod { self.timeout, async move { client.broadcast_evidence(e).await }, )? - .map_err(io::rpc_error)?; + .map_err(crate::components::io::rpc_error)?; Ok(response.hash) } @@ -71,7 +68,7 @@ mod prod { #[pre(self.peer_map.contains_key(&peer))] fn rpc_client_for(&self, peer: PeerId) -> Result { let peer_addr = self.peer_map.get(&peer).unwrap().to_owned(); - Ok(rpc::HttpClient::new(peer_addr).map_err(io::rpc_error)?) + rpc::HttpClient::new(peer_addr).map_err(crate::components::io::rpc_error) } } } diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index 0f684ed0c..f48d1b963 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -21,7 +21,6 @@ description = """ test = false [features] -default = ["amino"] amino = ["prost-amino", "prost-amino-derive"] [dependencies] diff --git a/p2p/src/error.rs b/p2p/src/error.rs index f3c01fe54..38ac222cc 100644 --- a/p2p/src/error.rs +++ b/p2p/src/error.rs @@ -2,9 +2,14 @@ use flex_error::{define_error, DisplayError, TraceError}; use prost::DecodeError; -use prost_amino::DecodeError as AminoDecodeError; use signature::Error as SignatureError; +#[cfg(feature = "amino")] +type AminoDecodeError = TraceError; + +#[cfg(not(feature = "amino"))] +type AminoDecodeError = flex_error::NoSource; + define_error! { Error { Crypto @@ -31,7 +36,7 @@ define_error! { | _ | { "malformed handshake message (protocol version mismatch?)" }, AminoDecode - [ TraceError ] + [ AminoDecodeError ] | _ | { "malformed handshake message (protocol version mismatch?)" }, MissingSecret diff --git a/p2p/src/secret_connection/protocol.rs b/p2p/src/secret_connection/protocol.rs index cb416d55d..59487f6d2 100644 --- a/p2p/src/secret_connection/protocol.rs +++ b/p2p/src/secret_connection/protocol.rs @@ -217,7 +217,7 @@ impl Version { #[allow(clippy::unused_self)] #[cfg(not(feature = "amino"))] - fn decode_auth_signature_amino(self, _: &[u8]) -> Result { + fn decode_auth_signature_amino(self, _: &[u8]) -> Result { panic!("attempted to decode auth signature using amino, but 'amino' feature is not present") } } diff --git a/rpc/src/client/transport/mock.rs b/rpc/src/client/transport/mock.rs index 19faa545c..fc2330150 100644 --- a/rpc/src/client/transport/mock.rs +++ b/rpc/src/client/transport/mock.rs @@ -199,8 +199,9 @@ pub trait MockRequestMatcher: Send + Sync { /// requests with specific methods to responses. /// /// [`MockRequestMatcher`]: trait.MockRequestMatcher.html +#[derive(Debug)] pub struct MockRequestMethodMatcher { - mappings: HashMap Result + Send + Sync>>, + mappings: HashMap>, } impl MockRequestMatcher for MockRequestMethodMatcher { @@ -208,9 +209,9 @@ impl MockRequestMatcher for MockRequestMethodMatcher { where R: Request, { - self.mappings.get(&request.method()).map(|res| match res() { + self.mappings.get(&request.method()).map(|res| match res { Ok(json) => R::Response::from_string(json), - Err(e) => Err(e), + Err(e) => Err(e.clone()), }) } } @@ -229,12 +230,8 @@ impl MockRequestMethodMatcher { /// /// Successful responses must be JSON-encoded. #[allow(dead_code)] - pub fn map( - mut self, - method: Method, - response: impl Fn() -> Result + Send + Sync + 'static, - ) -> Self { - self.mappings.insert(method, Box::new(response)); + pub fn map(mut self, method: Method, response: Result) -> Self { + self.mappings.insert(method, response); self } } @@ -263,10 +260,9 @@ mod test { async fn mock_client() { let abci_info_fixture = read_json_fixture("abci_info").await; let block_fixture = read_json_fixture("block").await; - let matcher = MockRequestMethodMatcher::default() - .map(Method::AbciInfo, move || Ok(abci_info_fixture.clone())) - .map(Method::Block, move || Ok(block_fixture.clone())); + .map(Method::AbciInfo, Ok(abci_info_fixture)) + .map(Method::Block, Ok(block_fixture)); let (client, driver) = MockClient::new(matcher); let driver_hdl = tokio::spawn(async move { driver.run().await }); diff --git a/rpc/src/error.rs b/rpc/src/error.rs index ae98c5b18..1089d48e2 100644 --- a/rpc/src/error.rs +++ b/rpc/src/error.rs @@ -1,15 +1,35 @@ //! JSON-RPC error types use flex_error::{define_error, DisplayError, DisplayOnly}; -use http::uri::InvalidUri; -use http::Error as HttpError; -use hyper::Error as HyperError; use std::time::Duration; -use tokio::sync::mpsc::error::SendError; use crate::response_error::ResponseError; use crate::rpc_url::Url; +#[cfg(feature = "http")] +type HttpError = flex_error::DisplayOnly; + +#[cfg(not(feature = "http"))] +type HttpError = flex_error::NoSource; + +#[cfg(feature = "http")] +type InvalidUriError = flex_error::DisplayOnly; + +#[cfg(not(feature = "http"))] +type InvalidUriError = flex_error::NoSource; + +#[cfg(feature = "hyper")] +type HyperError = flex_error::DisplayOnly; + +#[cfg(not(feature = "hyper"))] +type HyperError = flex_error::NoSource; + +#[cfg(feature = "tokio")] +type JoinError = flex_error::DisplayOnly; + +#[cfg(not(feature = "tokio"))] +type JoinError = flex_error::NoSource; + define_error! { #[derive(Debug, Clone)] Error { @@ -22,11 +42,11 @@ define_error! { | _ | { "I/O error" }, Http - [ DisplayOnly ] + [ HttpError ] | _ | { "HTTP error" }, Hyper - [ DisplayOnly ] + [ HyperError ] | _ | { "HTTP error" }, InvalidParams @@ -111,7 +131,7 @@ define_error! { }, InvalidUri - [ DisplayOnly ] + [ InvalidUriError ] | _ | { "invalid URI" }, Tendermint @@ -153,7 +173,7 @@ define_error! { | _ | { "tungstenite error" }, Join - [ DisplayOnly ] + [ JoinError ] | _ | { "join error" }, MalformedJson @@ -180,6 +200,7 @@ define_error! { } } -pub fn send_error(_: SendError) -> Error { +#[cfg(feature = "tokio")] +pub fn send_error(_: tokio::sync::mpsc::error::SendError) -> Error { channel_send_error() } diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index fbd00f7f2..1eb1c36f9 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -62,7 +62,6 @@ k256 = { version = "0.9", optional = true, features = ["ecdsa"] } ripemd160 = { version = "0.9", optional = true } [features] -default = ["secp256k1"] secp256k1 = ["k256", "ripemd160"] [dev-dependencies] From 67e275baf72058c26b829521b26de4920dd4d9a8 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 9 Jul 2021 15:59:06 +0000 Subject: [PATCH 14/25] Fix tungstenite error under wasm target --- rpc/Cargo.toml | 2 -- rpc/src/error.rs | 10 ++++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 570339d0b..9a5bbc09e 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -78,8 +78,6 @@ subtle-encoding = { version = "0.5", features = ["bech32-preview"] } url = "2.2" walkdir = "2.3" flex-error = "0.3.0" -tungstenite = "0.12.0" - async-trait = { version = "0.1", optional = true } async-tungstenite = { version = "0.12", features = ["tokio-runtime", "tokio-rustls"], optional = true } futures = { version = "0.3", optional = true } diff --git a/rpc/src/error.rs b/rpc/src/error.rs index 1089d48e2..d622e6f46 100644 --- a/rpc/src/error.rs +++ b/rpc/src/error.rs @@ -30,6 +30,12 @@ type JoinError = flex_error::DisplayOnly; #[cfg(not(feature = "tokio"))] type JoinError = flex_error::NoSource; +#[cfg(feature = "async-tungstenite")] +type TungsteniteError = flex_error::DisplayOnly; + +#[cfg(not(feature = "async-tungstenite"))] +type TungsteniteError = flex_error::NoSource; + define_error! { #[derive(Debug, Clone)] Error { @@ -61,7 +67,7 @@ define_error! { { message: String } - [ DisplayOnly ] + [ TungsteniteError ] | e | { format_args!("web socket error: {}", e.message) }, @@ -169,7 +175,7 @@ define_error! { | _ | { "parse error" }, Tungstenite - [ DisplayOnly ] + [ TungsteniteError ] | _ | { "tungstenite error" }, Join From 5763a285fce7c60fe1bcfeae07911ebe297c83a0 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Wed, 14 Jul 2021 16:44:58 +0200 Subject: [PATCH 15/25] Fix incoming_fixtures test --- rpc/src/error.rs | 2 +- rpc/tests/kvstore_fixtures.rs | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/rpc/src/error.rs b/rpc/src/error.rs index d622e6f46..6532e8806 100644 --- a/rpc/src/error.rs +++ b/rpc/src/error.rs @@ -168,7 +168,7 @@ define_error! { Serde [ DisplayOnly ] - | _ | { "parse error" }, + | _ | { "serde parse error" }, ParseUrl [ DisplayOnly ] diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index c7596a2e1..bbc552b1e 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -745,16 +745,8 @@ fn incoming_fixtures() { let result = endpoint::subscribe::Response::from_string(content); match result { - Err(ErrorReport(ErrorDetail::Response(e), _)) => { - let response = e.source; - assert_eq!(response.code(), Code::ParseError); - assert_eq!(response.message(), "Parse error. Invalid JSON"); - assert_eq!( - response.data().unwrap(), - "missing field `jsonrpc` at line 1 column 2" - ); - } - _ => panic!("expected Response error"), + Err(ErrorReport(ErrorDetail::Serde(_), _)) => {} + _ => panic!("expected Serde parse error, instead got {:?}", result), } } "subscribe_newblock_0" => { From 17506fa9e4739608061b7593cc656a4a71daa1fa Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Tue, 20 Jul 2021 07:33:35 +0000 Subject: [PATCH 16/25] Fix conflict --- rpc/src/response.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpc/src/response.rs b/rpc/src/response.rs index b10f58a88..d94e61dda 100644 --- a/rpc/src/response.rs +++ b/rpc/src/response.rs @@ -54,7 +54,7 @@ where /// Convert this wrapper into the underlying error, if any pub fn into_error(self) -> Option { - self.error + self.error.map(error::response_error) } /// Convert this wrapper into a result type From 515f31ebbaed7e59a7d9b8192ea11e871d34878f Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Fri, 23 Jul 2021 15:48:12 +0200 Subject: [PATCH 17/25] Update flex-error to v0.4.0 --- Cargo.toml | 3 ++ abci/Cargo.toml | 2 +- abci/src/application/kvstore.rs | 8 +-- abci/src/client.rs | 16 +++--- abci/src/codec.rs | 16 +++--- abci/src/error.rs | 6 ++- abci/src/server.rs | 11 ++-- light-client/Cargo.toml | 5 +- light-client/src/builder/light_client.rs | 18 +++---- light-client/src/builder/supervisor.rs | 4 +- light-client/src/components/io.rs | 8 +-- light-client/src/components/verifier.rs | 3 +- light-client/src/errors.rs | 12 +++-- light-client/src/evidence.rs | 4 +- light-client/src/fork_detector.rs | 6 +-- light-client/src/light_client.rs | 28 +++++----- .../src/operations/commit_validator.rs | 8 +-- light-client/src/operations/voting_power.rs | 19 ++++--- light-client/src/peer_list.rs | 10 ++-- light-client/src/predicates.rs | 50 ++++++++--------- light-client/src/store/sled/utils.rs | 14 ++--- light-client/src/supervisor.rs | 48 ++++++++--------- light-client/src/tests.rs | 4 +- light-client/src/utils/block_on.rs | 6 +-- p2p/Cargo.toml | 2 +- p2p/src/secret_connection.rs | 42 +++++++-------- p2p/src/secret_connection/protocol.rs | 12 ++--- p2p/src/secret_connection/public_key.rs | 7 +-- proto/Cargo.toml | 2 +- proto/src/error.rs | 14 ++--- proto/src/lib.rs | 14 ++--- rpc/Cargo.toml | 5 +- rpc/src/client.rs | 3 +- rpc/src/client/bin/main.rs | 53 +++++++++---------- rpc/src/client/sync.rs | 4 +- rpc/src/client/transport/http.rs | 30 ++++------- rpc/src/client/transport/mock.rs | 4 +- rpc/src/client/transport/websocket.rs | 31 +++++------ rpc/src/endpoint/consensus_state.rs | 32 +++++------ rpc/src/error.rs | 17 ++++-- rpc/src/method.rs | 4 +- rpc/src/order.rs | 4 +- rpc/src/paging.rs | 10 ++-- rpc/src/query.rs | 4 +- rpc/src/response.rs | 12 ++--- rpc/src/rpc_url.rs | 14 ++--- rpc/src/version.rs | 4 +- rpc/tests/kvstore_fixtures.rs | 12 +++-- rpc/tests/parse_response.rs | 9 ++-- tendermint/Cargo.toml | 2 +- tendermint/src/abci/gas.rs | 4 +- tendermint/src/abci/transaction/hash.rs | 6 +-- tendermint/src/account.rs | 9 ++-- tendermint/src/block.rs | 17 +++--- tendermint/src/block/commit.rs | 4 +- tendermint/src/block/commit_sig.rs | 28 ++++------ tendermint/src/block/header.rs | 14 ++--- tendermint/src/block/height.rs | 10 ++-- tendermint/src/block/id.rs | 6 +-- tendermint/src/block/meta.rs | 6 +-- tendermint/src/block/parts.rs | 10 ++-- tendermint/src/block/round.rs | 10 ++-- tendermint/src/block/signed_header.rs | 8 +-- tendermint/src/block/size.rs | 4 +- tendermint/src/chain/id.rs | 11 ++-- tendermint/src/config.rs | 21 +++----- tendermint/src/config/node_key.rs | 11 ++-- tendermint/src/config/priv_validator_key.rs | 11 ++-- tendermint/src/consensus/params.rs | 8 +-- tendermint/src/evidence.rs | 29 ++++------ tendermint/src/hash.rs | 10 ++-- tendermint/src/net.rs | 16 ++---- tendermint/src/node/id.rs | 8 +-- tendermint/src/proposal.rs | 4 +- tendermint/src/proposal/canonical_proposal.rs | 9 ++-- tendermint/src/proposal/msg_type.rs | 4 +- tendermint/src/proposal/sign_proposal.rs | 4 +- tendermint/src/public_key.rs | 27 ++++------ tendermint/src/signature.rs | 4 +- tendermint/src/time.rs | 6 +-- tendermint/src/timeout.rs | 8 +-- tendermint/src/validator.rs | 8 +-- tendermint/src/vote.rs | 8 +-- tendermint/src/vote/canonical_vote.rs | 9 ++-- tendermint/src/vote/power.rs | 8 ++- tendermint/src/vote/sign_vote.rs | 7 +-- tendermint/src/vote/validator_index.rs | 12 ++--- 87 files changed, 479 insertions(+), 556 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 01dfb0a40..4c541afbf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,6 @@ members = [ exclude = [ "proto-compiler" ] + +[patch.crates-io] +flex-error = { git = "https://github.com/informalsystems/flex-error", rev = "97cc1ce7fa798c9c30813bae718c52f4140cd003" } diff --git a/abci/Cargo.toml b/abci/Cargo.toml index d92fd7648..68ab45310 100644 --- a/abci/Cargo.toml +++ b/abci/Cargo.toml @@ -32,7 +32,7 @@ eyre = "0.6" prost = "0.7" tendermint-proto = { version = "0.20.0", path = "../proto" } tracing = "0.1" -flex-error = "0.3.0" +flex-error = "0.4.0" structopt = { version = "0.3", optional = true } tracing-subscriber = { version = "0.2", optional = true } diff --git a/abci/src/application/kvstore.rs b/abci/src/application/kvstore.rs index 7df30841b..4c174fca2 100644 --- a/abci/src/application/kvstore.rs +++ b/abci/src/application/kvstore.rs @@ -1,7 +1,7 @@ //! In-memory key/value store ABCI application. use crate::codec::{encode_varint, MAX_VARINT_LENGTH}; -use crate::{error, Application, Error}; +use crate::{Application, Error}; use bytes::BytesMut; use std::collections::HashMap; use std::sync::mpsc::{channel, Receiver, Sender}; @@ -204,7 +204,7 @@ impl KeyValueStoreDriver { /// Run the driver in the current thread (blocking). pub fn run(mut self) -> Result<(), Error> { loop { - let cmd = self.cmd_rx.recv().map_err(error::channel_recv_error)?; + let cmd = self.cmd_rx.recv().map_err(Error::channel_recv)?; match cmd { Command::GetInfo { result_tx } => { channel_send(&result_tx, (self.height, self.app_hash.clone()))? @@ -261,9 +261,9 @@ enum Command { } fn channel_send(tx: &Sender, value: T) -> Result<(), Error> { - tx.send(value).map_err(error::send_error) + tx.send(value).map_err(Error::send) } fn channel_recv(rx: &Receiver) -> Result { - rx.recv().map_err(error::channel_recv_error) + rx.recv().map_err(Error::channel_recv) } diff --git a/abci/src/client.rs b/abci/src/client.rs index f3dc0eae0..c5146f88a 100644 --- a/abci/src/client.rs +++ b/abci/src/client.rs @@ -1,7 +1,7 @@ //! Blocking ABCI client. use crate::codec::ClientCodec; -use crate::{error, Error}; +use crate::Error; use std::net::{TcpStream, ToSocketAddrs}; use tendermint_proto::abci::{ request, response, RequestApplySnapshotChunk, RequestBeginBlock, RequestCheckTx, RequestCommit, @@ -32,7 +32,7 @@ impl ClientBuilder { /// Client constructor that attempts to connect to the given network /// address. pub fn connect(self, addr: A) -> Result { - let stream = TcpStream::connect(addr).map_err(error::io_error)?; + let stream = TcpStream::connect(addr).map_err(Error::io)?; Ok(Client { codec: ClientCodec::new(stream, self.read_buf_size), }) @@ -56,11 +56,9 @@ macro_rules! perform { ($self:expr, $type:ident, $req:expr) => { match $self.perform(request::Value::$type($req))? { response::Value::$type(r) => Ok(r), - r => Err(error::unexpected_server_response_type_error( - stringify!($type).to_string(), - r, - ) - .into()), + r => { + Err(Error::unexpected_server_response_type(stringify!($type).to_string(), r).into()) + } } }; } @@ -154,7 +152,7 @@ impl Client { let res = self .codec .next() - .ok_or_else(error::server_connection_terminated_error)??; - res.value.ok_or_else(error::malformed_server_response_error) + .ok_or_else(Error::server_connection_terminated)??; + res.value.ok_or_else(Error::malformed_server_response) } } diff --git a/abci/src/codec.rs b/abci/src/codec.rs index ddd19ddc1..170d384e6 100644 --- a/abci/src/codec.rs +++ b/abci/src/codec.rs @@ -10,7 +10,7 @@ use std::io::{Read, Write}; use std::marker::PhantomData; use tendermint_proto::abci::{Request, Response}; -use crate::error::{self, Error}; +use crate::error::Error; /// The maximum number of bytes we expect in a varint. We use this to check if /// we're encountering a decoding error for a varint. @@ -76,7 +76,7 @@ where // more let bytes_read = match self.stream.read(self.read_window.as_mut()) { Ok(br) => br, - Err(e) => return Some(Err(error::io_error(e))), + Err(e) => return Some(Err(Error::io(e))), }; if bytes_read == 0 { // The underlying stream terminated @@ -100,10 +100,10 @@ where let bytes_written = self .stream .write(self.write_buf.as_ref()) - .map_err(error::io_error)?; + .map_err(Error::io)?; if bytes_written == 0 { - return Err(error::io_error(std::io::Error::new( + return Err(Error::io(std::io::Error::new( std::io::ErrorKind::WriteZero, "failed to write to underlying stream", ))); @@ -111,7 +111,7 @@ where self.write_buf.advance(bytes_written); } - self.stream.flush().map_err(error::io_error)?; + self.stream.flush().map_err(Error::io)?; Ok(()) } @@ -124,7 +124,7 @@ where B: BufMut, { let mut buf = BytesMut::new(); - message.encode(&mut buf).map_err(error::encode_error)?; + message.encode(&mut buf).map_err(Error::encode)?; let buf = buf.freeze(); encode_varint(buf.len() as u64, &mut dst); @@ -156,7 +156,7 @@ where src.advance(delim_len + (encoded_len as usize)); let mut result_bytes = BytesMut::from(tmp.split_to(encoded_len as usize).as_ref()); - let res = M::decode(&mut result_bytes).map_err(error::decode_error)?; + let res = M::decode(&mut result_bytes).map_err(Error::decode)?; Ok(Some(res)) } @@ -169,6 +169,6 @@ pub fn encode_varint(val: u64, mut buf: &mut B) { } pub fn decode_varint(mut buf: &mut B) -> Result { - let len = prost::encoding::decode_varint(&mut buf).map_err(error::decode_error)?; + let len = prost::encoding::decode_varint(&mut buf).map_err(Error::decode)?; Ok(len >> 1) } diff --git a/abci/src/error.rs b/abci/src/error.rs index 19e4589a7..dcdbb7e17 100644 --- a/abci/src/error.rs +++ b/abci/src/error.rs @@ -42,6 +42,8 @@ define_error! { } } -pub fn send_error(_e: std::sync::mpsc::SendError) -> Error { - channel_send_error() +impl Error { + pub fn send(_e: std::sync::mpsc::SendError) -> Error { + Error::channel_send() + } } diff --git a/abci/src/server.rs b/abci/src/server.rs index 91eccc727..623143307 100644 --- a/abci/src/server.rs +++ b/abci/src/server.rs @@ -2,10 +2,7 @@ use crate::application::RequestDispatcher; use crate::codec::ServerCodec; -use crate::{ - error::{self, Error}, - Application, -}; +use crate::{error::Error, Application}; use std::net::{TcpListener, TcpStream, ToSocketAddrs}; use std::thread; use tracing::{error, info}; @@ -39,8 +36,8 @@ impl ServerBuilder { Addr: ToSocketAddrs, App: Application, { - let listener = TcpListener::bind(addr).map_err(error::io_error)?; - let local_addr = listener.local_addr().map_err(error::io_error)?.to_string(); + let listener = TcpListener::bind(addr).map_err(Error::io)?; + let local_addr = listener.local_addr().map_err(Error::io)?.to_string(); info!("ABCI server running at {}", local_addr); Ok(Server { app, @@ -76,7 +73,7 @@ impl Server { /// Initiate a blocking listener for incoming connections. pub fn listen(self) -> Result<(), Error> { loop { - let (stream, addr) = self.listener.accept().map_err(error::io_error)?; + let (stream, addr) = self.listener.accept().map_err(Error::io)?; let addr = addr.to_string(); info!("Incoming connection from: {}", addr); self.spawn_client_handler(stream, addr); diff --git a/light-client/Cargo.toml b/light-client/Cargo.toml index 8df937600..01cadf9bb 100644 --- a/light-client/Cargo.toml +++ b/light-client/Cargo.toml @@ -30,7 +30,8 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["rpc-client", "lightstore-sled"] +default = ["std", "rpc-client", "lightstore-sled"] +std = [] rpc-client = ["tokio", "tendermint-rpc/http-client"] secp256k1 = ["tendermint/secp256k1", "tendermint-rpc/secp256k1"] lightstore-sled = ["sled"] @@ -50,7 +51,7 @@ serde_derive = "1.0.106" sled = { version = "0.34.3", optional = true } static_assertions = "1.1.0" tokio = { version = "1.0", features = ["rt"], optional = true } -flex-error = "0.3.0" +flex-error = "0.4.0" [dev-dependencies] tendermint-testgen = { path = "../testgen" } diff --git a/light-client/src/builder/light_client.rs b/light-client/src/builder/light_client.rs index 23ef5489f..c02846d19 100644 --- a/light-client/src/builder/light_client.rs +++ b/light-client/src/builder/light_client.rs @@ -2,7 +2,7 @@ use tendermint::{block::Height, Hash}; -use crate::builder::error::{self, Error}; +use crate::builder::error::Error; use crate::components::clock::Clock; use crate::components::io::{AtHeight, Io}; use crate::components::scheduler::Scheduler; @@ -133,7 +133,7 @@ impl LightClientBuilder { let trusted_state = self .light_store .highest_trusted_or_verified() - .ok_or_else(error::no_trusted_state_in_store_error)?; + .ok_or_else(Error::no_trusted_state_in_store)?; self.trust_light_block(trusted_state) } @@ -147,10 +147,10 @@ impl LightClientBuilder { let trusted_state = self .io .fetch_light_block(AtHeight::At(trusted_height)) - .map_err(error::io_error)?; + .map_err(Error::io)?; if trusted_state.height() != trusted_height { - return Err(error::height_mismatch_error( + return Err(Error::height_mismatch( trusted_height, trusted_state.height(), )); @@ -159,7 +159,7 @@ impl LightClientBuilder { let header_hash = self.hasher.hash_header(&trusted_state.signed_header.header); if header_hash != trusted_hash { - return Err(error::hash_mismatch_error(trusted_hash, header_hash)); + return Err(Error::hash_mismatch(trusted_hash, header_hash)); } self.trust_light_block(trusted_state) @@ -171,19 +171,19 @@ impl LightClientBuilder { self.predicates .is_within_trust_period(header, self.options.trusting_period, now) - .map_err(error::invalid_light_block_error)?; + .map_err(Error::invalid_light_block)?; self.predicates .is_header_from_past(header, self.options.clock_drift, now) - .map_err(error::invalid_light_block_error)?; + .map_err(Error::invalid_light_block)?; self.predicates .validator_sets_match(light_block, &*self.hasher) - .map_err(error::invalid_light_block_error)?; + .map_err(Error::invalid_light_block)?; self.predicates .next_validators_match(light_block, &*self.hasher) - .map_err(error::invalid_light_block_error)?; + .map_err(Error::invalid_light_block)?; Ok(()) } diff --git a/light-client/src/builder/supervisor.rs b/light-client/src/builder/supervisor.rs index abb36f2e1..7b7be6e4d 100644 --- a/light-client/src/builder/supervisor.rs +++ b/light-client/src/builder/supervisor.rs @@ -1,6 +1,6 @@ use std::time::Duration; -use crate::builder::error::{self, Error}; +use crate::builder::error::Error; use crate::peer_list::{PeerList, PeerListBuilder}; use crate::supervisor::Instance; use crate::types::PeerId; @@ -95,7 +95,7 @@ impl SupervisorBuilder { ) -> Result, Error> { let mut iter = witnesses.into_iter().peekable(); if iter.peek().is_none() { - return Err(error::empty_witness_list_error()); + return Err(Error::empty_witness_list()); } for (peer_id, address, instance) in iter { diff --git a/light-client/src/components/io.rs b/light-client/src/components/io.rs index e43712414..640583e38 100644 --- a/light-client/src/components/io.rs +++ b/light-client/src/components/io.rs @@ -164,7 +164,7 @@ mod prod { match res { Ok(response) => Ok(response.signed_header), - Err(err) => Err(rpc_error(err)), + Err(err) => Err(IoError::rpc(err)), } } @@ -175,7 +175,7 @@ mod prod { ) -> Result { let height = match height { AtHeight::Highest => { - return Err(invalid_height_error()); + return Err(IoError::invalid_height()); } AtHeight::At(height) => height, }; @@ -184,12 +184,12 @@ mod prod { let response = block_on(self.timeout, async move { client.validators(height, Paging::All).await })? - .map_err(rpc_error)?; + .map_err(IoError::rpc)?; let validator_set = match proposer_address { Some(proposer_address) => { TMValidatorSet::with_proposer(response.validators, proposer_address) - .map_err(invalid_validator_set_error)? + .map_err(IoError::invalid_validator_set)? } None => TMValidatorSet::without_proposer(response.validators), }; diff --git a/light-client/src/components/verifier.rs b/light-client/src/components/verifier.rs index d19e549b6..2416ca99c 100644 --- a/light-client/src/components/verifier.rs +++ b/light-client/src/components/verifier.rs @@ -11,7 +11,6 @@ use crate::{ }, types::{LightBlock, Time}, }; -use flex_error::ErrorReport; use preds::{ errors::{VerificationError, VerificationErrorDetail}, ProdPredicates, VerificationPredicates, @@ -35,7 +34,7 @@ impl From> for Verdict { fn from(result: Result<(), VerificationError>) -> Self { match result { Ok(()) => Self::Success, - Err(ErrorReport(e, _)) => match e.not_enough_trust() { + Err(VerificationError(e, _)) => match e.not_enough_trust() { Some(tally) => Self::NotEnoughTrust(tally), _ => Self::Invalid(e), }, diff --git a/light-client/src/errors.rs b/light-client/src/errors.rs index 085f4c712..c92286384 100644 --- a/light-client/src/errors.rs +++ b/light-client/src/errors.rs @@ -159,10 +159,12 @@ impl ErrorExt for ErrorDetail { } } -pub fn send_error(_e: crossbeam::SendError) -> Error { - channel_disconnected_error() -} +impl Error { + pub fn send(_e: crossbeam::SendError) -> Error { + Error::channel_disconnected() + } -pub fn recv_error(_e: crossbeam::RecvError) -> Error { - channel_disconnected_error() + pub fn recv(_e: crossbeam::RecvError) -> Error { + Error::channel_disconnected() + } } diff --git a/light-client/src/evidence.rs b/light-client/src/evidence.rs index 20f32b1c0..279fb9501 100644 --- a/light-client/src/evidence.rs +++ b/light-client/src/evidence.rs @@ -48,7 +48,7 @@ mod prod { self.timeout, async move { client.broadcast_evidence(e).await }, )? - .map_err(crate::components::io::rpc_error)?; + .map_err(IoError::rpc)?; Ok(response.hash) } @@ -68,7 +68,7 @@ mod prod { #[pre(self.peer_map.contains_key(&peer))] fn rpc_client_for(&self, peer: PeerId) -> Result { let peer_addr = self.peer_map.get(&peer).unwrap().to_owned(); - rpc::HttpClient::new(peer_addr).map_err(crate::components::io::rpc_error) + rpc::HttpClient::new(peer_addr).map_err(IoError::rpc) } } } diff --git a/light-client/src/fork_detector.rs b/light-client/src/fork_detector.rs index bd4c8caea..339592694 100644 --- a/light-client/src/fork_detector.rs +++ b/light-client/src/fork_detector.rs @@ -9,8 +9,6 @@ use crate::{ types::{LightBlock, PeerId, Status}, }; -use flex_error::ErrorReport; - /// Result of fork detection #[derive(Debug)] pub enum ForkDetection { @@ -122,13 +120,13 @@ impl ForkDetector for ProdForkDetector { primary: verified_block.clone(), witness: witness_block, }), - Err(ErrorReport(e, _)) if e.has_expired() => { + Err(Error(e, _)) if e.has_expired() => { forks.push(Fork::Forked { primary: verified_block.clone(), witness: witness_block, }); } - Err(ErrorReport(e, _)) => { + Err(Error(e, _)) => { if e.is_timeout().is_some() { forks.push(Fork::Timeout(witness_block.provider, e)) } else { diff --git a/light-client/src/light_client.rs b/light-client/src/light_client.rs index 4d2056dea..f5a06bd5f 100644 --- a/light-client/src/light_client.rs +++ b/light-client/src/light_client.rs @@ -11,7 +11,7 @@ use serde::{Deserialize, Serialize}; use crate::{ components::{clock::Clock, io::*, scheduler::*, verifier::*}, contracts::*, - errors::{self as error, Error}, + errors::Error, operations::Hasher, state::State, types::{Height, LightBlock, PeerId, Status, TrustThreshold}, @@ -122,7 +122,7 @@ impl LightClient { let target_block = self .io .fetch_light_block(AtHeight::Highest) - .map_err(error::io_error)?; + .map_err(Error::io)?; self.verify_to_target(target_block.height(), state) } @@ -178,7 +178,7 @@ impl LightClient { let highest = state .light_store .highest_trusted_or_verified() - .ok_or_else(error::no_initial_trusted_state_error)?; + .ok_or_else(Error::no_initial_trusted_state)?; if target_height >= highest.height() { // Perform forward verification with bisection @@ -204,10 +204,10 @@ impl LightClient { let trusted_state = state .light_store .highest_trusted_or_verified() - .ok_or_else(error::no_initial_trusted_state_error)?; + .ok_or_else(Error::no_initial_trusted_state)?; if target_height < trusted_state.height() { - return Err(error::target_lower_than_trusted_state_error( + return Err(Error::target_lower_than_trusted_state( target_height, trusted_state.height(), )); @@ -215,7 +215,7 @@ impl LightClient { // Check invariant [LCV-INV-TP.1] if !is_within_trust_period(&trusted_state, self.options.trusting_period, now) { - return Err(error::trusted_state_outside_trusting_period_error( + return Err(Error::trusted_state_outside_trusting_period( Box::new(trusted_state), self.options, )); @@ -251,7 +251,7 @@ impl LightClient { // and abort. state.light_store.update(¤t_block, Status::Failed); - return Err(error::invalid_light_block_error(e)); + return Err(Error::invalid_light_block(e)); } Verdict::NotEnoughTrust(_) => { // The current block cannot be trusted because of a missing overlap in the @@ -281,9 +281,9 @@ impl LightClient { let trusted_state = state .light_store .highest_trusted_or_verified() - .ok_or_else(error::no_initial_trusted_state_error)?; + .ok_or_else(Error::no_initial_trusted_state)?; - Err(error::target_lower_than_trusted_state_error( + Err(Error::target_lower_than_trusted_state( target_height, trusted_state.height(), )) @@ -318,13 +318,13 @@ impl LightClient { let root = state .light_store .highest_trusted_or_verified() - .ok_or_else(error::no_initial_trusted_state_error)?; + .ok_or_else(Error::no_initial_trusted_state)?; assert!(root.height() >= target_height); // Check invariant [LCV-INV-TP.1] if !is_within_trust_period(&root, self.options.trusting_period, self.clock.now()) { - return Err(error::trusted_state_outside_trusting_period_error( + return Err(Error::trusted_state_outside_trusting_period( Box::new(root), self.options, )); @@ -343,12 +343,12 @@ impl LightClient { .signed_header .header .last_block_id - .ok_or_else(|| error::missing_last_block_id_error(latest.height()))?; + .ok_or_else(|| Error::missing_last_block_id(latest.height()))?; let current_hash = self.hasher.hash_header(¤t.signed_header.header); if current_hash != latest_last_block_id.hash { - return Err(error::invalid_adjacent_headers_error( + return Err(Error::invalid_adjacent_headers( current_hash, latest_last_block_id.hash, )); @@ -394,7 +394,7 @@ impl LightClient { let block = self .io .fetch_light_block(AtHeight::At(height)) - .map_err(error::io_error)?; + .map_err(Error::io)?; state.light_store.insert(block.clone(), Status::Unverified); diff --git a/light-client/src/operations/commit_validator.rs b/light-client/src/operations/commit_validator.rs index 44dc728e7..a779e4736 100644 --- a/light-client/src/operations/commit_validator.rs +++ b/light-client/src/operations/commit_validator.rs @@ -2,7 +2,7 @@ use crate::{ operations::{Hasher, ProdHasher}, - predicates::errors::{self as error, VerificationError}, + predicates::errors::VerificationError, types::{SignedHeader, ValidatorSet}, }; @@ -58,12 +58,12 @@ impl CommitValidator for ProdCommitValidator { // See https://github.com/informalsystems/tendermint-rs/issues/650 let has_present_signatures = signatures.iter().any(|cs| !cs.is_absent()); if !has_present_signatures { - return Err(error::no_signature_for_commit_error()); + return Err(VerificationError::no_signature_for_commit()); } // Check that that the number of signatures matches the number of validators. if signatures.len() != validator_set.validators().len() { - return Err(error::mismatch_pre_commit_length_error( + return Err(VerificationError::mismatch_pre_commit_length( signatures.len(), validator_set.validators().len(), )); @@ -95,7 +95,7 @@ impl CommitValidator for ProdCommitValidator { }; if validator_set.validator(*validator_address) == None { - return Err(error::faulty_signer_error( + return Err(VerificationError::faulty_signer( *validator_address, self.hasher.hash_validator_set(validator_set), )); diff --git a/light-client/src/operations/voting_power.rs b/light-client/src/operations/voting_power.rs index 4365f10e7..689fab45b 100644 --- a/light-client/src/operations/voting_power.rs +++ b/light-client/src/operations/voting_power.rs @@ -1,7 +1,7 @@ //! Provides an interface and default implementation for the `VotingPower` operation use crate::{ - predicates::errors::{self as error, VerificationError}, + predicates::errors::VerificationError, types::{Commit, SignedHeader, TrustThreshold, ValidatorSet}, }; @@ -61,7 +61,7 @@ pub trait VotingPowerCalculator: Send + Sync { if trust_threshold.is_enough_power(voting_power.tallied, voting_power.total) { Ok(()) } else { - Err(error::not_enough_trust_error(voting_power)) + Err(VerificationError::not_enough_trust(voting_power)) } } @@ -79,7 +79,9 @@ pub trait VotingPowerCalculator: Send + Sync { if trust_threshold.is_enough_power(voting_power.tallied, voting_power.total) { Ok(()) } else { - Err(error::insufficient_signers_overlap_error(voting_power)) + Err(VerificationError::insufficient_signers_overlap( + voting_power, + )) } } @@ -124,7 +126,9 @@ impl VotingPowerCalculator for ProdVotingPowerCalculator { for (signature, vote) in non_absent_votes { // Ensure we only count a validator's power once if seen_validators.contains(&vote.validator_address) { - return Err(error::duplicate_validator_error(vote.validator_address)); + return Err(VerificationError::duplicate_validator( + vote.validator_address, + )); } else { seen_validators.insert(vote.validator_address); } @@ -147,7 +151,7 @@ impl VotingPowerCalculator for ProdVotingPowerCalculator { .verify_signature(&sign_bytes, signed_vote.signature()) .is_err() { - return Err(error::invalid_signature_error( + return Err(VerificationError::invalid_signature( signed_vote.signature().to_bytes(), Box::new(validator), sign_bytes, @@ -221,7 +225,6 @@ mod tests { use super::*; use crate::predicates::errors::VerificationErrorDetail; use crate::types::LightBlock; - use flex_error::ErrorReport; use tendermint::trust_threshold::TrustThresholdFraction; use tendermint_testgen::light_block::generate_signed_header; use tendermint_testgen::{ @@ -326,7 +329,7 @@ mod tests { ); match result_err { - Err(ErrorReport(VerificationErrorDetail::InvalidSignature(_), _)) => {} + Err(VerificationError(VerificationErrorDetail::InvalidSignature(_), _)) => {} _ => panic!("expected InvalidSignature error"), } } @@ -348,7 +351,7 @@ mod tests { ); match result_err { - Err(ErrorReport(VerificationErrorDetail::InvalidSignature(_), _)) => {} + Err(VerificationError(VerificationErrorDetail::InvalidSignature(_), _)) => {} _ => panic!("expected InvalidSignature error"), } } diff --git a/light-client/src/peer_list.rs b/light-client/src/peer_list.rs index c3c26da25..d4e567414 100644 --- a/light-client/src/peer_list.rs +++ b/light-client/src/peer_list.rs @@ -1,9 +1,6 @@ //! Provides a peer list for use within the `Supervisor` -use crate::{ - errors::{self as error, Error}, - types::PeerId, -}; +use crate::{errors::Error, types::PeerId}; use contracts::{post, pre}; use std::collections::{BTreeSet, HashMap}; @@ -150,7 +147,7 @@ impl PeerList { } else if let Some(err) = primary_error { Err(err) } else { - Err(error::no_witnesses_left_error()) + Err(Error::no_witnesses_left()) } } @@ -239,7 +236,6 @@ impl PeerListBuilder { mod tests { use super::*; use crate::errors::ErrorDetail; - use flex_error::ErrorReport; trait BTreeSetExt { fn to_vec(&self) -> Vec; @@ -309,7 +305,7 @@ mod tests { let _ = peer_list.replace_faulty_primary(None).unwrap(); let new_primary = peer_list.replace_faulty_primary(None); match new_primary { - Err(ErrorReport(ErrorDetail::NoWitnessesLeft(_), _)) => {} + Err(Error(ErrorDetail::NoWitnessesLeft(_), _)) => {} _ => panic!( "expected NoWitnessesLeft error, instead got {:?}", new_primary diff --git a/light-client/src/predicates.rs b/light-client/src/predicates.rs index b397e354d..a73afa6a9 100644 --- a/light-client/src/predicates.rs +++ b/light-client/src/predicates.rs @@ -6,7 +6,7 @@ use crate::{ types::{Header, LightBlock, SignedHeader, Time, TrustThreshold, ValidatorSet}, }; -use errors::{self as error, VerificationError}; +use errors::VerificationError; use std::time::Duration; pub mod errors; @@ -36,7 +36,7 @@ pub trait VerificationPredicates: Send + Sync { if light_block.signed_header.header.validators_hash == validators_hash { Ok(()) } else { - Err(error::invalid_validator_set_error( + Err(VerificationError::invalid_validator_set( light_block.signed_header.header.validators_hash, validators_hash, )) @@ -54,7 +54,7 @@ pub trait VerificationPredicates: Send + Sync { if light_block.signed_header.header.next_validators_hash == next_validators_hash { Ok(()) } else { - Err(error::invalid_next_validator_set_error( + Err(VerificationError::invalid_next_validator_set( light_block.signed_header.header.next_validators_hash, next_validators_hash, )) @@ -72,7 +72,7 @@ pub trait VerificationPredicates: Send + Sync { if header_hash == signed_header.commit.block_id.hash { Ok(()) } else { - Err(error::invalid_commit_value_error( + Err(VerificationError::invalid_commit_value( header_hash, signed_header.commit.block_id.hash, )) @@ -104,7 +104,7 @@ pub trait VerificationPredicates: Send + Sync { if expires_at > now { Ok(()) } else { - Err(error::not_within_trust_period_error(expires_at, now)) + Err(VerificationError::not_within_trust_period(expires_at, now)) } } @@ -118,7 +118,7 @@ pub trait VerificationPredicates: Send + Sync { if untrusted_header.time < now + clock_drift { Ok(()) } else { - Err(error::header_from_the_future_error( + Err(VerificationError::header_from_the_future( untrusted_header.time, now, )) @@ -134,7 +134,7 @@ pub trait VerificationPredicates: Send + Sync { if untrusted_header.time > trusted_header.time { Ok(()) } else { - Err(error::non_monotonic_bft_time_error( + Err(VerificationError::non_monotonic_bft_time( untrusted_header.time, trusted_header.time, )) @@ -152,7 +152,7 @@ pub trait VerificationPredicates: Send + Sync { if untrusted_header.height > trusted_header.height { Ok(()) } else { - Err(error::non_increasing_height_error( + Err(VerificationError::non_increasing_height( untrusted_header.height, trusted_height.increment(), )) @@ -196,7 +196,7 @@ pub trait VerificationPredicates: Send + Sync { { Ok(()) } else { - Err(error::invalid_next_validator_set_error( + Err(VerificationError::invalid_next_validator_set( light_block.signed_header.header.validators_hash, trusted_state.signed_header.header.next_validators_hash, )) @@ -290,7 +290,6 @@ pub fn verify( #[cfg(test)] mod tests { - use flex_error::ErrorReport; use std::ops::Sub; use std::time::Duration; use tendermint::Time; @@ -301,7 +300,8 @@ mod tests { }; use crate::predicates::{ - errors::VerificationErrorDetail, ProdPredicates, VerificationPredicates, + errors::{VerificationError, VerificationErrorDetail}, + ProdPredicates, VerificationPredicates, }; use crate::operations::{ @@ -337,7 +337,7 @@ mod tests { // 2. ensure header with non-monotonic bft time fails let result_err = vp.is_monotonic_bft_time(&header_one, &header_two); match result_err { - Err(ErrorReport(VerificationErrorDetail::NonMonotonicBftTime(e), _)) => { + Err(VerificationError(VerificationErrorDetail::NonMonotonicBftTime(e), _)) => { assert_eq!(e.header_bft_time, header_one.time); assert_eq!(e.trusted_header_bft_time, header_two.time); } @@ -361,7 +361,7 @@ mod tests { let result_err = vp.is_monotonic_height(&header_one, &header_two); match result_err { - Err(ErrorReport(VerificationErrorDetail::NonIncreasingHeight(e), _)) => { + Err(VerificationError(VerificationErrorDetail::NonIncreasingHeight(e), _)) => { assert_eq!(e.got, header_one.height); assert_eq!(e.expected, header_two.height.increment()); } @@ -390,7 +390,7 @@ mod tests { let expires_at = header.time + trusting_period; match result_err { - Err(ErrorReport(VerificationErrorDetail::NotWithinTrustPeriod(e), _)) => { + Err(VerificationError(VerificationErrorDetail::NotWithinTrustPeriod(e), _)) => { assert_eq!(e.expires_at, expires_at); assert_eq!(e.now, now); } @@ -416,7 +416,7 @@ mod tests { let result_err = vp.is_header_from_past(&header, one_second, now); match result_err { - Err(ErrorReport(VerificationErrorDetail::HeaderFromTheFuture(e), _)) => { + Err(VerificationError(VerificationErrorDetail::HeaderFromTheFuture(e), _)) => { assert_eq!(e.header_time, header.time); assert_eq!(e.now, now); } @@ -453,7 +453,7 @@ mod tests { let val_sets_match_err = vp.validator_sets_match(&light_block, &hasher); match val_sets_match_err { - Err(ErrorReport(VerificationErrorDetail::InvalidValidatorSet(e), _)) => { + Err(VerificationError(VerificationErrorDetail::InvalidValidatorSet(e), _)) => { assert_eq!( e.header_validators_hash, light_block.signed_header.header.validators_hash @@ -471,7 +471,7 @@ mod tests { let next_val_sets_match_err = vp.next_validators_match(&light_block, &hasher); match next_val_sets_match_err { - Err(ErrorReport(VerificationErrorDetail::InvalidNextValidatorSet(e), _)) => { + Err(VerificationError(VerificationErrorDetail::InvalidNextValidatorSet(e), _)) => { assert_eq!( e.header_next_validators_hash, light_block.signed_header.header.next_validators_hash @@ -507,11 +507,11 @@ mod tests { .unwrap(); let result_err = vp.header_matches_commit(&signed_header, &hasher); - // 3. ensure it fails with: VerificationError::InvalidCommitValue + // 3. ensure it fails with: VerificationVerificationError::InvalidCommitValue let header_hash = hasher.hash_header(&signed_header.header); match result_err { - Err(ErrorReport(VerificationErrorDetail::InvalidCommitValue(e), _)) => { + Err(VerificationError(VerificationErrorDetail::InvalidCommitValue(e), _)) => { assert_eq!(e.header_hash, header_hash); assert_eq!(e.commit_hash, signed_header.commit.block_id.hash); } @@ -543,7 +543,7 @@ mod tests { let mut result_err = vp.valid_commit(&signed_header, &val_set, &commit_validator); match result_err { - Err(ErrorReport(VerificationErrorDetail::NoSignatureForCommit(_), _)) => {} + Err(VerificationError(VerificationErrorDetail::NoSignatureForCommit(_), _)) => {} _ => panic!("expected ImplementationSpecific error"), } @@ -555,7 +555,7 @@ mod tests { result_err = vp.valid_commit(&signed_header, &val_set, &commit_validator); match result_err { - Err(ErrorReport(VerificationErrorDetail::MismatchPreCommitLength(e), _)) => { + Err(VerificationError(VerificationErrorDetail::MismatchPreCommitLength(e), _)) => { assert_eq!(e.pre_commit_length, signed_header.commit.signatures.len()); assert_eq!(e.validator_length, val_set.validators().len()); } @@ -588,7 +588,7 @@ mod tests { ); match result_err { - Err(ErrorReport(VerificationErrorDetail::FaultySigner(e), _)) => { + Err(VerificationError(VerificationErrorDetail::FaultySigner(e), _)) => { assert_eq!( e.signer, signed_header @@ -638,7 +638,7 @@ mod tests { let result_err = vp.valid_next_validator_set(&light_block3, &light_block2); match result_err { - Err(ErrorReport(VerificationErrorDetail::InvalidNextValidatorSet(e), _)) => { + Err(VerificationError(VerificationErrorDetail::InvalidNextValidatorSet(e), _)) => { assert_eq!( e.header_next_validators_hash, light_block3.signed_header.header.validators_hash @@ -693,7 +693,7 @@ mod tests { ); match result_err { - Err(ErrorReport(VerificationErrorDetail::NotEnoughTrust(e), _)) => { + Err(VerificationError(VerificationErrorDetail::NotEnoughTrust(e), _)) => { assert_eq!( e.tally, VotingPowerTally { @@ -737,7 +737,7 @@ mod tests { let trust_threshold = TrustThreshold::TWO_THIRDS; match result_err { - Err(ErrorReport(VerificationErrorDetail::InsufficientSignersOverlap(e), _)) => { + Err(VerificationError(VerificationErrorDetail::InsufficientSignersOverlap(e), _)) => { assert_eq!( e.tally, VotingPowerTally { diff --git a/light-client/src/store/sled/utils.rs b/light-client/src/store/sled/utils.rs index 21e6789d6..aefe3d7d6 100644 --- a/light-client/src/store/sled/utils.rs +++ b/light-client/src/store/sled/utils.rs @@ -6,7 +6,7 @@ use std::marker::PhantomData; use serde::{de::DeserializeOwned, Serialize}; -use crate::errors::{self as error, Error}; +use crate::errors::Error; use crate::types::Height; /// Provides a view over the database for storing key/value pairs at the given prefix. @@ -40,11 +40,11 @@ where /// Get the value associated with the given height within this tree pub fn get(&self, height: Height) -> Result, Error> { let key = key_bytes(height); - let value = self.tree.get(key).map_err(error::sled_error)?; + let value = self.tree.get(key).map_err(Error::sled)?; match value { Some(bytes) => { - let value = serde_cbor::from_slice(&bytes).map_err(error::serde_cbor_error)?; + let value = serde_cbor::from_slice(&bytes).map_err(Error::serde_cbor)?; Ok(value) } None => Ok(None), @@ -55,7 +55,7 @@ where pub fn contains_key(&self, height: Height) -> Result { let key = key_bytes(height); - let exists = self.tree.contains_key(key).map_err(error::sled_error)?; + let exists = self.tree.contains_key(key).map_err(Error::sled)?; Ok(exists) } @@ -63,9 +63,9 @@ where /// Insert a value associated with a height within this tree pub fn insert(&self, height: Height, value: &V) -> Result<(), Error> { let key = key_bytes(height); - let bytes = serde_cbor::to_vec(&value).map_err(error::serde_cbor_error)?; + let bytes = serde_cbor::to_vec(&value).map_err(Error::serde_cbor)?; - self.tree.insert(key, bytes).map_err(error::sled_error)?; + self.tree.insert(key, bytes).map_err(Error::sled)?; Ok(()) } @@ -74,7 +74,7 @@ where pub fn remove(&self, height: Height) -> Result<(), Error> { let key = key_bytes(height); - self.tree.remove(key).map_err(error::sled_error)?; + self.tree.remove(key).map_err(Error::sled)?; Ok(()) } diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index f67a94b78..4176070c4 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -4,7 +4,7 @@ use crossbeam_channel as channel; use tendermint::evidence::{ConflictingHeadersEvidence, Evidence}; -use crate::errors::{self as error, Error}; +use crate::errors::Error; use crate::evidence::EvidenceReporter; use crate::fork_detector::{Fork, ForkDetection, ForkDetector}; use crate::light_client::LightClient; @@ -214,7 +214,7 @@ impl Supervisor { Ok(verified_block) => { let trusted_block = primary .latest_trusted() - .ok_or_else(|| error::no_trusted_state_error(Status::Trusted))?; + .ok_or_else(|| Error::no_trusted_state(Status::Trusted))?; // Perform fork detection with the highest verified block and the trusted block. let outcome = self.detect_forks(&verified_block, &trusted_block)?; @@ -225,7 +225,7 @@ impl Supervisor { let forked = self.process_forks(forks)?; if !forked.is_empty() { // Fork detected, exiting - return Err(error::fork_detected_error(forked)); + return Err(Error::fork_detected(forked)); } // If there were no hard forks, perform verification again @@ -296,7 +296,7 @@ impl Supervisor { self.evidence_reporter .report(Evidence::ConflictingHeaders(Box::new(evidence)), provider) - .map_err(error::io_error)?; + .map_err(Error::io)?; Ok(()) } @@ -308,7 +308,7 @@ impl Supervisor { trusted_block: &LightBlock, ) -> Result { if self.peers.witnesses_ids().is_empty() { - return Err(error::no_witnesses_error()); + return Err(Error::no_witnesses()); } let witnesses = self @@ -327,28 +327,28 @@ impl Supervisor { /// This method should typically be called within a new thread with `std::thread::spawn`. pub fn run(mut self) -> Result<(), Error> { loop { - let event = self.receiver.recv().map_err(error::recv_error)?; + let event = self.receiver.recv().map_err(Error::recv)?; match event { HandleInput::LatestTrusted(sender) => { let outcome = self.latest_trusted(); - sender.send(outcome).map_err(error::send_error)?; + sender.send(outcome).map_err(Error::send)?; } HandleInput::Terminate(sender) => { - sender.send(()).map_err(error::send_error)?; + sender.send(()).map_err(Error::send)?; return Ok(()); } HandleInput::VerifyToTarget(height, sender) => { let outcome = self.verify_to_target(height); - sender.send(outcome).map_err(error::send_error)?; + sender.send(outcome).map_err(Error::send)?; } HandleInput::VerifyToHighest(sender) => { let outcome = self.verify_to_highest(); - sender.send(outcome).map_err(error::send_error)?; + sender.send(outcome).map_err(Error::send)?; } HandleInput::GetStatus(sender) => { let outcome = self.latest_status(); - sender.send(outcome).map_err(error::send_error)?; + sender.send(outcome).map_err(Error::send)?; } } } @@ -376,9 +376,9 @@ impl SupervisorHandle { let (sender, receiver) = channel::bounded::>(1); let event = make_event(sender); - self.sender.send(event).map_err(error::send_error)?; + self.sender.send(event).map_err(Error::send)?; - receiver.recv().map_err(error::recv_error)? + receiver.recv().map_err(Error::recv)? } } @@ -388,17 +388,17 @@ impl Handle for SupervisorHandle { self.sender .send(HandleInput::LatestTrusted(sender)) - .map_err(error::send_error)?; + .map_err(Error::send)?; - receiver.recv().map_err(error::recv_error) + receiver.recv().map_err(Error::recv) } fn latest_status(&self) -> Result { let (sender, receiver) = channel::bounded::(1); self.sender .send(HandleInput::GetStatus(sender)) - .map_err(error::send_error)?; - receiver.recv().map_err(error::recv_error) + .map_err(Error::send)?; + receiver.recv().map_err(Error::recv) } fn verify_to_highest(&self) -> Result { @@ -414,15 +414,16 @@ impl Handle for SupervisorHandle { self.sender .send(HandleInput::Terminate(sender)) - .map_err(error::send_error)?; + .map_err(Error::send)?; - receiver.recv().map_err(error::recv_error) + receiver.recv().map_err(Error::recv) } } #[cfg(test)] mod tests { use super::*; + use crate::errors::{Error, ErrorDetail}; use crate::light_client::Options; use crate::operations::ProdHasher; use crate::{ @@ -436,7 +437,6 @@ mod tests { tests::{MockClock, MockEvidenceReporter, MockIo, TrustOptions}, types::Time, }; - use flex_error::ErrorReport; use std::{collections::HashMap, convert::TryFrom, time::Duration}; use tendermint::block::Height; use tendermint::evidence::Duration as DurationStr; @@ -622,7 +622,7 @@ mod tests { let (result, _) = run_bisection_test(peer_list, 10); match result { - Err(ErrorReport(error::ErrorDetail::NoWitnesses(_), _)) => {} + Err(Error(ErrorDetail::NoWitnesses(_), _)) => {} _ => panic!("expected NoWitnesses error, instead got {:?}", result), } } @@ -645,7 +645,7 @@ mod tests { let (result, _) = run_bisection_test(peer_list, 10); match result { - Err(ErrorReport(error::ErrorDetail::Io(e), _)) => match e.source { + Err(Error(ErrorDetail::Io(e), _)) => match e.source { io::IoErrorDetail::Rpc(e) => match e.source { rpc::error::ErrorDetail::Response(e) => { assert_eq!(e.source, ResponseError::new(Code::InvalidRequest, None)) @@ -677,7 +677,7 @@ mod tests { // because MockIo returns an InvalidRequest error. This was previously // treated as a NoWitnessLeft error, which was misclassified. match result { - Err(ErrorReport(error::ErrorDetail::Io(e), _)) => match e.source { + Err(Error(ErrorDetail::Io(e), _)) => match e.source { crate::components::io::IoErrorDetail::Rpc(e) => match e.source { rpc::error::ErrorDetail::Response(e) => { assert_eq!(e.source.code(), rpc::Code::InvalidRequest) @@ -725,7 +725,7 @@ mod tests { let (result, _) = run_bisection_test(peer_list, 5); match result { - Err(ErrorReport(error::ErrorDetail::ForkDetected(_), _)) => {} + Err(Error(ErrorDetail::ForkDetected(_), _)) => {} _ => panic!("expected ForkDetected error"), } } diff --git a/light-client/src/tests.rs b/light-client/src/tests.rs index 138b150ec..82d2c3b6e 100644 --- a/light-client/src/tests.rs +++ b/light-client/src/tests.rs @@ -7,7 +7,7 @@ use tendermint::abci::transaction::Hash; use tendermint_rpc as rpc; use crate::components::clock::Clock; -use crate::components::io::{self, AtHeight, Io, IoError}; +use crate::components::io::{AtHeight, Io, IoError}; use crate::components::verifier::{ProdVerifier, Verdict, Verifier}; use crate::errors::Error; use crate::evidence::EvidenceReporter; @@ -114,7 +114,7 @@ impl Io for MockIo { }; self.light_blocks.get(&height).cloned().ok_or_else(|| { - io::rpc_error(rpc::error::response_error( + IoError::rpc(rpc::Error::response( rpc::response_error::ResponseError::new((-32600).into(), None), )) }) diff --git a/light-client/src/utils/block_on.rs b/light-client/src/utils/block_on.rs index b466830f1..6cd565a88 100644 --- a/light-client/src/utils/block_on.rs +++ b/light-client/src/utils/block_on.rs @@ -1,6 +1,6 @@ use std::{future::Future, time::Duration}; -use crate::components::io::{self, IoError}; +use crate::components::io::IoError; /// Run a future to completion on a new thread, with the given timeout. /// @@ -14,11 +14,11 @@ where let rt = tokio::runtime::Builder::new_current_thread() .enable_all() .build() - .map_err(io::runtime_error)?; + .map_err(IoError::runtime)?; if let Some(timeout) = timeout { let task = async { tokio::time::timeout(timeout, f).await }; - rt.block_on(task).map_err(|e| io::timeout_error(timeout, e)) + rt.block_on(task).map_err(|e| IoError::timeout(timeout, e)) } else { Ok(rt.block_on(f)) } diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index f48d1b963..1d3320785 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -38,7 +38,7 @@ x25519-dalek = "1.1" zeroize = "1" signature = "1.3.0" aead = "0.4.1" -flex-error = "0.3.0" +flex-error = "0.4.0" # path dependencies tendermint = { path = "../tendermint", version = "0.20.0" } diff --git a/p2p/src/secret_connection.rs b/p2p/src/secret_connection.rs index e8d32a82b..a02e4f24f 100644 --- a/p2p/src/secret_connection.rs +++ b/p2p/src/secret_connection.rs @@ -8,7 +8,7 @@ use std::{ slice, }; -use crate::error::{self, Error}; +use crate::error::Error; use chacha20poly1305::{ aead::{generic_array::GenericArray, AeadInPlace, NewAead}, ChaCha20Poly1305, @@ -107,7 +107,7 @@ impl Handshake { ) -> Result, Error> { let local_eph_privkey = match self.state.local_eph_privkey.take() { Some(key) => key, - None => return Err(error::missing_secret_error()), + None => return Err(Error::missing_secret()), }; let local_eph_pubkey = EphemeralPublic::from(&local_eph_privkey); @@ -124,7 +124,7 @@ impl Handshake { // - https://github.com/tendermint/kms/issues/142 // - https://eprint.iacr.org/2019/526.pdf if shared_secret.as_bytes().ct_eq(&[0x00; 32]).unwrap_u8() == 1 { - return Err(error::low_order_key_error()); + return Err(Error::low_order_key()); } // Sort by lexical order. @@ -178,26 +178,26 @@ impl Handshake { let pk_sum = auth_sig_msg .pub_key .and_then(|key| key.sum) - .ok_or_else(error::missing_key_error)?; + .ok_or_else(Error::missing_key)?; let remote_pubkey = match pk_sum { proto::crypto::public_key::Sum::Ed25519(ref bytes) => { - ed25519::PublicKey::from_bytes(bytes).map_err(error::signature_error) + ed25519::PublicKey::from_bytes(bytes).map_err(Error::signature) } - proto::crypto::public_key::Sum::Secp256k1(_) => Err(error::unsupported_key_error()), + proto::crypto::public_key::Sum::Secp256k1(_) => Err(Error::unsupported_key()), }?; - let remote_sig = ed25519::Signature::try_from(auth_sig_msg.sig.as_slice()) - .map_err(error::signature_error)?; + let remote_sig = + ed25519::Signature::try_from(auth_sig_msg.sig.as_slice()).map_err(Error::signature)?; if self.protocol_version.has_transcript() { remote_pubkey .verify(&self.state.sc_mac, &remote_sig) - .map_err(error::signature_error)?; + .map_err(Error::signature)?; } else { remote_pubkey .verify(&self.state.kdf.challenge, &remote_sig) - .map_err(error::signature_error)?; + .map_err(Error::signature)?; } // We've authorized. @@ -297,7 +297,7 @@ impl SecretConnection { b"", &mut sealed_frame[..TOTAL_FRAME_SIZE], ) - .map_err(error::aead_error)?; + .map_err(Error::aead)?; sealed_frame[TOTAL_FRAME_SIZE..].copy_from_slice(tag.as_slice()); @@ -307,14 +307,14 @@ impl SecretConnection { /// Decrypt AEAD authenticated data fn decrypt(&self, ciphertext: &[u8], out: &mut [u8]) -> Result { if ciphertext.len() < TAG_SIZE { - return Err(error::short_ciphertext_error(TAG_SIZE)); + return Err(Error::short_ciphertext(TAG_SIZE)); } // Split ChaCha20 ciphertext from the Poly1305 tag let (ct, tag) = ciphertext.split_at(ciphertext.len() - TAG_SIZE); if out.len() < ct.len() { - return Err(error::small_output_buffer_error()); + return Err(Error::small_output_buffer()); } let in_out = &mut out[..ct.len()]; @@ -327,7 +327,7 @@ impl SecretConnection { in_out, tag.into(), ) - .map_err(error::aead_error)?; + .map_err(Error::aead)?; Ok(in_out.len()) } @@ -446,15 +446,15 @@ fn share_eph_pubkey( // Should still work though. handler .write_all(&protocol_version.encode_initial_handshake(local_eph_pubkey)) - .map_err(error::io_error)?; + .map_err(Error::io)?; let mut response_len = 0_u8; handler .read_exact(slice::from_mut(&mut response_len)) - .map_err(error::io_error)?; + .map_err(Error::io)?; let mut buf = vec![0; response_len as usize]; - handler.read_exact(&mut buf).map_err(error::io_error)?; + handler.read_exact(&mut buf).map_err(Error::io)?; protocol_version.decode_initial_handshake(&buf) } @@ -463,9 +463,7 @@ fn sign_challenge( challenge: &[u8; 32], local_privkey: &dyn Signer, ) -> Result { - local_privkey - .try_sign(challenge) - .map_err(error::signature_error) + local_privkey.try_sign(challenge).map_err(Error::signature) } // TODO(ismail): change from DecodeError to something more generic @@ -479,10 +477,10 @@ fn share_auth_signature( .protocol_version .encode_auth_signature(pubkey, local_signature); - sc.write_all(&buf).map_err(error::io_error)?; + sc.write_all(&buf).map_err(Error::io)?; let mut buf = vec![0; sc.protocol_version.auth_sig_msg_response_len()]; - sc.read_exact(&mut buf).map_err(error::io_error)?; + sc.read_exact(&mut buf).map_err(Error::io)?; sc.protocol_version.decode_auth_signature(&buf) } diff --git a/p2p/src/secret_connection/protocol.rs b/p2p/src/secret_connection/protocol.rs index 59487f6d2..453051953 100644 --- a/p2p/src/secret_connection/protocol.rs +++ b/p2p/src/secret_connection/protocol.rs @@ -15,7 +15,7 @@ use tendermint_proto as proto; #[cfg(feature = "amino")] use super::amino_types; -use crate::error::{self, Error}; +use crate::error::Error; /// Size of an X25519 or Ed25519 public key const PUBLIC_KEY_SIZE: usize = 32; @@ -86,7 +86,7 @@ impl Version { // https://github.com/tendermint/tendermint/blob/9e98c74/p2p/conn/secret_connection.go#L315-L323 // TODO(tarcieri): proper protobuf framing if bytes.len() != 34 || bytes[..2] != [0x0a, 0x20] { - return Err(error::malformed_handshake_error()); + return Err(Error::malformed_handshake()); } let eph_pubkey_bytes: [u8; 32] = bytes[2..].try_into().expect("framing failed"); @@ -97,7 +97,7 @@ impl Version { // // Check that the length matches what we expect and the length prefix is correct if bytes.len() != 33 || bytes[0] != 32 { - return Err(error::malformed_handshake_error()); + return Err(Error::malformed_handshake()); } let eph_pubkey_bytes: [u8; 32] = bytes[1..].try_into().expect("framing failed"); @@ -106,7 +106,7 @@ impl Version { // Reject the key if it is of low order if is_low_order_point(&eph_pubkey) { - return Err(error::low_order_key_error()); + return Err(Error::low_order_key()); } Ok(eph_pubkey) @@ -161,7 +161,7 @@ impl Version { pub fn decode_auth_signature(self, bytes: &[u8]) -> Result { if self.is_protobuf() { // Parse Protobuf-encoded `AuthSigMessage` - proto::p2p::AuthSigMessage::decode_length_delimited(bytes).map_err(error::decode_error) + proto::p2p::AuthSigMessage::decode_length_delimited(bytes).map_err(Error::decode) } else { self.decode_auth_signature_amino(bytes) } @@ -204,7 +204,7 @@ impl Version { ) -> Result { // Legacy Amino encoded `AuthSigMessage` let amino_msg = amino_types::AuthSigMessage::decode_length_delimited(bytes) - .map_err(error::amino_decode_error)?; + .map_err(Error::amino_decode)?; let pub_key = proto::crypto::PublicKey { sum: Some(proto::crypto::public_key::Sum::Ed25519(amino_msg.pub_key)), }; diff --git a/p2p/src/secret_connection/public_key.rs b/p2p/src/secret_connection/public_key.rs index 45da0f358..5d3b43d5e 100644 --- a/p2p/src/secret_connection/public_key.rs +++ b/p2p/src/secret_connection/public_key.rs @@ -3,10 +3,7 @@ use ed25519_dalek as ed25519; use sha2::{digest::Digest, Sha256}; use std::fmt::{self, Display}; -use tendermint::{ - error::{self, Error}, - node, -}; +use tendermint::{error::Error, node}; /// Secret Connection peer public keys (signing, presently Ed25519-only) #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -24,7 +21,7 @@ impl PublicKey { pub fn from_raw_ed25519(bytes: &[u8]) -> Result { ed25519::PublicKey::from_bytes(bytes) .map(Self::Ed25519) - .map_err(error::signature_error) + .map_err(Error::signature) } /// Get Ed25519 public key diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 0ead449ee..cc2ce6695 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -26,7 +26,7 @@ serde_bytes = "0.11" num-traits = "0.2" num-derive = "0.3" chrono = { version = "0.4", features = ["serde"] } -flex-error = "0.3.0" +flex-error = "0.4.0" [dev-dependencies] serde_json = "1.0" diff --git a/proto/src/error.rs b/proto/src/error.rs index 3bdb5bce3..ab2d30b27 100644 --- a/proto/src/error.rs +++ b/proto/src/error.rs @@ -29,10 +29,12 @@ define_error! { } } -pub fn try_from_error(e: E) -> Error -where - E: Display, - T: TryFrom, -{ - try_from_protobuf_error(format!("{}", e)) +impl Error { + pub fn try_from(e: E) -> Error + where + E: Display, + T: TryFrom, + { + Error::try_from_protobuf(format!("{}", e)) + } } diff --git a/proto/src/lib.rs b/proto/src/lib.rs index 73bef76b4..18a883bc8 100644 --- a/proto/src/lib.rs +++ b/proto/src/lib.rs @@ -121,7 +121,7 @@ where fn encode(&self, buf: &mut B) -> Result<(), Error> { T::from(self.clone()) .encode(buf) - .map_err(error::encode_message_error) + .map_err(Error::encode_message) } /// Encode with a length-delimiter to a buffer in Protobuf format. @@ -135,7 +135,7 @@ where fn encode_length_delimited(&self, buf: &mut B) -> Result<(), Error> { T::from(self.clone()) .encode_length_delimited(buf) - .map_err(error::encode_message_error) + .map_err(Error::encode_message) } /// Constructor that attempts to decode an instance from a buffer. @@ -147,9 +147,9 @@ where /// /// [`prost::Message::decode`]: https://docs.rs/prost/*/prost/trait.Message.html#method.decode fn decode(buf: B) -> Result { - let raw = T::decode(buf).map_err(error::decode_message_error)?; + let raw = T::decode(buf).map_err(Error::decode_message)?; - Self::try_from(raw).map_err(error::try_from_error::) + Self::try_from(raw).map_err(Error::try_from::) } /// Constructor that attempts to decode a length-delimited instance from @@ -162,9 +162,9 @@ where /// /// [`prost::Message::decode_length_delimited`]: https://docs.rs/prost/*/prost/trait.Message.html#method.decode_length_delimited fn decode_length_delimited(buf: B) -> Result { - let raw = T::decode_length_delimited(buf).map_err(error::decode_message_error)?; + let raw = T::decode_length_delimited(buf).map_err(Error::decode_message)?; - Self::try_from(raw).map_err(error::try_from_error::) + Self::try_from(raw).map_err(Error::try_from::) } /// Returns the encoded length of the message without a length delimiter. @@ -192,7 +192,7 @@ where /// Encode with a length-delimiter to a `Vec` Protobuf-encoded message. fn encode_length_delimited_vec(&self) -> Result, Error> { let len = self.encoded_len(); - let lenu64 = len.try_into().map_err(error::parse_length_error)?; + let lenu64 = len.try_into().map_err(Error::parse_length)?; let mut wire = Vec::with_capacity(len + encoded_len_varint(lenu64)); self.encode_length_delimited(&mut wire).map(|_| wire) } diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 9a5bbc09e..ea44d470b 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -31,7 +31,8 @@ path = "src/client/bin/main.rs" required-features = [ "cli" ] [features] -default = [] +default = ["std"] +std = [] cli = [ "http-client", "structopt", @@ -77,7 +78,7 @@ uuid = { version = "0.8", default-features = false } subtle-encoding = { version = "0.5", features = ["bech32-preview"] } url = "2.2" walkdir = "2.3" -flex-error = "0.3.0" +flex-error = "0.4.0" async-trait = { version = "0.1", optional = true } async-tungstenite = { version = "0.12", features = ["tokio-runtime", "tokio-rustls"], optional = true } futures = { version = "0.3", optional = true } diff --git a/rpc/src/client.rs b/rpc/src/client.rs index 58dcf1e20..b5eebb7a6 100644 --- a/rpc/src/client.rs +++ b/rpc/src/client.rs @@ -14,7 +14,6 @@ pub use transport::websocket::{WebSocketClient, WebSocketClientDriver, WebSocket use crate::endpoint::validators::DEFAULT_VALIDATORS_PER_PAGE; use crate::endpoint::*; -use crate::error; use crate::paging::Paging; use crate::query::Query; use crate::{Error, Order, SimpleRequest}; @@ -250,7 +249,7 @@ pub trait Client { while self.health().await.is_err() { if attempts_remaining == 0 { - return Err(error::timeout_error(timeout)); + return Err(Error::timeout(timeout)); } attempts_remaining -= 1; diff --git a/rpc/src/client/bin/main.rs b/rpc/src/client/bin/main.rs index 8c155e271..950a9dfcd 100644 --- a/rpc/src/client/bin/main.rs +++ b/rpc/src/client/bin/main.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use structopt::StructOpt; use tendermint::abci::{Path, Transaction}; use tendermint_rpc::{ - error, Client, Error, HttpClient, Paging, Scheme, SubscriptionClient, Url, WebSocketClient, + Client, Error, HttpClient, Paging, Scheme, SubscriptionClient, Url, WebSocketClient, }; use tracing::level_filters::LevelFilter; use tracing::{error, info, warn}; @@ -153,7 +153,7 @@ async fn main() { let result = match opt.url.scheme() { Scheme::Http | Scheme::Https => http_request(opt.url, proxy_url, opt.req).await, Scheme::WebSocket | Scheme::SecureWebSocket => match opt.proxy_url { - Some(_) => Err(error::invalid_params_error( + Some(_) => Err(Error::invalid_params( "proxies are only supported for use with HTTP clients at present".to_string(), )), None => websocket_request(opt.url, opt.req).await, @@ -221,7 +221,7 @@ async fn websocket_request(url: Url, req: Request) -> Result<(), Error> { }; client.close()?; - driver_hdl.await.map_err(error::join_error)??; + driver_hdl.await.map_err(Error::join)??; result } @@ -231,7 +231,7 @@ where { let result = match req { ClientRequest::AbciInfo => { - serde_json::to_string_pretty(&client.abci_info().await?).map_err(error::serde_error)? + serde_json::to_string_pretty(&client.abci_info().await?).map_err(Error::serde)? } ClientRequest::AbciQuery { path, @@ -243,71 +243,70 @@ where .abci_query( path.map(|s| Path::from_str(&s)) .transpose() - .map_err(error::tendermint_error)?, + .map_err(Error::tendermint)?, data, height.map(Into::into), prove, ) .await?, ) - .map_err(error::serde_error)?, + .map_err(Error::serde)?, ClientRequest::Block { height } => { - serde_json::to_string_pretty(&client.block(height).await?) - .map_err(error::serde_error)? + serde_json::to_string_pretty(&client.block(height).await?).map_err(Error::serde)? } ClientRequest::Blockchain { min, max } => { serde_json::to_string_pretty(&client.blockchain(min, max).await?) - .map_err(error::serde_error)? + .map_err(Error::serde)? } ClientRequest::BlockResults { height } => { serde_json::to_string_pretty(&client.block_results(height).await?) - .map_err(error::serde_error)? + .map_err(Error::serde)? } ClientRequest::BroadcastTxAsync { tx } => serde_json::to_string_pretty( &client .broadcast_tx_async(Transaction::from(tx.into_bytes())) .await?, ) - .map_err(error::serde_error)?, + .map_err(Error::serde)?, ClientRequest::BroadcastTxCommit { tx } => serde_json::to_string_pretty( &client .broadcast_tx_commit(Transaction::from(tx.into_bytes())) .await?, ) - .map_err(error::serde_error)?, + .map_err(Error::serde)?, ClientRequest::BroadcastTxSync { tx } => serde_json::to_string_pretty( &client .broadcast_tx_sync(Transaction::from(tx.into_bytes())) .await?, ) - .map_err(error::serde_error)?, + .map_err(Error::serde)?, ClientRequest::Commit { height } => { - serde_json::to_string_pretty(&client.commit(height).await?) - .map_err(error::serde_error)? + serde_json::to_string_pretty(&client.commit(height).await?).map_err(Error::serde)? + } + ClientRequest::LatestBlock => { + serde_json::to_string_pretty(&client.latest_block().await?).map_err(Error::serde)? } - ClientRequest::LatestBlock => serde_json::to_string_pretty(&client.latest_block().await?) - .map_err(error::serde_error)?, ClientRequest::LatestBlockResults => { serde_json::to_string_pretty(&client.latest_block_results().await?) - .map_err(error::serde_error)? + .map_err(Error::serde)? + } + ClientRequest::LatestCommit => { + serde_json::to_string_pretty(&client.latest_commit().await?).map_err(Error::serde)? } - ClientRequest::LatestCommit => serde_json::to_string_pretty(&client.latest_commit().await?) - .map_err(error::serde_error)?, ClientRequest::ConsensusState => { - serde_json::to_string_pretty(&client.consensus_state().await?) - .map_err(error::serde_error)? + serde_json::to_string_pretty(&client.consensus_state().await?).map_err(Error::serde)? } ClientRequest::Genesis => { - serde_json::to_string_pretty(&client.genesis().await?).map_err(error::serde_error)? + serde_json::to_string_pretty(&client.genesis().await?).map_err(Error::serde)? } ClientRequest::Health => { - serde_json::to_string_pretty(&client.health().await?).map_err(error::serde_error)? + serde_json::to_string_pretty(&client.health().await?).map_err(Error::serde)? } ClientRequest::NetInfo => { - serde_json::to_string_pretty(&client.net_info().await?).map_err(error::serde_error)? + serde_json::to_string_pretty(&client.net_info().await?).map_err(Error::serde)? } ClientRequest::Status => { - serde_json::to_string_pretty(&client.status().await?).map_err(error::serde_error)? + serde_json::to_string_pretty(&client.status().await?).map_err(Error::serde)? } ClientRequest::Validators { height, @@ -327,7 +326,7 @@ where } }; serde_json::to_string_pretty(&client.validators(height, paging).await?) - .map_err(error::serde_error)? + .map_err(Error::serde)? } }; diff --git a/rpc/src/client/sync.rs b/rpc/src/client/sync.rs index 3fa48ad69..466d8977a 100644 --- a/rpc/src/client/sync.rs +++ b/rpc/src/client/sync.rs @@ -11,7 +11,7 @@ use futures::Stream; use pin_project::pin_project; use tokio::sync::mpsc; -use crate::{error, Error}; +use crate::Error; /// Constructor for an unbounded channel. pub fn unbounded() -> (ChannelTx, ChannelRx) { @@ -28,7 +28,7 @@ pub struct ChannelTx(mpsc::UnboundedSender); impl ChannelTx { pub fn send(&self, value: T) -> Result<(), Error> { - self.0.send(value).map_err(error::send_error) + self.0.send(value).map_err(Error::send) } } diff --git a/rpc/src/client/transport/http.rs b/rpc/src/client/transport/http.rs index 148b1b80e..40abb925c 100644 --- a/rpc/src/client/transport/http.rs +++ b/rpc/src/client/transport/http.rs @@ -1,7 +1,7 @@ //! HTTP-based transport for Tendermint RPC Client. use crate::client::Client; -use crate::{error, Error, Scheme, SimpleRequest, Url}; +use crate::{Error, Scheme, SimpleRequest, Url}; use async_trait::async_trait; use std::convert::{TryFrom, TryInto}; use std::str::FromStr; @@ -101,7 +101,7 @@ impl TryFrom for HttpClientUrl { fn try_from(value: Url) -> Result { match value.scheme() { Scheme::Http | Scheme::Https => Ok(Self(value)), - _ => Err(error::invalid_url_error(value)), + _ => Err(Error::invalid_url(value)), } } } @@ -133,7 +133,7 @@ impl TryFrom for HttpClientUrl { host, port, } => format!("http://{}:{}", host, port).parse(), - net::Address::Unix { .. } => Err(error::invalid_network_address_error()), + net::Address::Unix { .. } => Err(Error::invalid_network_address()), } } } @@ -148,16 +148,12 @@ impl TryFrom for hyper::Uri { type Error = Error; fn try_from(value: HttpClientUrl) -> Result { - value - .0 - .to_string() - .parse() - .map_err(error::invalid_uri_error) + value.0.to_string().parse().map_err(Error::invalid_uri) } } mod sealed { - use crate::{error, Error, Response, SimpleRequest}; + use crate::{Error, Response, SimpleRequest}; use hyper::body::Buf; use hyper::client::connect::Connect; use hyper::client::HttpConnector; @@ -188,11 +184,7 @@ mod sealed { R: SimpleRequest, { let request = self.build_request(request)?; - let response = self - .inner - .request(request) - .await - .map_err(error::hyper_error)?; + let response = self.inner.request(request).await.map_err(Error::hyper)?; let response_body = response_to_string(response).await?; tracing::debug!("Incoming response: {}", response_body); R::Response::from_string(&response_body) @@ -211,7 +203,7 @@ mod sealed { .method("POST") .uri(&self.uri) .body(hyper::Body::from(request_body.into_bytes())) - .map_err(error::http_error)?; + .map_err(Error::http)?; { let headers = request.headers_mut(); @@ -256,7 +248,7 @@ mod sealed { pub fn new_http_proxy(uri: Uri, proxy_uri: Uri) -> Result { let proxy = Proxy::new(Intercept::All, proxy_uri); let proxy_connector = - ProxyConnector::from_proxy(HttpConnector::new(), proxy).map_err(error::io_error)?; + ProxyConnector::from_proxy(HttpConnector::new(), proxy).map_err(Error::io)?; Ok(Self::HttpProxy(HyperClient::new( uri, hyper::Client::builder().build(proxy_connector), @@ -267,7 +259,7 @@ mod sealed { let proxy = Proxy::new(Intercept::All, proxy_uri); let proxy_connector = ProxyConnector::from_proxy(HttpsConnector::with_native_roots(), proxy) - .map_err(error::io_error)?; + .map_err(Error::io)?; Ok(Self::HttpsProxy(HyperClient::new( uri, @@ -292,10 +284,10 @@ mod sealed { let mut response_body = String::new(); hyper::body::aggregate(response.into_body()) .await - .map_err(error::hyper_error)? + .map_err(Error::hyper)? .reader() .read_to_string(&mut response_body) - .map_err(error::io_error)?; + .map_err(Error::io)?; Ok(response_body) } diff --git a/rpc/src/client/transport/mock.rs b/rpc/src/client/transport/mock.rs index 7d2b6e5ec..1efcabc85 100644 --- a/rpc/src/client/transport/mock.rs +++ b/rpc/src/client/transport/mock.rs @@ -6,7 +6,7 @@ use crate::client::transport::router::SubscriptionRouter; use crate::event::Event; use crate::query::Query; use crate::utils::uuid_str; -use crate::{error, Client, Error, Method, Request, Response, Subscription, SubscriptionClient}; +use crate::{Client, Error, Method, Request, Response, Subscription, SubscriptionClient}; use async_trait::async_trait; use std::collections::HashMap; @@ -60,7 +60,7 @@ impl Client for MockClient { { self.matcher .response_for(request) - .ok_or_else(error::mismatch_response_error)? + .ok_or_else(Error::mismatch_response)? } } diff --git a/rpc/src/client/transport/websocket.rs b/rpc/src/client/transport/websocket.rs index a92df25a8..3a4f865d4 100644 --- a/rpc/src/client/transport/websocket.rs +++ b/rpc/src/client/transport/websocket.rs @@ -8,8 +8,7 @@ use crate::event::Event; use crate::query::Query; use crate::request::Wrapper; use crate::{ - error::{self, Error}, - response, Client, Id, Request, Response, Scheme, SimpleRequest, Subscription, + error::Error, response, Client, Id, Request, Response, Scheme, SimpleRequest, Subscription, SubscriptionClient, Url, }; use async_trait::async_trait; @@ -184,7 +183,7 @@ impl TryFrom for WebSocketClientUrl { fn try_from(value: Url) -> Result { match value.scheme() { Scheme::WebSocket | Scheme::SecureWebSocket => Ok(Self(value)), - _ => Err(error::invalid_params_error(format!( + _ => Err(Error::invalid_params(format!( "cannot use URL {} with WebSocket clients", value ))), @@ -219,7 +218,7 @@ impl TryFrom for WebSocketClientUrl { host, port, } => format!("ws://{}:{}/websocket", host, port).parse(), - net::Address::Unix { .. } => Err(error::invalid_params_error( + net::Address::Unix { .. } => Err(Error::invalid_params( "only TCP-based node addresses are supported".to_string(), )), } @@ -241,7 +240,7 @@ mod sealed { use crate::query::Query; use crate::request::Wrapper; use crate::utils::uuid_str; - use crate::{error, Error, Response, SimpleRequest, Subscription, Url}; + use crate::{Error, Response, SimpleRequest, Subscription, Url}; use async_tungstenite::tokio::{connect_async, connect_async_with_tls_connector}; use tracing::debug; @@ -279,7 +278,7 @@ mod sealed { pub async fn new(url: Url) -> Result<(Self, WebSocketClientDriver), Error> { let url = url.to_string(); debug!("Connecting to unsecure WebSocket endpoint: {}", url); - let (stream, _response) = connect_async(url).await.map_err(error::tungstenite_error)?; + let (stream, _response) = connect_async(url).await.map_err(Error::tungstenite)?; let (cmd_tx, cmd_rx) = unbounded(); let driver = WebSocketClientDriver::new(stream, cmd_rx); Ok(( @@ -309,7 +308,7 @@ mod sealed { // connector for us. let (stream, _response) = connect_async_with_tls_connector(url, None) .await - .map_err(error::tungstenite_error)?; + .map_err(Error::tungstenite)?; let (cmd_tx, cmd_rx) = unbounded(); let driver = WebSocketClientDriver::new(stream, cmd_rx); Ok(( @@ -341,9 +340,7 @@ mod sealed { response_tx, }))?; let response = response_rx.recv().await.ok_or_else(|| { - error::client_internal_error( - "failed to hear back from WebSocket driver".to_string(), - ) + Error::client_internal("failed to hear back from WebSocket driver".to_string()) })??; tracing::debug!("Incoming response: {}", response); R::Response::from_string(response) @@ -362,9 +359,7 @@ mod sealed { }))?; // Make sure our subscription request went through successfully. let _ = response_rx.recv().await.ok_or_else(|| { - error::client_internal_error( - "failed to hear back from WebSocket driver".to_string(), - ) + Error::client_internal("failed to hear back from WebSocket driver".to_string()) })??; Ok(Subscription::new(id, query, subscription_rx)) } @@ -376,9 +371,7 @@ mod sealed { response_tx, }))?; let _ = response_rx.recv().await.ok_or_else(|| { - error::client_internal_error( - "failed to hear back from WebSocket driver".to_string(), - ) + Error::client_internal("failed to hear back from WebSocket driver".to_string()) })??; Ok(()) } @@ -536,7 +529,7 @@ impl WebSocketClientDriver { self.handle_incoming_msg(msg).await? }, Err(e) => return Err( - error::web_socket_error( + Error::web_socket( "failed to read from WebSocket connection".to_string(), e ), @@ -550,7 +543,7 @@ impl WebSocketClientDriver { }, _ = ping_interval.tick() => self.ping().await?, _ = &mut recv_timeout => { - return Err(error::web_socket_timeout_error(RECV_TIMEOUT)); + return Err(Error::web_socket_timeout(RECV_TIMEOUT)); } } } @@ -558,7 +551,7 @@ impl WebSocketClientDriver { async fn send_msg(&mut self, msg: Message) -> Result<(), Error> { self.stream.send(msg).await.map_err(|e| { - error::web_socket_error("failed to write to WebSocket connection".to_string(), e) + Error::web_socket("failed to write to WebSocket connection".to_string(), e) }) } diff --git a/rpc/src/endpoint/consensus_state.rs b/rpc/src/endpoint/consensus_state.rs index f71b48036..38569585d 100644 --- a/rpc/src/endpoint/consensus_state.rs +++ b/rpc/src/endpoint/consensus_state.rs @@ -1,6 +1,6 @@ //! `/consensus_state` endpoint JSON-RPC wrapper -use crate::{error, Error, Method}; +use crate::{Error, Method}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::fmt; use std::str::FromStr; @@ -181,92 +181,92 @@ impl FromStr for VoteSummary { let parts: Vec<&str> = s .strip_prefix("Vote{") .ok_or_else(|| { - error::client_internal_error( + Error::client_internal( "invalid format for consensus state vote summary string".to_string(), ) })? .strip_suffix('}') .ok_or_else(|| { - error::client_internal_error( + Error::client_internal( "invalid format for consensus state vote summary string".to_string(), ) })? .split(' ') .collect(); if parts.len() != 6 { - return Err(error::client_internal_error(format!( + return Err(Error::client_internal(format!( "expected 6 parts to a consensus state vote summary, but got {}", parts.len() ))); } let validator: Vec<&str> = parts[0].split(':').collect(); if validator.len() != 2 { - return Err(error::client_internal_error(format!( + return Err(Error::client_internal(format!( "failed to parse validator info for consensus state vote summary: {}", parts[0], ))); } let height_round_type: Vec<&str> = parts[1].split('/').collect(); if height_round_type.len() != 3 { - return Err(error::client_internal_error(format!( + return Err(Error::client_internal(format!( "failed to parse height/round/type for consensus state vote summary: {}", parts[1] ))); } let validator_index = i32::from_str(validator[0]).map_err(|e| { - error::client_internal_error(format!( + Error::client_internal(format!( "failed to parse validator index from consensus state vote summary: {} ({})", e, validator[0], )) })?; let validator_address_fingerprint = Fingerprint::from_str(validator[1]).map_err(|e| { - error::client_internal_error(format!( + Error::client_internal(format!( "failed to parse validator address fingerprint from consensus state vote summary: {}", e )) })?; let height = Height::from_str(height_round_type[0]).map_err(|e| { - error::client_internal_error(format!( + Error::client_internal(format!( "failed to parse height from consensus state vote summary: {}", e )) })?; let round = Round::from_str(height_round_type[1]).map_err(|e| { - error::client_internal_error(format!( + Error::client_internal(format!( "failed to parse round from consensus state vote summary: {}", e )) })?; let vote_type_parts: Vec<&str> = height_round_type[2].split('(').collect(); if vote_type_parts.len() != 2 { - return Err(error::client_internal_error(format!( + return Err(Error::client_internal(format!( "invalid structure for vote type in consensus state vote summary: {}", height_round_type[2] ))); } let vote_type_str = vote_type_parts[1].trim_end_matches(')'); let vote_type = vote::Type::from_str(vote_type_str).map_err(|e| { - error::client_internal_error(format!( + Error::client_internal(format!( "failed to parse vote type from consensus state vote summary: {} ({})", e, vote_type_str )) })?; let block_id_hash_fingerprint = Fingerprint::from_str(parts[2]).map_err(|e| { - error::client_internal_error(format!( + Error::client_internal(format!( "failed to parse block ID hash fingerprint from consensus state vote summary: {}", e )) })?; let signature_fingerprint = Fingerprint::from_str(parts[3]).map_err(|e| { - error::client_internal_error(format!( + Error::client_internal(format!( "failed to parse signature fingerprint from consensus state vote summary: {}", e )) })?; let timestamp = Time::parse_from_rfc3339(parts[5]).map_err(|e| { - error::client_internal_error(format!( + Error::client_internal(format!( "failed to parse timestamp from consensus state vote summary: {}", e )) @@ -311,7 +311,7 @@ impl FromStr for Fingerprint { fn from_str(s: &str) -> Result { Ok(Self(hex::decode_upper(s).map_err(|e| { - error::client_internal_error(format!( + Error::client_internal(format!( "failed to parse fingerprint as an uppercase hexadecimal string: {}", e )) diff --git a/rpc/src/error.rs b/rpc/src/error.rs index 6532e8806..0ba405e1a 100644 --- a/rpc/src/error.rs +++ b/rpc/src/error.rs @@ -1,6 +1,6 @@ //! JSON-RPC error types -use flex_error::{define_error, DisplayError, DisplayOnly}; +use flex_error::{define_error, DefaultTracer, DisplayError, DisplayOnly, ErrorMessageTracer}; use std::time::Duration; use crate::response_error::ResponseError; @@ -206,7 +206,18 @@ define_error! { } } +impl Clone for Error { + fn clone(&self) -> Self { + Error( + self.detail().clone(), + DefaultTracer::new_message(self.trace()), + ) + } +} + #[cfg(feature = "tokio")] -pub fn send_error(_: tokio::sync::mpsc::error::SendError) -> Error { - channel_send_error() +impl Error { + pub fn send(_: tokio::sync::mpsc::error::SendError) -> Error { + Error::channel_send() + } } diff --git a/rpc/src/method.rs b/rpc/src/method.rs index 5d23e1b84..2a964184b 100644 --- a/rpc/src/method.rs +++ b/rpc/src/method.rs @@ -1,6 +1,6 @@ //! JSON-RPC request methods -use crate::{error, Error}; +use crate::Error; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use std::{ fmt::{self, Display}, @@ -126,7 +126,7 @@ impl FromStr for Method { "tx_search" => Method::TxSearch, "unsubscribe" => Method::Unsubscribe, "validators" => Method::Validators, - other => return Err(error::method_not_found_error(other.to_string())), + other => return Err(Error::method_not_found(other.to_string())), }) } } diff --git a/rpc/src/order.rs b/rpc/src/order.rs index 564074bb2..f5079cc60 100644 --- a/rpc/src/order.rs +++ b/rpc/src/order.rs @@ -1,6 +1,6 @@ //! Ordering of paginated RPC responses. -use crate::{error, Error}; +use crate::Error; use serde::{Deserialize, Serialize}; use std::str::FromStr; @@ -23,7 +23,7 @@ impl FromStr for Order { match s { "asc" => Ok(Self::Ascending), "desc" => Ok(Self::Descending), - _ => Err(error::invalid_params_error(format!( + _ => Err(Error::invalid_params(format!( "invalid order type: {} (must be \"asc\" or \"desc\")", s ))), diff --git a/rpc/src/paging.rs b/rpc/src/paging.rs index e92dd8915..25768dcca 100644 --- a/rpc/src/paging.rs +++ b/rpc/src/paging.rs @@ -1,6 +1,6 @@ //! Pagination-related data structures for the Tendermint RPC. -use crate::{error, Error}; +use crate::Error; use serde::{Deserialize, Serialize}; use std::convert::TryInto; use std::str::FromStr; @@ -29,8 +29,8 @@ impl FromStr for PageNumber { type Err = Error; fn from_str(s: &str) -> Result { - let raw = i64::from_str(s).map_err(error::parse_int_error)?; - let raw_usize: usize = raw.try_into().map_err(error::out_of_range_error)?; + let raw = i64::from_str(s).map_err(Error::parse_int)?; + let raw_usize: usize = raw.try_into().map_err(Error::out_of_range)?; Ok(raw_usize.into()) } } @@ -55,8 +55,8 @@ impl FromStr for PerPage { type Err = Error; fn from_str(s: &str) -> Result { - let raw = i64::from_str(s).map_err(error::parse_int_error)?; - let raw_u8: u8 = raw.try_into().map_err(error::out_of_range_error)?; + let raw = i64::from_str(s).map_err(Error::parse_int)?; + let raw_u8: u8 = raw.try_into().map_err(Error::out_of_range)?; Ok(raw_u8.into()) } } diff --git a/rpc/src/query.rs b/rpc/src/query.rs index 37871da2f..ca82f265a 100644 --- a/rpc/src/query.rs +++ b/rpc/src/query.rs @@ -7,7 +7,7 @@ // TODO(thane): These warnings are generated by the PEG for some reason. Try to fix and remove. #![allow(clippy::redundant_closure_call, clippy::unit_arg)] -use crate::{error, Error}; +use crate::Error; use chrono::{Date, DateTime, FixedOffset, Utc}; use std::fmt; use std::str::FromStr; @@ -232,7 +232,7 @@ impl FromStr for EventType { match s { "NewBlock" => Ok(Self::NewBlock), "Tx" => Ok(Self::Tx), - invalid => Err(error::unrecognized_event_type_error(invalid.to_string())), + invalid => Err(Error::unrecognized_event_type(invalid.to_string())), } } } diff --git a/rpc/src/response.rs b/rpc/src/response.rs index d94e61dda..454fce22e 100644 --- a/rpc/src/response.rs +++ b/rpc/src/response.rs @@ -1,7 +1,7 @@ //! JSON-RPC response types use crate::response_error::ResponseError; -use crate::{error, Error, Id, Version}; +use crate::{Error, Id, Version}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use std::io::Read; @@ -10,13 +10,13 @@ pub trait Response: Serialize + DeserializeOwned + Sized { /// Parse a JSON-RPC response from a JSON string fn from_string(response: impl AsRef<[u8]>) -> Result { let wrapper: Wrapper = - serde_json::from_slice(response.as_ref()).map_err(error::serde_error)?; + serde_json::from_slice(response.as_ref()).map_err(Error::serde)?; wrapper.into_result() } /// Parse a JSON-RPC response from an `io::Reader` fn from_reader(reader: impl Read) -> Result { - let wrapper: Wrapper = serde_json::from_reader(reader).map_err(error::serde_error)?; + let wrapper: Wrapper = serde_json::from_reader(reader).map_err(Error::serde)?; wrapper.into_result() } } @@ -54,7 +54,7 @@ where /// Convert this wrapper into the underlying error, if any pub fn into_error(self) -> Option { - self.error.map(error::response_error) + self.error.map(Error::response) } /// Convert this wrapper into a result type @@ -63,11 +63,11 @@ where self.version().ensure_supported()?; if let Some(e) = self.error { - Err(error::response_error(e)) + Err(Error::response(e)) } else if let Some(result) = self.result { Ok(result) } else { - Err(error::malformed_json_error()) + Err(Error::malformed_json()) } } diff --git a/rpc/src/rpc_url.rs b/rpc/src/rpc_url.rs index 228adaa19..3b077a629 100644 --- a/rpc/src/rpc_url.rs +++ b/rpc/src/rpc_url.rs @@ -1,7 +1,7 @@ //! URL representation for RPC clients. -use crate::error; -use serde::de::Error; +use crate::error::Error; +use serde::de::Error as SerdeError; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryFrom; use std::fmt; @@ -36,7 +36,7 @@ impl FromStr for Scheme { "https" => Scheme::Https, "ws" => Scheme::WebSocket, "wss" => Scheme::SecureWebSocket, - _ => return Err(error::unsupported_scheme_error(s.to_string())), + _ => return Err(Error::unsupported_scheme(s.to_string())), }) } } @@ -55,20 +55,20 @@ pub struct Url { } impl FromStr for Url { - type Err = error::Error; + type Err = Error; fn from_str(s: &str) -> Result { - let inner: url::Url = s.parse().map_err(error::parse_url_error)?; + let inner: url::Url = s.parse().map_err(Error::parse_url)?; let scheme: Scheme = inner.scheme().parse()?; let host = inner .host_str() - .ok_or_else(|| error::invalid_params_error(format!("URL is missing its host: {}", s)))? + .ok_or_else(|| Error::invalid_params(format!("URL is missing its host: {}", s)))? .to_owned(); let port = inner.port_or_known_default().ok_or_else(|| { - error::invalid_params_error(format!("cannot determine appropriate port for URL: {}", s)) + Error::invalid_params(format!("cannot determine appropriate port for URL: {}", s)) })?; Ok(Self { inner, diff --git a/rpc/src/version.rs b/rpc/src/version.rs index d5bff1722..6722ae2be 100644 --- a/rpc/src/version.rs +++ b/rpc/src/version.rs @@ -1,6 +1,6 @@ //! JSON-RPC versions -use super::error::{self, Error}; +use super::error::Error; use serde::{Deserialize, Serialize}; use std::{ fmt::{self, Display}, @@ -31,7 +31,7 @@ impl Version { if self.is_supported() { Ok(()) } else { - Err(error::unsupported_rpc_version_error( + Err(Error::unsupported_rpc_version( self.0.to_string(), SUPPORTED_VERSION.to_string(), )) diff --git a/rpc/tests/kvstore_fixtures.rs b/rpc/tests/kvstore_fixtures.rs index 753eb7aef..47954e268 100644 --- a/rpc/tests/kvstore_fixtures.rs +++ b/rpc/tests/kvstore_fixtures.rs @@ -1,11 +1,13 @@ //! Tendermint kvstore RPC endpoint testing. -use flex_error::ErrorReport; use std::str::FromStr; use std::{fs, path::PathBuf}; use subtle_encoding::{base64, hex}; use tendermint_rpc::{ - endpoint, error::ErrorDetail, request::Wrapper as RequestWrapper, Code, Order, Response, + endpoint, + error::{Error, ErrorDetail}, + request::Wrapper as RequestWrapper, + Code, Order, Response, }; use walkdir::WalkDir; @@ -312,7 +314,7 @@ fn incoming_fixtures() { let res = endpoint::block::Response::from_string(&content); match res { - Err(ErrorReport(ErrorDetail::Response(e), _)) => { + Err(Error(ErrorDetail::Response(e), _)) => { let response = e.source; assert_eq!(response.code(), Code::InternalError); assert_eq!(response.message(), "Internal error"); @@ -735,7 +737,7 @@ fn incoming_fixtures() { let result = endpoint::subscribe::Response::from_string(content); match result { - Err(ErrorReport(ErrorDetail::Response(e), _)) => { + Err(Error(ErrorDetail::Response(e), _)) => { let response = e.source; assert_eq!(response.code(), Code::InternalError); @@ -749,7 +751,7 @@ fn incoming_fixtures() { let result = endpoint::subscribe::Response::from_string(content); match result { - Err(ErrorReport(ErrorDetail::Serde(_), _)) => {} + Err(Error(ErrorDetail::Serde(_), _)) => {} _ => panic!("expected Serde parse error, instead got {:?}", result), } } diff --git a/rpc/tests/parse_response.rs b/rpc/tests/parse_response.rs index 7ebc2d018..e3aff8857 100644 --- a/rpc/tests/parse_response.rs +++ b/rpc/tests/parse_response.rs @@ -3,11 +3,14 @@ use std::{fs, path::PathBuf}; use tendermint::abci::Code; -use flex_error::ErrorReport; use std::str::FromStr; use tendermint::vote; use tendermint_rpc::endpoint::consensus_state::RoundVote; -use tendermint_rpc::{endpoint, error::ErrorDetail, Code as RpcCode, Response}; +use tendermint_rpc::{ + endpoint, + error::{Error, ErrorDetail}, + Code as RpcCode, Response, +}; const EXAMPLE_APP: &str = "GaiaApp"; const EXAMPLE_CHAIN: &str = "cosmoshub-2"; @@ -296,7 +299,7 @@ fn jsonrpc_error() { let result = endpoint::blockchain::Response::from_string(&read_json_fixture("error")); match result { - Err(ErrorReport(ErrorDetail::Response(e), _)) => { + Err(Error(ErrorDetail::Response(e), _)) => { let response = e.source; assert_eq!(response.code(), RpcCode::InternalError); assert_eq!(response.message(), "Internal error"); diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index 1eb1c36f9..016131acf 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -55,7 +55,7 @@ tendermint-proto = { version = "0.20.0", path = "../proto" } toml = { version = "0.5" } url = { version = "2.2" } zeroize = { version = "1.1", features = ["zeroize_derive"] } -flex-error = "0.3.0" +flex-error = "0.4.0" time = "0.1.40" k256 = { version = "0.9", optional = true, features = ["ecdsa"] } diff --git a/tendermint/src/abci/gas.rs b/tendermint/src/abci/gas.rs index 322b6b6ca..eae908fcc 100644 --- a/tendermint/src/abci/gas.rs +++ b/tendermint/src/abci/gas.rs @@ -5,7 +5,7 @@ //! //! -use crate::error::{self, Error}; +use crate::error::Error; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use std::{ fmt::{self, Display}, @@ -47,7 +47,7 @@ impl FromStr for Gas { fn from_str(s: &str) -> Result { let res = s .parse::() - .map_err(|e| error::parse_int_error(s.to_string(), e))? + .map_err(|e| Error::parse_int(s.to_string(), e))? .into(); Ok(res) diff --git a/tendermint/src/abci/transaction/hash.rs b/tendermint/src/abci/transaction/hash.rs index 49dc2a18c..a4fa167cb 100644 --- a/tendermint/src/abci/transaction/hash.rs +++ b/tendermint/src/abci/transaction/hash.rs @@ -1,6 +1,6 @@ //! Transaction hashes -use crate::error::{self, Error}; +use crate::error::Error; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use std::{ fmt::{self, Debug, Display}, @@ -64,10 +64,10 @@ impl FromStr for Hash { // Accept either upper or lower case hex let bytes = hex::decode_upper(s) .or_else(|_| hex::decode(s)) - .map_err(error::subtle_encoding_error)?; + .map_err(Error::subtle_encoding)?; if bytes.len() != LENGTH { - return Err(error::invalid_hash_size_error()); + return Err(Error::invalid_hash_size()); } let mut result_bytes = [0u8; LENGTH]; diff --git a/tendermint/src/account.rs b/tendermint/src/account.rs index e1cbd00c0..b79691f7b 100644 --- a/tendermint/src/account.rs +++ b/tendermint/src/account.rs @@ -1,9 +1,6 @@ //! Tendermint accounts -use crate::{ - error::{self, Error}, - public_key::Ed25519, -}; +use crate::{error::Error, public_key::Ed25519}; use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use sha2::{Digest, Sha256}; @@ -36,7 +33,7 @@ impl TryFrom> for Id { fn try_from(value: Vec) -> Result { if value.len() != LENGTH { - return Err(error::invalid_account_id_length_error()); + return Err(Error::invalid_account_id_length()); } let mut slice: [u8; LENGTH] = [0; LENGTH]; slice.copy_from_slice(&value[..]); @@ -118,7 +115,7 @@ impl FromStr for Id { // Accept either upper or lower case hex let bytes = hex::decode_upper(s) .or_else(|_| hex::decode(s)) - .map_err(error::subtle_encoding_error)?; + .map_err(Error::subtle_encoding)?; bytes.try_into() } diff --git a/tendermint/src/block.rs b/tendermint/src/block.rs index 235549ab2..fcc70ad7d 100644 --- a/tendermint/src/block.rs +++ b/tendermint/src/block.rs @@ -21,7 +21,7 @@ pub use self::{ round::*, size::Size, }; -use crate::{abci::transaction, error, error::Error, evidence}; +use crate::{abci::transaction, error::Error, evidence}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::Block as RawBlock; @@ -55,10 +55,7 @@ impl TryFrom for Block { type Error = Error; fn try_from(value: RawBlock) -> Result { - let header: Header = value - .header - .ok_or_else(error::missing_header_error)? - .try_into()?; + let header: Header = value.header.ok_or_else(Error::missing_header)?.try_into()?; // if last_commit is Commit::Default, it is considered nil by Go. let last_commit = value .last_commit @@ -66,7 +63,7 @@ impl TryFrom for Block { .transpose()? .filter(|c| c != &Commit::default()); if last_commit.is_none() && header.height.value() != 1 { - return Err(error::invalid_block_error( + return Err(Error::invalid_block( "last_commit is empty on non-first block".to_string(), )); } @@ -77,10 +74,10 @@ impl TryFrom for Block { //} Ok(Block { header, - data: value.data.ok_or_else(error::missing_data_error)?.into(), + data: value.data.ok_or_else(Error::missing_data)?.into(), evidence: value .evidence - .ok_or_else(error::missing_evidence_error)? + .ok_or_else(Error::missing_evidence)? .try_into()?, last_commit, }) @@ -107,12 +104,12 @@ impl Block { last_commit: Option, ) -> Result { if last_commit.is_none() && header.height.value() != 1 { - return Err(error::invalid_block_error( + return Err(Error::invalid_block( "last_commit is empty on non-first block".to_string(), )); } if last_commit.is_some() && header.height.value() == 1 { - return Err(error::invalid_block_error( + return Err(Error::invalid_block( "last_commit is filled on first block".to_string(), )); } diff --git a/tendermint/src/block/commit.rs b/tendermint/src/block/commit.rs index 34d319e83..d4a3187af 100644 --- a/tendermint/src/block/commit.rs +++ b/tendermint/src/block/commit.rs @@ -2,7 +2,7 @@ use crate::block::commit_sig::CommitSig; use crate::block::{Height, Id, Round}; -use crate::error::{self, Error}; +use crate::error::Error; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::Commit as RawCommit; @@ -42,7 +42,7 @@ impl TryFrom for Commit { round: value.round.try_into()?, block_id: value .block_id - .ok_or_else(|| error::invalid_block_error("missing block id".to_string()))? + .ok_or_else(|| Error::invalid_block("missing block id".to_string()))? .try_into()?, /* gogoproto.nullable = false */ signatures: signatures?, }) diff --git a/tendermint/src/block/commit_sig.rs b/tendermint/src/block/commit_sig.rs index 77527ee7c..e5b2a35c2 100644 --- a/tendermint/src/block/commit_sig.rs +++ b/tendermint/src/block/commit_sig.rs @@ -1,6 +1,6 @@ //! CommitSig within Commit -use crate::error::{self, Error}; +use crate::error::Error; use crate::{account, Signature, Time}; use num_traits::ToPrimitive; use std::convert::{TryFrom, TryInto}; @@ -74,31 +74,26 @@ impl TryFrom for CommitSig { let timestamp = value.timestamp.unwrap(); // 0001-01-01T00:00:00.000Z translates to EPOCH-62135596800 seconds if timestamp.nanos != 0 || timestamp.seconds != -62135596800 { - return Err(error::invalid_timestamp_error( + return Err(Error::invalid_timestamp( "absent commitsig has non-zero timestamp".to_string(), )); } } if !value.signature.is_empty() { - return Err(error::invalid_signature_error( - "empty signature".to_string(), - )); + return Err(Error::invalid_signature("empty signature".to_string())); } return Ok(CommitSig::BlockIdFlagAbsent); } if value.block_id_flag == BlockIdFlag::Commit.to_i32().unwrap() { if value.signature.is_empty() { - return Err(error::invalid_signature_error( + return Err(Error::invalid_signature( "regular commitsig has no signature".to_string(), )); } if value.validator_address.is_empty() { - return Err(error::invalid_validator_address_error()); + return Err(Error::invalid_validator_address()); } - let timestamp = value - .timestamp - .ok_or_else(error::missing_timestamp_error)? - .into(); + let timestamp = value.timestamp.ok_or_else(Error::missing_timestamp)?.into(); return Ok(CommitSig::BlockIdFlagCommit { validator_address: value.validator_address.try_into()?, @@ -108,23 +103,20 @@ impl TryFrom for CommitSig { } if value.block_id_flag == BlockIdFlag::Nil.to_i32().unwrap() { if value.signature.is_empty() { - return Err(error::invalid_signature_error( + return Err(Error::invalid_signature( "nil commitsig has no signature".to_string(), )); } if value.validator_address.is_empty() { - return Err(error::invalid_validator_address_error()); + return Err(Error::invalid_validator_address()); } return Ok(CommitSig::BlockIdFlagNil { validator_address: value.validator_address.try_into()?, - timestamp: value - .timestamp - .ok_or_else(error::missing_timestamp_error)? - .into(), + timestamp: value.timestamp.ok_or_else(Error::missing_timestamp)?.into(), signature: value.signature.try_into()?, }); } - Err(error::block_id_flag_error()) + Err(Error::block_id_flag()) } } diff --git a/tendermint/src/block/header.rs b/tendermint/src/block/header.rs index 522acd38b..6a483850c 100644 --- a/tendermint/src/block/header.rs +++ b/tendermint/src/block/header.rs @@ -1,7 +1,7 @@ //! Block headers use crate::merkle::simple_hash_from_byte_vectors; -use crate::{account, block, chain, error, AppHash, Error, Hash, Time}; +use crate::{account, block, chain, AppHash, Error, Hash, Time}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::Header as RawHeader; @@ -89,7 +89,7 @@ impl TryFrom for Header { // height").into()); //} if last_block_id.is_some() && height.value() == 1 { - return Err(error::invalid_first_header_error()); + return Err(Error::invalid_first_header()); } //if last_commit_hash.is_none() && height.value() != 1 { // return Err(Kind::InvalidHeader.context("last_commit_hash is null on non-first @@ -109,16 +109,10 @@ impl TryFrom for Header { // height").into()); //} Ok(Header { - version: value - .version - .ok_or_else(error::missing_version_error)? - .into(), + version: value.version.ok_or_else(Error::missing_version)?.into(), chain_id: value.chain_id.try_into()?, height, - time: value - .time - .ok_or_else(error::missing_timestamp_error)? - .into(), + time: value.time.ok_or_else(Error::missing_timestamp)?.into(), last_block_id, last_commit_hash, data_hash: if value.data_hash.is_empty() { diff --git a/tendermint/src/block/height.rs b/tendermint/src/block/height.rs index c1a60b765..2a50b734f 100644 --- a/tendermint/src/block/height.rs +++ b/tendermint/src/block/height.rs @@ -1,4 +1,4 @@ -use crate::error::{self, Error}; +use crate::error::Error; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryInto; use std::{ @@ -21,9 +21,7 @@ impl TryFrom for Height { type Error = Error; fn try_from(value: i64) -> Result { - Ok(Height( - value.try_into().map_err(error::negative_height_error)?, - )) + Ok(Height(value.try_into().map_err(Error::negative_height)?)) } } @@ -38,7 +36,7 @@ impl TryFrom for Height { fn try_from(value: u64) -> Result { // Make sure the u64 value can be converted safely to i64 - let _ival: i64 = value.try_into().map_err(error::integer_overflow_error)?; + let _ival: i64 = value.try_into().map_err(Error::integer_overflow)?; Ok(Height(value)) } @@ -104,7 +102,7 @@ impl FromStr for Height { fn from_str(s: &str) -> Result { Height::try_from( s.parse::() - .map_err(|e| error::parse_int_error(s.to_string(), e))?, + .map_err(|e| Error::parse_int(s.to_string(), e))?, ) } } diff --git a/tendermint/src/block/id.rs b/tendermint/src/block/id.rs index a2fb7bf88..881dc5b8d 100644 --- a/tendermint/src/block/id.rs +++ b/tendermint/src/block/id.rs @@ -1,6 +1,6 @@ use crate::{ block::parts::Header as PartSetHeader, - error::{self, Error}, + error::Error, hash::{Algorithm, Hash}, }; use serde::{Deserialize, Serialize}; @@ -64,7 +64,7 @@ impl TryFrom for Id { fn try_from(value: RawBlockId) -> Result { if value.part_set_header.is_none() { - return Err(error::invalid_part_set_header_error( + return Err(Error::invalid_part_set_header( "part_set_header is None".to_string(), )); } @@ -103,7 +103,7 @@ impl TryFrom for Id { fn try_from(value: RawCanonicalBlockId) -> Result { if value.part_set_header.is_none() { - return Err(error::invalid_part_set_header_error( + return Err(Error::invalid_part_set_header( "part_set_header is None".to_string(), )); } diff --git a/tendermint/src/block/meta.rs b/tendermint/src/block/meta.rs index 826306dfe..2963e5dae 100644 --- a/tendermint/src/block/meta.rs +++ b/tendermint/src/block/meta.rs @@ -1,7 +1,7 @@ //! Block metadata use super::{Header, Id}; -use crate::error::{self, Error}; +use crate::error::Error; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::BlockMeta as RawMeta; @@ -30,12 +30,12 @@ impl TryFrom for Meta { Ok(Meta { block_id: value .block_id - .ok_or_else(|| error::invalid_block_error("no block_id".to_string()))? + .ok_or_else(|| Error::invalid_block("no block_id".to_string()))? .try_into()?, block_size: value.block_size, header: value .header - .ok_or_else(|| error::invalid_block_error("no header".to_string()))? + .ok_or_else(|| Error::invalid_block("no header".to_string()))? .try_into()?, num_txs: value.num_txs, }) diff --git a/tendermint/src/block/parts.rs b/tendermint/src/block/parts.rs index 961650586..b8479e4b8 100644 --- a/tendermint/src/block/parts.rs +++ b/tendermint/src/block/parts.rs @@ -1,6 +1,6 @@ //! Block parts -use crate::error::{self, Error}; +use crate::error::Error; use crate::hash::Algorithm; use crate::hash::SHA256_HASH_SIZE; use crate::Hash; @@ -32,7 +32,7 @@ impl TryFrom for Header { fn try_from(value: RawPartSetHeader) -> Result { if !value.hash.is_empty() && value.hash.len() != SHA256_HASH_SIZE { - return Err(error::invalid_hash_size_error()); + return Err(Error::invalid_hash_size()); } Ok(Self { total: value.total, @@ -55,7 +55,7 @@ impl TryFrom for Header { fn try_from(value: RawCanonicalPartSetHeader) -> Result { if !value.hash.is_empty() && value.hash.len() != SHA256_HASH_SIZE { - return Err(error::invalid_hash_size_error()); + return Err(Error::invalid_hash_size()); } Ok(Self { total: value.total, @@ -77,12 +77,12 @@ impl Header { /// constructor pub fn new(total: u32, hash: Hash) -> Result { if total == 0 && hash != Hash::None { - return Err(error::invalid_part_set_header_error( + return Err(Error::invalid_part_set_header( "zero total with existing hash".to_string(), )); } if total != 0 && hash == Hash::None { - return Err(error::invalid_part_set_header_error( + return Err(Error::invalid_part_set_header( "non-zero total with empty hash".to_string(), )); } diff --git a/tendermint/src/block/round.rs b/tendermint/src/block/round.rs index b0e3fecf0..6f42468ef 100644 --- a/tendermint/src/block/round.rs +++ b/tendermint/src/block/round.rs @@ -1,4 +1,4 @@ -use crate::error::{self, Error}; +use crate::error::Error; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryInto; use std::{ @@ -15,9 +15,7 @@ impl TryFrom for Round { type Error = Error; fn try_from(value: i32) -> Result { - Ok(Round( - value.try_into().map_err(error::negative_round_error)?, - )) + Ok(Round(value.try_into().map_err(Error::negative_round)?)) } } @@ -31,7 +29,7 @@ impl TryFrom for Round { type Error = Error; fn try_from(value: u32) -> Result { - let _val: i32 = value.try_into().map_err(error::integer_overflow_error)?; + let _val: i32 = value.try_into().map_err(Error::integer_overflow)?; Ok(Round(value)) } @@ -91,7 +89,7 @@ impl FromStr for Round { fn from_str(s: &str) -> Result { Round::try_from( s.parse::() - .map_err(|e| error::parse_int_error(s.to_string(), e))?, + .map_err(|e| Error::parse_int(s.to_string(), e))?, ) } } diff --git a/tendermint/src/block/signed_header.rs b/tendermint/src/block/signed_header.rs index 044d3f51b..8aa3a2300 100644 --- a/tendermint/src/block/signed_header.rs +++ b/tendermint/src/block/signed_header.rs @@ -1,7 +1,7 @@ //! SignedHeader contains commit and and block header. //! It is what the rpc endpoint /commit returns and hence can be used by a //! light client. -use crate::{block, error, Error}; +use crate::{block, Error}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::SignedHeader as RawSignedHeader; @@ -23,11 +23,11 @@ impl TryFrom for SignedHeader { fn try_from(value: RawSignedHeader) -> Result { let header = value .header - .ok_or_else(error::invalid_signed_header_error)? + .ok_or_else(Error::invalid_signed_header)? .try_into()?; let commit = value .commit - .ok_or_else(error::invalid_signed_header_error)? + .ok_or_else(Error::invalid_signed_header)? .try_into()?; Self::new(header, commit) // Additional checks } @@ -46,7 +46,7 @@ impl SignedHeader { /// Constructor. pub fn new(header: block::Header, commit: block::Commit) -> Result { if header.height != commit.height { - return Err(error::invalid_signed_header_error()); + return Err(Error::invalid_signed_header()); } Ok(Self { header, commit }) } diff --git a/tendermint/src/block/size.rs b/tendermint/src/block/size.rs index 9a28aea15..e29edfb58 100644 --- a/tendermint/src/block/size.rs +++ b/tendermint/src/block/size.rs @@ -1,6 +1,6 @@ //! Block size parameters -use crate::error::{self, Error}; +use crate::error::Error; use std::convert::{TryFrom, TryInto}; use tendermint_proto::Protobuf; use { @@ -42,7 +42,7 @@ impl TryFrom for Size { max_bytes: value .max_bytes .try_into() - .map_err(error::integer_overflow_error)?, + .map_err(Error::integer_overflow)?, max_gas: value.max_gas, time_iota_ms: Self::default_time_iota_ms(), }) diff --git a/tendermint/src/chain/id.rs b/tendermint/src/chain/id.rs index 3221441ae..b6a75e465 100644 --- a/tendermint/src/chain/id.rs +++ b/tendermint/src/chain/id.rs @@ -1,6 +1,6 @@ //! Tendermint blockchain identifiers -use crate::error::{self, Error}; +use crate::error::Error; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryFrom; use std::{ @@ -27,13 +27,13 @@ impl TryFrom for Id { fn try_from(value: String) -> Result { if value.is_empty() || value.len() > MAX_LENGTH { - return Err(error::length_error()); + return Err(Error::length()); } for byte in value.as_bytes() { match byte { b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b'.' => (), - _ => return Err(error::parse_error("chain id charset".to_string())), + _ => return Err(Error::parse("chain id charset".to_string())), } } @@ -135,6 +135,7 @@ impl<'de> Deserialize<'de> for Id { #[cfg(test)] mod tests { use super::*; + use crate::error::ErrorDetail; const EXAMPLE_CHAIN_ID: &str = "gaia-9000"; @@ -152,7 +153,7 @@ mod tests { #[test] fn rejects_empty_chain_ids() { match "".parse::().unwrap_err().detail() { - error::ErrorDetail::Length(_) => {} + ErrorDetail::Length(_) => {} _ => panic!("expected length error"), } } @@ -161,7 +162,7 @@ mod tests { fn rejects_overlength_chain_ids() { let overlong_id = String::from_utf8(vec![b'x'; MAX_LENGTH + 1]).unwrap(); match overlong_id.parse::().unwrap_err().detail() { - error::ErrorDetail::Length(_) => {} + ErrorDetail::Length(_) => {} _ => panic!("expected length error"), } } diff --git a/tendermint/src/config.rs b/tendermint/src/config.rs index 6a533ff0e..bf5e59bb2 100644 --- a/tendermint/src/config.rs +++ b/tendermint/src/config.rs @@ -11,11 +11,7 @@ mod priv_validator_key; pub use self::{node_key::NodeKey, priv_validator_key::PrivValidatorKey}; -use crate::{ - error::{self, Error}, - genesis::Genesis, - net, node, Moniker, Timeout, -}; +use crate::{error::Error, genesis::Genesis, net, node, Moniker, Timeout}; use serde::{de, de::Error as _, ser, Deserialize, Serialize}; use std::{ collections::BTreeMap, @@ -108,7 +104,7 @@ pub struct TendermintConfig { impl TendermintConfig { /// Parse Tendermint `config.toml` pub fn parse_toml>(toml_string: T) -> Result { - let res = toml::from_str(toml_string.as_ref()).map_err(error::toml_error)?; + let res = toml::from_str(toml_string.as_ref()).map_err(Error::toml)?; Ok(res) } @@ -119,7 +115,7 @@ impl TendermintConfig { P: AsRef, { let toml_string = fs::read_to_string(path) - .map_err(|e| error::file_io_error(format!("{}", path.as_ref().display()), e))?; + .map_err(|e| Error::file_io(format!("{}", path.as_ref().display()), e))?; Self::parse_toml(toml_string) } @@ -128,9 +124,9 @@ impl TendermintConfig { pub fn load_genesis_file(&self, home: impl AsRef) -> Result { let path = home.as_ref().join(&self.genesis_file); let genesis_json = fs::read_to_string(&path) - .map_err(|e| error::file_io_error(format!("{}", path.display()), e))?; + .map_err(|e| Error::file_io(format!("{}", path.display()), e))?; - let res = serde_json::from_str(genesis_json.as_ref()).map_err(error::serde_json_error)?; + let res = serde_json::from_str(genesis_json.as_ref()).map_err(Error::serde_json)?; Ok(res) } @@ -209,17 +205,14 @@ impl FromStr for LogLevel { global = Some(parts[0].to_owned()); continue; } else if parts.len() != 2 { - return Err(error::parse_error(format!( - "error parsing log level: {}", - level - ))); + return Err(Error::parse(format!("error parsing log level: {}", level))); } let key = parts[0].to_owned(); let value = parts[1].to_owned(); if components.insert(key, value).is_some() { - return Err(error::parse_error(format!( + return Err(Error::parse(format!( "duplicate log level setting for: {}", level ))); diff --git a/tendermint/src/config/node_key.rs b/tendermint/src/config/node_key.rs index 37dcf8187..e1abe7e4a 100644 --- a/tendermint/src/config/node_key.rs +++ b/tendermint/src/config/node_key.rs @@ -1,11 +1,6 @@ //! Node keys -use crate::{ - error::{self, Error}, - node, - private_key::PrivateKey, - public_key::PublicKey, -}; +use crate::{error::Error, node, private_key::PrivateKey, public_key::PublicKey}; use serde::{Deserialize, Serialize}; use std::{fs, path::Path}; @@ -19,7 +14,7 @@ pub struct NodeKey { impl NodeKey { /// Parse `node_key.json` pub fn parse_json>(json_string: T) -> Result { - let res = serde_json::from_str(json_string.as_ref()).map_err(error::serde_json_error)?; + let res = serde_json::from_str(json_string.as_ref()).map_err(Error::serde_json)?; Ok(res) } @@ -29,7 +24,7 @@ impl NodeKey { P: AsRef, { let json_string = fs::read_to_string(path) - .map_err(|e| error::file_io_error(format!("{}", path.as_ref().display()), e))?; + .map_err(|e| Error::file_io(format!("{}", path.as_ref().display()), e))?; Self::parse_json(json_string) } diff --git a/tendermint/src/config/priv_validator_key.rs b/tendermint/src/config/priv_validator_key.rs index ae5566cea..f6f3d7bc4 100644 --- a/tendermint/src/config/priv_validator_key.rs +++ b/tendermint/src/config/priv_validator_key.rs @@ -1,12 +1,7 @@ //! Validator private keys use crate::public_key::TendermintKey; -use crate::{ - account, - error::{self, Error}, - private_key::PrivateKey, - public_key::PublicKey, -}; +use crate::{account, error::Error, private_key::PrivateKey, public_key::PublicKey}; use serde::{Deserialize, Serialize}; use std::{fs, path::Path}; @@ -27,7 +22,7 @@ impl PrivValidatorKey { /// Parse `priv_validator_key.json` pub fn parse_json>(json_string: T) -> Result { let result = - serde_json::from_str::(json_string.as_ref()).map_err(error::serde_json_error)?; + serde_json::from_str::(json_string.as_ref()).map_err(Error::serde_json)?; // Validate that the parsed key type is usable as a consensus key TendermintKey::new_consensus_key(result.priv_key.public_key())?; @@ -41,7 +36,7 @@ impl PrivValidatorKey { P: AsRef, { let json_string = fs::read_to_string(path) - .map_err(|e| error::file_io_error(format!("{}", path.as_ref().display()), e))?; + .map_err(|e| Error::file_io(format!("{}", path.as_ref().display()), e))?; Self::parse_json(json_string) } diff --git a/tendermint/src/consensus/params.rs b/tendermint/src/consensus/params.rs index 0e6291d38..3408ffe5d 100644 --- a/tendermint/src/consensus/params.rs +++ b/tendermint/src/consensus/params.rs @@ -1,6 +1,6 @@ //! Tendermint consensus parameters -use crate::error::{self, Error}; +use crate::error::Error; use crate::{block, evidence, public_key}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; @@ -35,15 +35,15 @@ impl TryFrom for Params { Ok(Self { block: value .block - .ok_or_else(|| error::invalid_block_error("missing block".to_string()))? + .ok_or_else(|| Error::invalid_block("missing block".to_string()))? .try_into()?, evidence: value .evidence - .ok_or_else(error::invalid_evidence_error)? + .ok_or_else(Error::invalid_evidence)? .try_into()?, validator: value .validator - .ok_or_else(error::invalid_validator_params_error)? + .ok_or_else(Error::invalid_validator_params)? .try_into()?, version: value.version.map(TryFrom::try_from).transpose()?, }) diff --git a/tendermint/src/evidence.rs b/tendermint/src/evidence.rs index e4968007c..1aca9db12 100644 --- a/tendermint/src/evidence.rs +++ b/tendermint/src/evidence.rs @@ -1,7 +1,7 @@ //! Evidence of malfeasance by validators (i.e. signing conflicting votes). use crate::{ - block::signed_header::SignedHeader, error, error::Error, serializers, vote::Power, Time, Vote, + block::signed_header::SignedHeader, error::Error, serializers, vote::Power, Time, Vote, }; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; @@ -40,7 +40,7 @@ impl TryFrom for Evidence { type Error = Error; fn try_from(value: RawEvidence) -> Result { - match value.sum.ok_or_else(error::invalid_evidence_error)? { + match value.sum.ok_or_else(Error::invalid_evidence)? { Sum::DuplicateVoteEvidence(ev) => Ok(Evidence::DuplicateVote(ev.try_into()?)), Sum::LightClientAttackEvidence(_ev) => Ok(Evidence::LightClientAttackEvidence), } @@ -76,18 +76,15 @@ impl TryFrom for DuplicateVoteEvidence { Ok(Self { vote_a: value .vote_a - .ok_or_else(error::missing_evidence_error)? + .ok_or_else(Error::missing_evidence)? .try_into()?, vote_b: value .vote_b - .ok_or_else(error::missing_evidence_error)? + .ok_or_else(Error::missing_evidence)? .try_into()?, total_voting_power: value.total_voting_power.try_into()?, validator_power: value.validator_power.try_into()?, - timestamp: value - .timestamp - .ok_or_else(error::missing_timestamp_error)? - .into(), + timestamp: value.timestamp.ok_or_else(Error::missing_timestamp)?.into(), }) } } @@ -108,7 +105,7 @@ impl DuplicateVoteEvidence { /// constructor pub fn new(vote_a: Vote, vote_b: Vote) -> Result { if vote_a.height != vote_b.height { - return Err(error::invalid_evidence_error()); + return Err(Error::invalid_evidence()); } // Todo: make more assumptions about what is considered a valid evidence for duplicate vote Ok(Self { @@ -233,10 +230,10 @@ impl TryFrom for Params { max_age_num_blocks: value .max_age_num_blocks .try_into() - .map_err(error::negative_max_age_num_error)?, + .map_err(Error::negative_max_age_num)?, max_age_duration: value .max_age_duration - .ok_or_else(error::missing_max_age_duration_error)? + .ok_or_else(Error::missing_max_age_duration)? .try_into()?, max_bytes: value.max_bytes, }) @@ -275,14 +272,8 @@ impl TryFrom for Duration { fn try_from(value: RawDuration) -> Result { Ok(Self(std::time::Duration::new( - value - .seconds - .try_into() - .map_err(error::integer_overflow_error)?, - value - .nanos - .try_into() - .map_err(error::integer_overflow_error)?, + value.seconds.try_into().map_err(Error::integer_overflow)?, + value.nanos.try_into().map_err(Error::integer_overflow)?, ))) } } diff --git a/tendermint/src/hash.rs b/tendermint/src/hash.rs index 3d0713c41..de70237c0 100644 --- a/tendermint/src/hash.rs +++ b/tendermint/src/hash.rs @@ -1,6 +1,6 @@ //! Hash functions and their outputs -use crate::error::{self, Error}; +use crate::error::Error; use serde::de::Error as _; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryFrom; @@ -66,7 +66,7 @@ impl Hash { h.copy_from_slice(bytes); Ok(Hash::Sha256(h)) } else { - Err(error::invalid_hash_size_error()) + Err(Error::invalid_hash_size()) } } } @@ -82,7 +82,7 @@ impl Hash { let mut h = [0u8; SHA256_HASH_SIZE]; Hex::upper_case() .decode_to_slice(s.as_bytes(), &mut h) - .map_err(error::subtle_encoding_error)?; + .map_err(Error::subtle_encoding)?; Ok(Hash::Sha256(h)) } } @@ -214,12 +214,12 @@ impl AppHash { /// Decode a `Hash` from upper-case hexadecimal pub fn from_hex_upper(s: &str) -> Result { if s.len() % 2 != 0 { - return Err(error::invalid_app_hash_length_error()); + return Err(Error::invalid_app_hash_length()); } let mut h = vec![0; s.len() / 2]; Hex::upper_case() .decode_to_slice(s.as_bytes(), &mut h) - .map_err(error::subtle_encoding_error)?; + .map_err(Error::subtle_encoding)?; Ok(AppHash(h)) } } diff --git a/tendermint/src/net.rs b/tendermint/src/net.rs index 4ec0a72be..6e9c4ceff 100644 --- a/tendermint/src/net.rs +++ b/tendermint/src/net.rs @@ -1,9 +1,6 @@ //! Remote addresses (`tcp://` or `unix://`) -use crate::{ - error::{self, Error}, - node, -}; +use crate::{error::Error, node}; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; use std::{ @@ -82,7 +79,7 @@ impl FromStr for Address { // If the address has no scheme, assume it's TCP format!("{}{}", TCP_PREFIX, addr) }; - let url = Url::parse(&prefixed_addr).map_err(error::parse_url_error)?; + let url = Url::parse(&prefixed_addr).map_err(Error::parse_url)?; match url.scheme() { "tcp" => Ok(Self::Tcp { peer_id: if !url.username().is_empty() { @@ -93,20 +90,17 @@ impl FromStr for Address { host: url .host_str() .ok_or_else(|| { - error::parse_error(format!("invalid TCP address (missing host): {}", addr)) + Error::parse(format!("invalid TCP address (missing host): {}", addr)) })? .to_owned(), port: url.port().ok_or_else(|| { - error::parse_error(format!("invalid TCP address (missing port): {}", addr)) + Error::parse(format!("invalid TCP address (missing port): {}", addr)) })?, }), "unix" => Ok(Self::Unix { path: PathBuf::from(url.path()), }), - _ => Err(error::parse_error(format!( - "invalid address scheme: {:?}", - addr - ))), + _ => Err(Error::parse(format!("invalid address scheme: {:?}", addr))), } } } diff --git a/tendermint/src/node/id.rs b/tendermint/src/node/id.rs index 6e6a4ffc0..160953c7a 100644 --- a/tendermint/src/node/id.rs +++ b/tendermint/src/node/id.rs @@ -12,7 +12,7 @@ use subtle::{self, ConstantTimeEq}; use subtle_encoding::hex; use crate::{ - error::{self, Error}, + error::Error, public_key::{Ed25519, PublicKey}, }; @@ -80,10 +80,10 @@ impl FromStr for Id { // Accept either upper or lower case hex let bytes = hex::decode_upper(s) .or_else(|_| hex::decode(s)) - .map_err(error::subtle_encoding_error)?; + .map_err(Error::subtle_encoding)?; if bytes.len() != LENGTH { - return Err(error::parse_error("invalid length".to_string())); + return Err(Error::parse("invalid length".to_string())); } let mut result_bytes = [0u8; LENGTH]; @@ -105,7 +105,7 @@ impl TryFrom for Id { match pk { PublicKey::Ed25519(ed25519) => Ok(Id::from(ed25519)), #[cfg(feature = "secp256k1")] - _ => Err(error::unsupported_key_type_error()), + _ => Err(Error::unsupported_key_type()), } } } diff --git a/tendermint/src/proposal.rs b/tendermint/src/proposal.rs index 6f761fc38..f2c508f74 100644 --- a/tendermint/src/proposal.rs +++ b/tendermint/src/proposal.rs @@ -11,7 +11,7 @@ pub use sign_proposal::{SignProposalRequest, SignedProposalResponse}; use crate::block::{Height, Id as BlockId, Round}; use crate::chain::Id as ChainId; use crate::consensus::State; -use crate::error::{self, Error}; +use crate::error::Error; use crate::Signature; use crate::Time; use bytes::BufMut; @@ -45,7 +45,7 @@ impl TryFrom for Proposal { fn try_from(value: RawProposal) -> Result { if value.pol_round < -1 { - return Err(error::negative_pol_round_error()); + return Err(Error::negative_pol_round()); } let pol_round = match value.pol_round { -1 => None, diff --git a/tendermint/src/proposal/canonical_proposal.rs b/tendermint/src/proposal/canonical_proposal.rs index 43f1d4212..13de36f93 100644 --- a/tendermint/src/proposal/canonical_proposal.rs +++ b/tendermint/src/proposal/canonical_proposal.rs @@ -3,7 +3,7 @@ use super::Type; use crate::block::{Height, Id as BlockId, Round}; use crate::chain::Id as ChainId; -use crate::error::{self, Error}; +use crate::error::Error; use crate::Time; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::CanonicalProposal as RawCanonicalProposal; @@ -35,14 +35,13 @@ impl TryFrom for CanonicalProposal { fn try_from(value: RawCanonicalProposal) -> Result { if value.pol_round < -1 { - return Err(error::negative_pol_round_error()); + return Err(Error::negative_pol_round()); } - let round = - Round::try_from(i32::try_from(value.round).map_err(error::integer_overflow_error)?)?; + let round = Round::try_from(i32::try_from(value.round).map_err(Error::integer_overflow)?)?; let pol_round = match value.pol_round { -1 => None, n => Some(Round::try_from( - i32::try_from(n).map_err(error::integer_overflow_error)?, + i32::try_from(n).map_err(Error::integer_overflow)?, )?), }; // If the Hash is empty in BlockId, the BlockId should be empty. diff --git a/tendermint/src/proposal/msg_type.rs b/tendermint/src/proposal/msg_type.rs index df0783b26..e06319944 100644 --- a/tendermint/src/proposal/msg_type.rs +++ b/tendermint/src/proposal/msg_type.rs @@ -1,4 +1,4 @@ -use crate::error::{self, Error}; +use crate::error::Error; use serde::de::Error as _; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use std::convert::TryFrom; @@ -20,7 +20,7 @@ impl TryFrom for Type { fn try_from(value: i32) -> Result { match value { 32 => Ok(Type::Proposal), - _ => Err(error::invalid_message_type_error()), + _ => Err(Error::invalid_message_type()), } } } diff --git a/tendermint/src/proposal/sign_proposal.rs b/tendermint/src/proposal/sign_proposal.rs index 3dce8ec0a..e8a23e70a 100644 --- a/tendermint/src/proposal/sign_proposal.rs +++ b/tendermint/src/proposal/sign_proposal.rs @@ -1,6 +1,6 @@ use super::Proposal; use crate::chain::Id as ChainId; -use crate::error::{self, Error}; +use crate::error::Error; use bytes::BufMut; use std::convert::{TryFrom, TryInto}; use tendermint_proto::privval::RemoteSignerError; @@ -26,7 +26,7 @@ impl TryFrom for SignProposalRequest { fn try_from(value: RawSignProposalRequest) -> Result { if value.proposal.is_none() { - return Err(error::no_proposal_found_error()); + return Err(Error::no_proposal_found()); } Ok(SignProposalRequest { proposal: Proposal::try_from(value.proposal.unwrap())?, diff --git a/tendermint/src/public_key.rs b/tendermint/src/public_key.rs index bb502b2f1..ab6b2068c 100644 --- a/tendermint/src/public_key.rs +++ b/tendermint/src/public_key.rs @@ -9,10 +9,7 @@ mod pub_key_response; pub use pub_key_request::PubKeyRequest; pub use pub_key_response::PubKeyResponse; -use crate::{ - error::{self, Error}, - signature::Signature, -}; +use crate::{error::Error, signature::Signature}; use serde::{de, ser, Deserialize, Serialize}; use signature::Verifier as _; use std::convert::TryFrom; @@ -64,17 +61,17 @@ impl TryFrom for PublicKey { fn try_from(value: RawPublicKey) -> Result { let sum = &value .sum - .ok_or_else(|| error::invalid_key_error("empty sum".to_string()))?; + .ok_or_else(|| Error::invalid_key("empty sum".to_string()))?; if let Sum::Ed25519(b) = sum { return Self::from_raw_ed25519(b) - .ok_or_else(|| error::invalid_key_error("malformed ed25519 key".to_string())); + .ok_or_else(|| Error::invalid_key("malformed ed25519 key".to_string())); } #[cfg(feature = "secp256k1")] if let Sum::Secp256k1(b) = sum { return Self::from_raw_secp256k1(b) - .ok_or_else(|| error::invalid_key_error("malformed key".to_string())); + .ok_or_else(|| Error::invalid_key("malformed key".to_string())); } - Err(error::invalid_key_error("not an ed25519 key".to_string())) + Err(Error::invalid_key("not an ed25519 key".to_string())) } } @@ -135,16 +132,12 @@ impl PublicKey { match self { PublicKey::Ed25519(pk) => match signature { Signature::Ed25519(sig) => pk.verify(msg, sig).map_err(|_| { - error::signature_invalid_error( - "Ed25519 signature verification failed".to_string(), - ) + Error::signature_invalid("Ed25519 signature verification failed".to_string()) }), - Signature::None => Err(error::signature_invalid_error( - "missing signature".to_string(), - )), + Signature::None => Err(Error::signature_invalid("missing signature".to_string())), }, #[cfg(feature = "secp256k1")] - PublicKey::Secp256k1(_) => Err(error::invalid_key_error( + PublicKey::Secp256k1(_) => Err(Error::invalid_key( "unsupported signature algorithm (ECDSA/secp256k1)".to_string(), )), } @@ -245,7 +238,7 @@ impl TendermintKey { #[allow(unreachable_patterns)] match public_key { PublicKey::Ed25519(_) => Ok(TendermintKey::AccountKey(public_key)), - _ => Err(error::invalid_key_error( + _ => Err(Error::invalid_key( "only ed25519 consensus keys are supported".to_string(), )), } @@ -302,7 +295,7 @@ impl FromStr for Algorithm { match s { "ed25519" => Ok(Algorithm::Ed25519), "secp256k1" => Ok(Algorithm::Secp256k1), - _ => Err(error::parse_error(format!("invalid algorithm: {}", s))), + _ => Err(Error::parse(format!("invalid algorithm: {}", s))), } } } diff --git a/tendermint/src/signature.rs b/tendermint/src/signature.rs index a2a6a83e2..90f4a752b 100644 --- a/tendermint/src/signature.rs +++ b/tendermint/src/signature.rs @@ -6,7 +6,7 @@ pub use signature::{Signer, Verifier}; #[cfg(feature = "secp256k1")] pub use k256::ecdsa::Signature as Secp256k1; -use crate::error::{self, Error}; +use crate::error::Error; use std::convert::TryFrom; use tendermint_proto::Protobuf; @@ -31,7 +31,7 @@ impl TryFrom> for Signature { return Ok(Self::default()); } if value.len() != ED25519_SIGNATURE_SIZE { - return Err(error::invalid_signature_id_length_error()); + return Err(Error::invalid_signature_id_length()); } let mut slice: [u8; ED25519_SIGNATURE_SIZE] = [0; ED25519_SIGNATURE_SIZE]; slice.copy_from_slice(&value[..]); diff --git a/tendermint/src/time.rs b/tendermint/src/time.rs index 10044e8f5..35dc12103 100644 --- a/tendermint/src/time.rs +++ b/tendermint/src/time.rs @@ -11,7 +11,7 @@ use tendermint_proto::google::protobuf::Timestamp; use tendermint_proto::serializers::timestamp; use tendermint_proto::Protobuf; -use crate::error::{self, Error}; +use crate::error::Error; /// Tendermint timestamps /// @@ -63,13 +63,13 @@ impl Time { self.0 .signed_duration_since(other.0) .to_std() - .map_err(error::out_of_range_error) + .map_err(Error::out_of_range) } /// Parse [`Time`] from an RFC 3339 date pub fn parse_from_rfc3339(s: &str) -> Result { let date = DateTime::parse_from_rfc3339(s) - .map_err(error::chrono_parse_error)? + .map_err(Error::chrono_parse)? .with_timezone(&Utc); Ok(Time(date)) } diff --git a/tendermint/src/timeout.rs b/tendermint/src/timeout.rs index 7a4dbef19..d4e75e939 100644 --- a/tendermint/src/timeout.rs +++ b/tendermint/src/timeout.rs @@ -1,4 +1,4 @@ -use crate::error::{self, Error}; +use crate::error::Error; use serde::{de, de::Error as _, ser, Deserialize, Serialize}; use std::{fmt, ops::Deref, str::FromStr, time::Duration}; @@ -33,20 +33,20 @@ impl FromStr for Timeout { fn from_str(s: &str) -> Result { // Timeouts are either 'ms' or 's', and should always end with 's' if s.len() < 2 || !s.ends_with('s') { - return Err(error::parse_error("invalid units".to_string())); + return Err(Error::parse("invalid units".to_string())); } let units = match s.chars().nth(s.len() - 2) { Some('m') => "ms", Some('0'..='9') => "s", - _ => return Err(error::parse_error("invalid units".to_string())), + _ => return Err(Error::parse("invalid units".to_string())), }; let numeric_part = s.chars().take(s.len() - units.len()).collect::(); let numeric_value = numeric_part .parse::() - .map_err(|e| error::parse_int_error(numeric_part, e))?; + .map_err(|e| Error::parse_int(numeric_part, e))?; let duration = match units { "s" => Duration::from_secs(numeric_value), diff --git a/tendermint/src/validator.rs b/tendermint/src/validator.rs index 35376217e..36a14ed0d 100644 --- a/tendermint/src/validator.rs +++ b/tendermint/src/validator.rs @@ -3,7 +3,7 @@ use serde::{de::Error as _, Deserialize, Deserializer, Serialize}; use subtle_encoding::base64; -use crate::{account, error, hash::Hash, merkle, vote, Error, PublicKey, Signature}; +use crate::{account, hash::Hash, merkle, vote, Error, PublicKey, Signature}; use std::convert::{TryFrom, TryInto}; use tendermint_proto::types::SimpleValidator as RawSimpleValidator; @@ -37,7 +37,7 @@ impl TryFrom for Set { // Ensure that the raw voting power matches the computed one let raw_voting_power = value.total_voting_power.try_into()?; if raw_voting_power != validator_set.total_voting_power() { - return Err(error::raw_voting_power_mismatch_error( + return Err(Error::raw_voting_power_mismatch( raw_voting_power, validator_set.total_voting_power(), )); @@ -92,7 +92,7 @@ impl Set { .iter() .find(|v| v.address == proposer_address) .cloned() - .ok_or_else(|| error::proposer_not_found_error(proposer_address))?; + .ok_or_else(|| Error::proposer_not_found(proposer_address))?; // Create the validator set with the given proposer. // This is required by IBC on-chain validation. @@ -171,7 +171,7 @@ impl TryFrom for Info { address: value.address.try_into()?, pub_key: value .pub_key - .ok_or_else(error::missing_public_key_error)? + .ok_or_else(Error::missing_public_key)? .try_into()?, power: value.voting_power.try_into()?, name: None, diff --git a/tendermint/src/vote.rs b/tendermint/src/vote.rs index 95138054b..986cbf3af 100644 --- a/tendermint/src/vote.rs +++ b/tendermint/src/vote.rs @@ -11,7 +11,7 @@ pub use self::sign_vote::*; pub use self::validator_index::ValidatorIndex; use crate::chain::Id as ChainId; use crate::consensus::State; -use crate::error::{self, Error}; +use crate::error::Error; use crate::hash; use crate::{account, block, Signature, Time}; use bytes::BufMut; @@ -65,7 +65,7 @@ impl TryFrom for Vote { fn try_from(value: RawVote) -> Result { if value.timestamp.is_none() { - return Err(error::missing_timestamp_error()); + return Err(Error::missing_timestamp()); } Ok(Vote { vote_type: value.r#type.try_into()?, @@ -231,7 +231,7 @@ impl TryFrom for Type { match value { 1 => Ok(Type::Prevote), 2 => Ok(Type::Precommit), - _ => Err(error::invalid_message_type_error()), + _ => Err(Error::invalid_message_type()), } } } @@ -259,7 +259,7 @@ impl FromStr for Type { match s { "Prevote" => Ok(Self::Prevote), "Precommit" => Ok(Self::Precommit), - _ => Err(error::invalid_message_type_error()), + _ => Err(Error::invalid_message_type()), } } } diff --git a/tendermint/src/vote/canonical_vote.rs b/tendermint/src/vote/canonical_vote.rs index 91d93e825..92cfa6e62 100644 --- a/tendermint/src/vote/canonical_vote.rs +++ b/tendermint/src/vote/canonical_vote.rs @@ -1,5 +1,5 @@ use crate::chain::Id as ChainId; -use crate::error::{self, Error}; +use crate::error::Error; use crate::{block, Time}; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; @@ -37,12 +37,9 @@ impl TryFrom for CanonicalVote { fn try_from(value: RawCanonicalVote) -> Result { if value.timestamp.is_none() { - return Err(error::missing_timestamp_error()); + return Err(Error::missing_timestamp()); } - let _val: i32 = value - .round - .try_into() - .map_err(error::integer_overflow_error)?; + let _val: i32 = value.round.try_into().map_err(Error::integer_overflow)?; // If the Hash is empty in BlockId, the BlockId should be empty. // See: https://github.com/informalsystems/tendermint-rs/issues/663 diff --git a/tendermint/src/vote/power.rs b/tendermint/src/vote/power.rs index 3d6a02021..86b4bc09e 100644 --- a/tendermint/src/vote/power.rs +++ b/tendermint/src/vote/power.rs @@ -5,7 +5,7 @@ use std::fmt; use serde::{de::Error as _, Deserialize, Deserializer, Serialize, Serializer}; -use crate::error::{self, Error}; +use crate::error::Error; /// Voting power #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Default)] @@ -21,9 +21,7 @@ impl TryFrom for Power { type Error = Error; fn try_from(value: i64) -> Result { - Ok(Power( - value.try_into().map_err(error::negative_power_error)?, - )) + Ok(Power(value.try_into().map_err(Error::negative_power)?)) } } @@ -37,7 +35,7 @@ impl TryFrom for Power { type Error = Error; fn try_from(value: u64) -> Result { - let _val: i64 = value.try_into().map_err(error::integer_overflow_error)?; + let _val: i64 = value.try_into().map_err(Error::integer_overflow)?; Ok(Power(value)) } diff --git a/tendermint/src/vote/sign_vote.rs b/tendermint/src/vote/sign_vote.rs index d0690d4e9..4d07ce394 100644 --- a/tendermint/src/vote/sign_vote.rs +++ b/tendermint/src/vote/sign_vote.rs @@ -1,5 +1,5 @@ use crate::chain; -use crate::error::{self, Error}; +use crate::error::Error; use crate::Vote; use bytes::BufMut; use std::convert::{TryFrom, TryInto}; @@ -23,10 +23,7 @@ impl TryFrom for SignVoteRequest { type Error = Error; fn try_from(value: RawSignVoteRequest) -> Result { - let vote = value - .vote - .ok_or_else(error::no_vote_found_error)? - .try_into()?; + let vote = value.vote.ok_or_else(Error::no_vote_found)?.try_into()?; let chain_id = value.chain_id.try_into()?; diff --git a/tendermint/src/vote/validator_index.rs b/tendermint/src/vote/validator_index.rs index 684ca7e26..e11ad1834 100644 --- a/tendermint/src/vote/validator_index.rs +++ b/tendermint/src/vote/validator_index.rs @@ -1,4 +1,4 @@ -use crate::error::{self, Error}; +use crate::error::Error; use std::convert::TryInto; use std::{ convert::TryFrom, @@ -15,9 +15,7 @@ impl TryFrom for ValidatorIndex { fn try_from(value: i32) -> Result { Ok(ValidatorIndex( - value - .try_into() - .map_err(error::negative_validator_index_error)?, + value.try_into().map_err(Error::negative_validator_index)?, )) } } @@ -32,7 +30,7 @@ impl TryFrom for ValidatorIndex { type Error = Error; fn try_from(value: u32) -> Result { - let _val: i32 = value.try_into().map_err(error::integer_overflow_error)?; + let _val: i32 = value.try_into().map_err(Error::integer_overflow)?; Ok(ValidatorIndex(value)) } } @@ -48,7 +46,7 @@ impl TryFrom for ValidatorIndex { fn try_from(value: usize) -> Result { Ok(ValidatorIndex( - value.try_into().map_err(error::integer_overflow_error)?, + value.try_into().map_err(Error::integer_overflow)?, )) } } @@ -87,7 +85,7 @@ impl FromStr for ValidatorIndex { fn from_str(s: &str) -> Result { ValidatorIndex::try_from( s.parse::() - .map_err(|e| error::parse_int_error("validator index decode".to_string(), e))?, + .map_err(|e| Error::parse_int("validator index decode".to_string(), e))?, ) } } From bef6287e67e7c87f50bcfa5b1500ad6e4e71a809 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 26 Jul 2021 09:45:14 +0200 Subject: [PATCH 18/25] set std feature in flex-error instead of individual crates --- Cargo.toml | 2 +- abci/Cargo.toml | 6 +++++- light-client/Cargo.toml | 6 ++++-- p2p/Cargo.toml | 6 +++++- proto/Cargo.toml | 8 +++++++- rpc/Cargo.toml | 6 ++++-- tendermint/Cargo.toml | 6 +++++- 7 files changed, 31 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b7e9663f4..b1db50c86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,4 +22,4 @@ exclude = [ opt-level = "s" [patch.crates-io] -flex-error = { git = "https://github.com/informalsystems/flex-error", rev = "97cc1ce7fa798c9c30813bae718c52f4140cd003" } +flex-error = { git = "https://github.com/informalsystems/flex-error", rev = "ae7a190911bad1a9a3a9dff77560f8594437e822" } diff --git a/abci/Cargo.toml b/abci/Cargo.toml index 3bd9c59ae..6fe9805d5 100644 --- a/abci/Cargo.toml +++ b/abci/Cargo.toml @@ -21,10 +21,14 @@ path = "src/application/kvstore/main.rs" required-features = [ "binary", "kvstore-app" ] [features] +default = ["std"] client = [] echo-app = [] kvstore-app = [] binary = [ "structopt", "tracing-subscriber" ] +std = [ + "flex-error/std" +] [dependencies] bytes = "1.0" @@ -32,7 +36,7 @@ eyre = "0.6" prost = "0.7" tendermint-proto = { version = "0.21.0", path = "../proto" } tracing = "0.1" -flex-error = "0.4.0" +flex-error = { version = "0.4.1", default-features = false } structopt = { version = "0.3", optional = true } tracing-subscriber = { version = "0.2", optional = true } diff --git a/light-client/Cargo.toml b/light-client/Cargo.toml index cfa98cae7..5a3caff44 100644 --- a/light-client/Cargo.toml +++ b/light-client/Cargo.toml @@ -29,11 +29,13 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["std", "rpc-client", "lightstore-sled"] -std = [] rpc-client = ["tokio", "tendermint-rpc/http-client"] secp256k1 = ["tendermint/secp256k1", "tendermint-rpc/secp256k1"] lightstore-sled = ["sled"] unstable = [] +std = [ + "flex-error/std" +] [dependencies] tendermint = { version = "0.21.0", path = "../tendermint" } @@ -49,7 +51,7 @@ serde_derive = "1.0.106" sled = { version = "0.34.3", optional = true } static_assertions = "1.1.0" tokio = { version = "1.0", features = ["rt"], optional = true } -flex-error = "0.4.0" +flex-error = { version = "0.4.1", default-features = false } [dev-dependencies] tendermint-testgen = { path = "../testgen" } diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index 4be9f6ec1..2cef954e1 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -23,7 +23,11 @@ description = """ test = false [features] +default = ["std"] amino = ["prost-amino", "prost-amino-derive"] +std = [ + "flex-error/std" +] [dependencies] chacha20poly1305 = "0.8" @@ -40,7 +44,7 @@ x25519-dalek = "1.1" zeroize = "1" signature = "1.3.0" aead = "0.4.1" -flex-error = "0.4.0" +flex-error = { version = "0.4.1", default-features = false } # path dependencies tendermint = { path = "../tendermint", version = "0.21.0" } diff --git a/proto/Cargo.toml b/proto/Cargo.toml index d01c68791..1311d94e2 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -26,7 +26,13 @@ serde_bytes = "0.11" num-traits = "0.2" num-derive = "0.3" chrono = { version = "0.4", features = ["serde"] } -flex-error = "0.4.0" +flex-error = { version = "0.4.1", default-features = false } [dev-dependencies] serde_json = "1.0" + +[features] +default = ["std"] +std = [ + "flex-error/std" +] diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index 6502a9ab0..a11a21d6a 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -30,7 +30,6 @@ required-features = [ "cli" ] [features] default = ["std"] -std = [] cli = [ "http-client", "structopt", @@ -60,6 +59,9 @@ websocket-client = [ "tokio/time", "tracing" ] +std = [ + "flex-error/std" +] [dependencies] bytes = "1.0" @@ -76,7 +78,7 @@ uuid = { version = "0.8", default-features = false } subtle-encoding = { version = "0.5", features = ["bech32-preview"] } url = "2.2" walkdir = "2.3" -flex-error = "0.4.0" +flex-error = { version = "0.4.1", default-features = false } async-trait = { version = "0.1", optional = true } async-tungstenite = { version = "0.12", features = ["tokio-runtime", "tokio-rustls"], optional = true } futures = { version = "0.3", optional = true } diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index 3da692ce9..3d9f8281b 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -55,14 +55,18 @@ tendermint-proto = { version = "0.21.0", path = "../proto" } toml = { version = "0.5" } url = { version = "2.2" } zeroize = { version = "1.1", features = ["zeroize_derive"] } -flex-error = "0.4.0" +flex-error = { version = "0.4.1", default-features = false } time = "0.1.40" k256 = { version = "0.9", optional = true, features = ["ecdsa"] } ripemd160 = { version = "0.9", optional = true } [features] +default = ["std"] secp256k1 = ["k256", "ripemd160"] +std = [ + "flex-error/std" +] [dev-dependencies] pretty_assertions = "0.7.2" From da2ed80c834a2199ecd8da2eb1c083f1a7bc49e8 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 26 Jul 2021 07:51:26 +0000 Subject: [PATCH 19/25] Add flex-error patch to tools/Cargo.toml --- tools/Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tools/Cargo.toml b/tools/Cargo.toml index ce4a0ef08..fdfc7136b 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -6,3 +6,6 @@ members = [ "proto-compiler", "rpc-probe" ] + +[patch.crates-io] +flex-error = { git = "https://github.com/informalsystems/flex-error", rev = "ae7a190911bad1a9a3a9dff77560f8594437e822" } From bb30f3094fe0de901c935eadbb89f90e8590a9e2 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 26 Jul 2021 14:31:40 +0200 Subject: [PATCH 20/25] Use published version of flex-error v0.4.1 --- Cargo.toml | 3 --- tools/Cargo.toml | 3 --- 2 files changed, 6 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b1db50c86..712da3286 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,3 @@ exclude = [ [profile.release.package.tendermint-light-client-js] # Tell `rustc` to optimize for small code size. opt-level = "s" - -[patch.crates-io] -flex-error = { git = "https://github.com/informalsystems/flex-error", rev = "ae7a190911bad1a9a3a9dff77560f8594437e822" } diff --git a/tools/Cargo.toml b/tools/Cargo.toml index fdfc7136b..ce4a0ef08 100644 --- a/tools/Cargo.toml +++ b/tools/Cargo.toml @@ -6,6 +6,3 @@ members = [ "proto-compiler", "rpc-probe" ] - -[patch.crates-io] -flex-error = { git = "https://github.com/informalsystems/flex-error", rev = "ae7a190911bad1a9a3a9dff77560f8594437e822" } From 90a0df2b1ec4f9f6020ee0e9f56fd4b21935eb40 Mon Sep 17 00:00:00 2001 From: Soares Chen Date: Mon, 26 Jul 2021 14:52:35 +0200 Subject: [PATCH 21/25] Enable flex-error/eyre_tracer feature by default --- abci/Cargo.toml | 4 ++-- light-client/Cargo.toml | 3 ++- p2p/Cargo.toml | 3 ++- proto/Cargo.toml | 3 ++- rpc/Cargo.toml | 3 ++- tendermint/Cargo.toml | 3 ++- 6 files changed, 12 insertions(+), 7 deletions(-) diff --git a/abci/Cargo.toml b/abci/Cargo.toml index 6fe9805d5..cb6a70b16 100644 --- a/abci/Cargo.toml +++ b/abci/Cargo.toml @@ -21,7 +21,8 @@ path = "src/application/kvstore/main.rs" required-features = [ "binary", "kvstore-app" ] [features] -default = ["std"] +default = ["std", "eyre_tracer"] +eyre_tracer = ["flex-error/eyre_tracer"] client = [] echo-app = [] kvstore-app = [] @@ -32,7 +33,6 @@ std = [ [dependencies] bytes = "1.0" -eyre = "0.6" prost = "0.7" tendermint-proto = { version = "0.21.0", path = "../proto" } tracing = "0.1" diff --git a/light-client/Cargo.toml b/light-client/Cargo.toml index 5a3caff44..6c5c6b551 100644 --- a/light-client/Cargo.toml +++ b/light-client/Cargo.toml @@ -28,7 +28,8 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [features] -default = ["std", "rpc-client", "lightstore-sled"] +default = ["std", "eyre_tracer", "rpc-client", "lightstore-sled"] +eyre_tracer = ["flex-error/eyre_tracer"] rpc-client = ["tokio", "tendermint-rpc/http-client"] secp256k1 = ["tendermint/secp256k1", "tendermint-rpc/secp256k1"] lightstore-sled = ["sled"] diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index 2cef954e1..09dea5d5d 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -23,7 +23,8 @@ description = """ test = false [features] -default = ["std"] +default = ["std", "eyre_tracer"] +eyre_tracer = ["flex-error/eyre_tracer"] amino = ["prost-amino", "prost-amino-derive"] std = [ "flex-error/std" diff --git a/proto/Cargo.toml b/proto/Cargo.toml index 1311d94e2..b41f165de 100644 --- a/proto/Cargo.toml +++ b/proto/Cargo.toml @@ -32,7 +32,8 @@ flex-error = { version = "0.4.1", default-features = false } serde_json = "1.0" [features] -default = ["std"] +default = ["std", "eyre_tracer"] +eyre_tracer = ["flex-error/eyre_tracer"] std = [ "flex-error/std" ] diff --git a/rpc/Cargo.toml b/rpc/Cargo.toml index a11a21d6a..6fbc00e45 100644 --- a/rpc/Cargo.toml +++ b/rpc/Cargo.toml @@ -29,7 +29,8 @@ path = "src/client/bin/main.rs" required-features = [ "cli" ] [features] -default = ["std"] +default = ["std", "eyre_tracer"] +eyre_tracer = ["flex-error/eyre_tracer"] cli = [ "http-client", "structopt", diff --git a/tendermint/Cargo.toml b/tendermint/Cargo.toml index 3d9f8281b..bbb4b5991 100644 --- a/tendermint/Cargo.toml +++ b/tendermint/Cargo.toml @@ -62,7 +62,8 @@ k256 = { version = "0.9", optional = true, features = ["ecdsa"] } ripemd160 = { version = "0.9", optional = true } [features] -default = ["std"] +default = ["std", "eyre_tracer"] +eyre_tracer = ["flex-error/eyre_tracer"] secp256k1 = ["k256", "ripemd160"] std = [ "flex-error/std" From 2ea6da457360f455c6a7b735cea1c4db7ff9da0c Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Tue, 3 Aug 2021 15:39:40 -0400 Subject: [PATCH 22/25] Add .changelog entry (#940) Signed-off-by: Thane Thomson --- .changelog/unreleased/breaking-changes/923-flex-error.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changelog/unreleased/breaking-changes/923-flex-error.md diff --git a/.changelog/unreleased/breaking-changes/923-flex-error.md b/.changelog/unreleased/breaking-changes/923-flex-error.md new file mode 100644 index 000000000..78a0e2912 --- /dev/null +++ b/.changelog/unreleased/breaking-changes/923-flex-error.md @@ -0,0 +1,5 @@ +- All crates' error handling has been refactored to make use of + [`flex-error`](https://github.com/informalsystems/flex-error/). This gives + users greater flexibility in terms of the error handling/reporting systems + they want to use and is a critical step towards `no_std` support. + ([#923](https://github.com/informalsystems/tendermint-rs/pull/923)) From bc2f0cba5c440ec64731f655f5632da3c6f6d730 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Sat, 7 Aug 2021 15:13:54 -0400 Subject: [PATCH 23/25] flex-error: resolve conflicts with `master` (#945) * Implement full-duplex secret connection (#938) * Implement thread-safe cloning of a secret connection Signed-off-by: Thane Thomson * Expand documentation for SecretConnection on threading considerations Signed-off-by: Thane Thomson * Extract peer construction into its own method Signed-off-by: Thane Thomson * Add test for cloned SecretConnection This adds a `TcpStream`-based test for parallelizing operations on a `SecretConnection`. I used `TcpStream` instead of the buffered reader in the other tests because it wasn't feasible to implement the `TryClone` trait for that buffered pipe implementation. Signed-off-by: Thane Thomson * Add more messages to test Signed-off-by: Thane Thomson * Expand comment for clarity Signed-off-by: Thane Thomson * Add .changelog entry Signed-off-by: Thane Thomson * Restore half-duplex operations Signed-off-by: Thane Thomson * Extract encrypt/decrypt fns as independent methods Signed-off-by: Thane Thomson * Remove unnecessary trait bounds Signed-off-by: Thane Thomson * Extract send/receive state Signed-off-by: Thane Thomson * Extract read/write functionality as standalone methods Signed-off-by: Thane Thomson * Add logic to facilitate splitting SecretConnection into its sending and receiving halves Signed-off-by: Thane Thomson * Restore split SecretConnection test using new semantics Signed-off-by: Thane Thomson * Update changelog entry Signed-off-by: Thane Thomson * Update docs for `SecretConnection` Signed-off-by: Thane Thomson * Condense error reporting Signed-off-by: Thane Thomson * Extract TryClone trait into its own crate As per the discussion at https://github.com/informalsystems/tendermint-rs/pull/938#discussion_r677457303, this extracts the `TryClone` trait into a new crate called `tendermint-std-ext` in the `std-ext` directory. This new crate is intended to contain any code that we need that extends the Rust standard library. Signed-off-by: Thane Thomson * Reorder imports Signed-off-by: Thane Thomson * Assert validation regardless of debug build This introduces the internal encryption assertions at runtime regardless of build type. This may introduce a small performance hit, but it's probably worth it to ensure correctness. Effectively this is keeping an eye on the code in the `encrypt_and_write` fn to ensure its correctness. Signed-off-by: Thane Thomson * Remove remote_pubkey optionality from sender/receiver halves Signed-off-by: Thane Thomson * Update SecretConnection docs with comment content Signed-off-by: Thane Thomson * Fix doc link to TryClone trait Signed-off-by: Thane Thomson * Fix doc link to TryClone trait Signed-off-by: Thane Thomson * Add docs on SecretConnection failures and connection integrity Signed-off-by: Thane Thomson * Synchronize sending/receiving failures to comply with crypto algorithm constraints Signed-off-by: Thane Thomson * Rename try_split method to split for SecretConnection Signed-off-by: Thane Thomson * Remove redundant field name prefixes Signed-off-by: Thane Thomson * Fix broken link in docs Signed-off-by: Thane Thomson * Fix recent clippy errors on `master` (#941) * Fix needless borrows in codebase Signed-off-by: Thane Thomson * Ignore needless collect warning (we do actually seem to need it) Signed-off-by: Thane Thomson * Remove trailing semicolon in macro to fix docs compiling Signed-off-by: Thane Thomson --- .../improvements/814-clonable-secret-conn.md | 4 + Cargo.toml | 1 + light-client/src/components/verifier.rs | 4 +- light-client/src/macros.rs | 2 +- light-client/src/peer_list.rs | 4 +- light-client/src/predicates.rs | 6 +- light-client/src/store/memory.rs | 1 + light-client/src/supervisor.rs | 2 +- p2p/Cargo.toml | 1 + p2p/src/error.rs | 10 + p2p/src/secret_connection.rs | 505 ++++++++++++------ p2p/src/transport.rs | 4 +- release.sh | 2 +- std-ext/Cargo.toml | 20 + std-ext/README.md | 24 + std-ext/src/lib.rs | 8 + std-ext/src/try_clone.rs | 27 + tendermint/src/abci/tag.rs | 2 +- tendermint/src/chain/id.rs | 2 +- test/Cargo.toml | 2 +- test/src/test/unit/p2p/secret_connection.rs | 106 +++- testgen/src/tester.rs | 6 +- testgen/src/validator_set.rs | 2 +- 23 files changed, 544 insertions(+), 201 deletions(-) create mode 100644 .changelog/unreleased/improvements/814-clonable-secret-conn.md create mode 100644 std-ext/Cargo.toml create mode 100644 std-ext/README.md create mode 100644 std-ext/src/lib.rs create mode 100644 std-ext/src/try_clone.rs diff --git a/.changelog/unreleased/improvements/814-clonable-secret-conn.md b/.changelog/unreleased/improvements/814-clonable-secret-conn.md new file mode 100644 index 000000000..6e7fa027b --- /dev/null +++ b/.changelog/unreleased/improvements/814-clonable-secret-conn.md @@ -0,0 +1,4 @@ +- `[tendermint-p2p]` The `SecretConnection` can now be split into two halves to + facilitate full-duplex communication (must be facilitated by using each half + in a separate thread). + ([#938](https://github.com/informalsystems/tendermint-rs/pull/938)) diff --git a/Cargo.toml b/Cargo.toml index 712da3286..4a3a782c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "pbt-gen", "proto", "rpc", + "std-ext", "tendermint", "test", "testgen" diff --git a/light-client/src/components/verifier.rs b/light-client/src/components/verifier.rs index 2416ca99c..57a6e40cf 100644 --- a/light-client/src/components/verifier.rs +++ b/light-client/src/components/verifier.rs @@ -119,8 +119,8 @@ impl Verifier for ProdVerifier { &*self.voting_power_calculator, &*self.commit_validator, &*self.hasher, - &trusted, - &untrusted, + trusted, + untrusted, options, now, ) diff --git a/light-client/src/macros.rs b/light-client/src/macros.rs index a5d5aa94f..284ebe651 100644 --- a/light-client/src/macros.rs +++ b/light-client/src/macros.rs @@ -4,7 +4,7 @@ #[macro_export] macro_rules! bail { ($kind:expr) => { - return Err($kind.into()); + return Err($kind.into()) }; } diff --git a/light-client/src/peer_list.rs b/light-client/src/peer_list.rs index d4e567414..a0e408e30 100644 --- a/light-client/src/peer_list.rs +++ b/light-client/src/peer_list.rs @@ -111,7 +111,7 @@ impl PeerList { /// - The given peer id must not be the primary peer id. /// - The given peer must be in the witness list #[pre(faulty_witness != self.primary && self.witnesses.contains(&faulty_witness))] - #[post(Self::invariant(&self))] + #[post(Self::invariant(self))] pub fn replace_faulty_witness(&mut self, faulty_witness: PeerId) -> Option { let mut result = None; @@ -133,7 +133,7 @@ impl PeerList { /// /// ## Errors /// - If there are no witness left, returns `ErrorKind::NoWitnessLeft`. - #[post(ret.is_ok() ==> Self::invariant(&self))] + #[post(ret.is_ok() ==> Self::invariant(self))] pub fn replace_faulty_primary( &mut self, primary_error: Option, diff --git a/light-client/src/predicates.rs b/light-client/src/predicates.rs index a73afa6a9..3a0ade065 100644 --- a/light-client/src/predicates.rs +++ b/light-client/src/predicates.rs @@ -233,10 +233,10 @@ pub fn verify( vp.is_header_from_past(&untrusted.signed_header.header, options.clock_drift, now)?; // Ensure the header validator hashes match the given validators - vp.validator_sets_match(&untrusted, &*hasher)?; + vp.validator_sets_match(untrusted, &*hasher)?; // Ensure the header next validator hashes match the given next validators - vp.next_validators_match(&untrusted, &*hasher)?; + vp.next_validators_match(untrusted, &*hasher)?; // Ensure the header matches the commit vp.header_matches_commit(&untrusted.signed_header, hasher)?; @@ -259,7 +259,7 @@ pub fn verify( if untrusted.height() == trusted_next_height { // If the untrusted block is the very next block after the trusted block, // check that their (next) validator sets hashes match. - vp.valid_next_validator_set(&untrusted, trusted)?; + vp.valid_next_validator_set(untrusted, trusted)?; } else { // Otherwise, ensure that the untrusted block has a greater height than // the trusted block. diff --git a/light-client/src/store/memory.rs b/light-client/src/store/memory.rs index 6af7389c5..300adb2ea 100644 --- a/light-client/src/store/memory.rs +++ b/light-client/src/store/memory.rs @@ -81,6 +81,7 @@ impl LightStore for MemoryStore { .map(|(_, e)| e.light_block.clone()) } + #[allow(clippy::needless_collect)] fn all(&self, status: Status) -> Box> { let light_blocks: Vec<_> = self .store diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 4176070c4..8047f8fc5 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -319,7 +319,7 @@ impl Supervisor { .collect(); self.fork_detector - .detect_forks(verified_block, &trusted_block, witnesses) + .detect_forks(verified_block, trusted_block, witnesses) } /// Run the supervisor event loop in the same thread. diff --git a/p2p/Cargo.toml b/p2p/Cargo.toml index 09dea5d5d..d5d9e31fa 100644 --- a/p2p/Cargo.toml +++ b/p2p/Cargo.toml @@ -50,6 +50,7 @@ flex-error = { version = "0.4.1", default-features = false } # path dependencies tendermint = { path = "../tendermint", version = "0.21.0" } tendermint-proto = { path = "../proto", version = "0.21.0" } +tendermint-std-ext = { path = "../std-ext", version = "0.21.0" } # optional dependencies prost-amino = { version = "0.6", optional = true } diff --git a/p2p/src/error.rs b/p2p/src/error.rs index 38ac222cc..3f609f524 100644 --- a/p2p/src/error.rs +++ b/p2p/src/error.rs @@ -63,5 +63,15 @@ define_error! { SmallOutputBuffer | _ | { "output buffer is too small" }, + TransportClone + { detail: String } + | e | { format_args!("failed to clone underlying transport: {}", e.detail) } + + } +} + +impl From for Error { + fn from(e: std::io::Error) -> Self { + Self::io(e) } } diff --git a/p2p/src/secret_connection.rs b/p2p/src/secret_connection.rs index a02e4f24f..18581a1b6 100644 --- a/p2p/src/secret_connection.rs +++ b/p2p/src/secret_connection.rs @@ -1,12 +1,12 @@ //! `SecretConnection`: Transport layer encryption for Tendermint P2P connections. -use std::{ - cmp, - convert::{TryFrom, TryInto}, - io::{self, Read, Write}, - marker::{Send, Sync}, - slice, -}; +use std::cmp; +use std::convert::{TryFrom, TryInto}; +use std::io::{self, Read, Write}; +use std::marker::{Send, Sync}; +use std::slice; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use crate::error::Error; use chacha20poly1305::{ @@ -20,6 +20,7 @@ use subtle::ConstantTimeEq; use x25519_dalek::{EphemeralSecret, PublicKey as EphemeralPublic}; use tendermint_proto as proto; +use tendermint_std_ext::TryClone; pub use self::{ kdf::Kdf, @@ -205,16 +206,59 @@ impl Handshake { } } +// Macro usage allows us to avoid unnecessarily cloning the Arc +// that indicates whether we need to terminate the connection. +// +// Limitation: this only checks once prior to the execution of an I/O operation +// whether we need to terminate. This should be sufficient for our purposes +// though. +macro_rules! checked_io { + ($term:expr, $f:expr) => {{ + if $term.load(Ordering::SeqCst) { + return Err(io::Error::new( + io::ErrorKind::Other, + "secret connection was terminated elsewhere by previous error", + )); + } + let result = { $f }; + if result.is_err() { + $term.store(true, Ordering::SeqCst); + } + result + }}; +} + /// Encrypted connection between peers in a Tendermint network. -pub struct SecretConnection { +/// +/// ## Connection integrity and failures +/// +/// Due to the underlying encryption mechanism (currently [RFC 8439]), when a +/// read or write failure occurs, it is necessary to disconnect from the remote +/// peer and attempt to reconnect. +/// +/// ## Half- and full-duplex connections +/// By default, a `SecretConnection` facilitates half-duplex operations (i.e. +/// one can either read from the connection or write to it at a given time, but +/// not both simultaneously). +/// +/// If, however, the underlying I/O handler class implements +/// [`tendermint_std_ext::TryClone`], then you can use +/// [`SecretConnection::split`] to split the `SecretConnection` into its +/// sending and receiving halves. Each of these halves can then be used in a +/// separate thread to facilitate full-duplex communication. +/// +/// ## Contracts +/// +/// When reading data, data smaller than [`DATA_MAX_SIZE`] is read atomically. +/// +/// [RFC 8439]: https://www.rfc-editor.org/rfc/rfc8439.html +pub struct SecretConnection { io_handler: IoHandler, protocol_version: Version, - recv_nonce: Nonce, - send_nonce: Nonce, - recv_cipher: ChaCha20Poly1305, - send_cipher: ChaCha20Poly1305, remote_pubkey: Option, - recv_buffer: Vec, + send_state: SendState, + recv_state: ReceiveState, + terminate: Arc, } impl SecretConnection { @@ -249,12 +293,17 @@ impl SecretConnection { let mut sc = Self { io_handler, protocol_version, - recv_buffer: vec![], - recv_nonce: Nonce::default(), - send_nonce: Nonce::default(), - recv_cipher: h.state.recv_cipher.clone(), - send_cipher: h.state.send_cipher.clone(), remote_pubkey: None, + send_state: SendState { + cipher: h.state.send_cipher.clone(), + nonce: Nonce::default(), + }, + recv_state: ReceiveState { + cipher: h.state.recv_cipher.clone(), + nonce: Nonce::default(), + buffer: vec![], + }, + terminate: Arc::new(AtomicBool::new(false)), }; // Share each other's pubkey & challenge signature. @@ -272,165 +321,129 @@ impl SecretConnection { sc.remote_pubkey = Some(remote_pubkey); Ok(sc) } - - /// Encrypt AEAD authenticated data - #[allow(clippy::cast_possible_truncation)] - fn encrypt( - &self, - chunk: &[u8], - sealed_frame: &mut [u8; TAG_SIZE + TOTAL_FRAME_SIZE], - ) -> Result<(), Error> { - debug_assert!(!chunk.is_empty(), "chunk is empty"); - debug_assert!( - chunk.len() <= TOTAL_FRAME_SIZE - DATA_LEN_SIZE, - "chunk is too big: {}! max: {}", - chunk.len(), - DATA_MAX_SIZE, - ); - sealed_frame[..DATA_LEN_SIZE].copy_from_slice(&(chunk.len() as u32).to_le_bytes()); - sealed_frame[DATA_LEN_SIZE..DATA_LEN_SIZE + chunk.len()].copy_from_slice(chunk); - - let tag = self - .send_cipher - .encrypt_in_place_detached( - GenericArray::from_slice(self.send_nonce.to_bytes()), - b"", - &mut sealed_frame[..TOTAL_FRAME_SIZE], - ) - .map_err(Error::aead)?; - - sealed_frame[TOTAL_FRAME_SIZE..].copy_from_slice(tag.as_slice()); - - Ok(()) - } - - /// Decrypt AEAD authenticated data - fn decrypt(&self, ciphertext: &[u8], out: &mut [u8]) -> Result { - if ciphertext.len() < TAG_SIZE { - return Err(Error::short_ciphertext(TAG_SIZE)); - } - - // Split ChaCha20 ciphertext from the Poly1305 tag - let (ct, tag) = ciphertext.split_at(ciphertext.len() - TAG_SIZE); - - if out.len() < ct.len() { - return Err(Error::small_output_buffer()); - } - - let in_out = &mut out[..ct.len()]; - in_out.copy_from_slice(ct); - - self.recv_cipher - .decrypt_in_place_detached( - GenericArray::from_slice(self.recv_nonce.to_bytes()), - b"", - in_out, - tag.into(), - ) - .map_err(Error::aead)?; - - Ok(in_out.len()) - } } -impl Read for SecretConnection +impl SecretConnection where - IoHandler: Read + Write + Send + Sync, + IoHandler: TryClone, + ::Error: std::error::Error + Send + Sync + 'static, { - // CONTRACT: data smaller than DATA_MAX_SIZE is read atomically. - fn read(&mut self, data: &mut [u8]) -> io::Result { - if !self.recv_buffer.is_empty() { - let n = cmp::min(data.len(), self.recv_buffer.len()); - data.copy_from_slice(&self.recv_buffer[..n]); - let mut leftover_portion = vec![ - 0; - self.recv_buffer - .len() - .checked_sub(n) - .expect("leftover calculation failed") - ]; - leftover_portion.clone_from_slice(&self.recv_buffer[n..]); - self.recv_buffer = leftover_portion; - - return Ok(n); - } + /// For secret connections whose underlying I/O layer implements + /// [`tendermint_std_ext::TryClone`], this attempts to split such a + /// connection into its sending and receiving halves. + /// + /// This facilitates full-duplex communications when each half is used in + /// a separate thread. + /// + /// ## Errors + /// Fails when the `try_clone` operation for the underlying I/O handler + /// fails. + pub fn split(self) -> Result<(Sender, Receiver), Error> { + let remote_pubkey = self.remote_pubkey.expect("remote_pubkey to be initialized"); + Ok(( + Sender { + io_handler: self + .io_handler + .try_clone() + .map_err(|e| Error::transport_clone(e.to_string()))?, + remote_pubkey, + state: self.send_state, + terminate: self.terminate.clone(), + }, + Receiver { + io_handler: self.io_handler, + remote_pubkey, + state: self.recv_state, + terminate: self.terminate, + }, + )) + } +} - let mut sealed_frame = [0_u8; TAG_SIZE + TOTAL_FRAME_SIZE]; - self.io_handler.read_exact(&mut sealed_frame)?; +impl Read for SecretConnection { + fn read(&mut self, data: &mut [u8]) -> io::Result { + checked_io!( + self.terminate, + read_and_decrypt(&mut self.io_handler, &mut self.recv_state, data) + ) + } +} - // decrypt the frame - let mut frame = [0_u8; TOTAL_FRAME_SIZE]; - let res = self.decrypt(&sealed_frame, &mut frame); +impl Write for SecretConnection { + fn write(&mut self, data: &[u8]) -> io::Result { + checked_io!( + self.terminate, + encrypt_and_write(&mut self.io_handler, &mut self.send_state, data) + ) + } - if let Err(err) = res { - return Err(io::Error::new(io::ErrorKind::Other, err.to_string())); - } + fn flush(&mut self) -> io::Result<()> { + checked_io!(self.terminate, self.io_handler.flush()) + } +} - self.recv_nonce.increment(); - // end decryption +// Sending state for a `SecretConnection`. +struct SendState { + cipher: ChaCha20Poly1305, + nonce: Nonce, +} - let chunk_length = u32::from_le_bytes(frame[..4].try_into().expect("chunk framing failed")); +// Receiving state for a `SecretConnection`. +struct ReceiveState { + cipher: ChaCha20Poly1305, + nonce: Nonce, + buffer: Vec, +} - if chunk_length as usize > DATA_MAX_SIZE { - return Err(io::Error::new( - io::ErrorKind::Other, - format!("chunk is too big: {}! max: {}", chunk_length, DATA_MAX_SIZE), - )); - } +/// The sending end of a [`SecretConnection`]. +pub struct Sender { + io_handler: IoHandler, + remote_pubkey: PublicKey, + state: SendState, + terminate: Arc, +} - let mut chunk = vec![0; chunk_length as usize]; - chunk.clone_from_slice( - &frame[DATA_LEN_SIZE - ..(DATA_LEN_SIZE - .checked_add(chunk_length as usize) - .expect("chunk size addition overflow"))], - ); +impl Sender { + /// Returns the remote pubkey. Panics if there's no key. + pub const fn remote_pubkey(&self) -> PublicKey { + self.remote_pubkey + } +} - let n = cmp::min(data.len(), chunk.len()); - data[..n].copy_from_slice(&chunk[..n]); - self.recv_buffer.copy_from_slice(&chunk[n..]); +impl Write for Sender { + fn write(&mut self, buf: &[u8]) -> io::Result { + checked_io!( + self.terminate, + encrypt_and_write(&mut self.io_handler, &mut self.state, buf) + ) + } - Ok(n) + fn flush(&mut self) -> io::Result<()> { + checked_io!(self.terminate, self.io_handler.flush()) } } -impl Write for SecretConnection -where - IoHandler: Read + Write + Send + Sync, -{ - // Writes encrypted frames of `TAG_SIZE` + `TOTAL_FRAME_SIZE` - // CONTRACT: data smaller than DATA_MAX_SIZE is read atomically. - fn write(&mut self, data: &[u8]) -> io::Result { - let mut n = 0_usize; - let mut data_copy = data; - while !data_copy.is_empty() { - let chunk: &[u8]; - if DATA_MAX_SIZE < data.len() { - chunk = &data[..DATA_MAX_SIZE]; - data_copy = &data_copy[DATA_MAX_SIZE..]; - } else { - chunk = data_copy; - data_copy = &[0_u8; 0]; - } - let sealed_frame = &mut [0_u8; TAG_SIZE + TOTAL_FRAME_SIZE]; - let res = self.encrypt(chunk, sealed_frame); - if let Err(err) = res { - return Err(io::Error::new(io::ErrorKind::Other, err.to_string())); - } - self.send_nonce.increment(); - // end encryption - - self.io_handler.write_all(&sealed_frame[..])?; - n = n - .checked_add(chunk.len()) - .expect("overflow when adding chunk lenghts"); - } +/// The receiving end of a [`SecretConnection`]. +pub struct Receiver { + io_handler: IoHandler, + remote_pubkey: PublicKey, + state: ReceiveState, + terminate: Arc, +} - Ok(n) +impl Receiver { + /// Returns the remote pubkey. Panics if there's no key. + pub const fn remote_pubkey(&self) -> PublicKey { + self.remote_pubkey } +} - fn flush(&mut self) -> io::Result<()> { - self.io_handler.flush() +impl Read for Receiver { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + checked_io!( + self.terminate, + read_and_decrypt(&mut self.io_handler, &mut self.state, buf) + ) } } @@ -444,17 +457,13 @@ fn share_eph_pubkey( // TODO(ismail): on the go side this is done in parallel, here we do send and receive after // each other. thread::spawn would require a static lifetime. // Should still work though. - handler - .write_all(&protocol_version.encode_initial_handshake(local_eph_pubkey)) - .map_err(Error::io)?; + handler.write_all(&protocol_version.encode_initial_handshake(local_eph_pubkey))?; let mut response_len = 0_u8; - handler - .read_exact(slice::from_mut(&mut response_len)) - .map_err(Error::io)?; + handler.read_exact(slice::from_mut(&mut response_len))?; let mut buf = vec![0; response_len as usize]; - handler.read_exact(&mut buf).map_err(Error::io)?; + handler.read_exact(&mut buf)?; protocol_version.decode_initial_handshake(&buf) } @@ -477,11 +486,10 @@ fn share_auth_signature( .protocol_version .encode_auth_signature(pubkey, local_signature); - sc.write_all(&buf).map_err(Error::io)?; + sc.write_all(&buf)?; let mut buf = vec![0; sc.protocol_version.auth_sig_msg_response_len()]; - sc.read_exact(&mut buf).map_err(Error::io)?; - + sc.read_exact(&mut buf)?; sc.protocol_version.decode_auth_signature(&buf) } @@ -494,3 +502,164 @@ pub fn sort32(first: [u8; 32], second: [u8; 32]) -> ([u8; 32], [u8; 32]) { (second, first) } } + +/// Encrypt AEAD authenticated data +#[allow(clippy::cast_possible_truncation)] +fn encrypt( + chunk: &[u8], + send_cipher: &ChaCha20Poly1305, + send_nonce: &Nonce, + sealed_frame: &mut [u8; TAG_SIZE + TOTAL_FRAME_SIZE], +) -> Result<(), Error> { + assert!(!chunk.is_empty(), "chunk is empty"); + assert!( + chunk.len() <= TOTAL_FRAME_SIZE - DATA_LEN_SIZE, + "chunk is too big: {}! max: {}", + chunk.len(), + DATA_MAX_SIZE, + ); + sealed_frame[..DATA_LEN_SIZE].copy_from_slice(&(chunk.len() as u32).to_le_bytes()); + sealed_frame[DATA_LEN_SIZE..DATA_LEN_SIZE + chunk.len()].copy_from_slice(chunk); + + let tag = send_cipher + .encrypt_in_place_detached( + GenericArray::from_slice(send_nonce.to_bytes()), + b"", + &mut sealed_frame[..TOTAL_FRAME_SIZE], + ) + .map_err(Error::aead)?; + + sealed_frame[TOTAL_FRAME_SIZE..].copy_from_slice(tag.as_slice()); + + Ok(()) +} + +// Writes encrypted frames of `TAG_SIZE` + `TOTAL_FRAME_SIZE` +fn encrypt_and_write( + io_handler: &mut IoHandler, + send_state: &mut SendState, + data: &[u8], +) -> io::Result { + let mut n = 0_usize; + let mut data_copy = data; + while !data_copy.is_empty() { + let chunk: &[u8]; + if DATA_MAX_SIZE < data.len() { + chunk = &data[..DATA_MAX_SIZE]; + data_copy = &data_copy[DATA_MAX_SIZE..]; + } else { + chunk = data_copy; + data_copy = &[0_u8; 0]; + } + let sealed_frame = &mut [0_u8; TAG_SIZE + TOTAL_FRAME_SIZE]; + encrypt(chunk, &send_state.cipher, &send_state.nonce, sealed_frame) + .map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?; + send_state.nonce.increment(); + // end encryption + + io_handler.write_all(&sealed_frame[..])?; + n = n + .checked_add(chunk.len()) + .expect("overflow when adding chunk lengths"); + } + + Ok(n) +} + +/// Decrypt AEAD authenticated data +fn decrypt( + ciphertext: &[u8], + recv_cipher: &ChaCha20Poly1305, + recv_nonce: &Nonce, + out: &mut [u8], +) -> Result { + if ciphertext.len() < TAG_SIZE { + return Err(Error::short_ciphertext(TAG_SIZE)); + } + + // Split ChaCha20 ciphertext from the Poly1305 tag + let (ct, tag) = ciphertext.split_at(ciphertext.len() - TAG_SIZE); + + if out.len() < ct.len() { + return Err(Error::small_output_buffer()); + } + + let in_out = &mut out[..ct.len()]; + in_out.copy_from_slice(ct); + + recv_cipher + .decrypt_in_place_detached( + GenericArray::from_slice(recv_nonce.to_bytes()), + b"", + in_out, + tag.into(), + ) + .map_err(Error::aead)?; + + Ok(in_out.len()) +} + +fn read_and_decrypt( + io_handler: &mut IoHandler, + recv_state: &mut ReceiveState, + data: &mut [u8], +) -> io::Result { + if !recv_state.buffer.is_empty() { + let n = cmp::min(data.len(), recv_state.buffer.len()); + data.copy_from_slice(&recv_state.buffer[..n]); + let mut leftover_portion = vec![ + 0; + recv_state + .buffer + .len() + .checked_sub(n) + .expect("leftover calculation failed") + ]; + leftover_portion.clone_from_slice(&recv_state.buffer[n..]); + recv_state.buffer = leftover_portion; + + return Ok(n); + } + + let mut sealed_frame = [0_u8; TAG_SIZE + TOTAL_FRAME_SIZE]; + io_handler.read_exact(&mut sealed_frame)?; + + // decrypt the frame + let mut frame = [0_u8; TOTAL_FRAME_SIZE]; + let res = decrypt( + &sealed_frame, + &recv_state.cipher, + &recv_state.nonce, + &mut frame, + ); + + if let Err(err) = res { + return Err(io::Error::new(io::ErrorKind::Other, err.to_string())); + } + + recv_state.nonce.increment(); + // end decryption + + let chunk_length = u32::from_le_bytes(frame[..4].try_into().expect("chunk framing failed")); + + if chunk_length as usize > DATA_MAX_SIZE { + return Err(io::Error::new( + io::ErrorKind::Other, + format!("chunk is too big: {}! max: {}", chunk_length, DATA_MAX_SIZE), + )); + } + + let mut chunk = vec![0; chunk_length as usize]; + chunk.clone_from_slice( + &frame[DATA_LEN_SIZE + ..(DATA_LEN_SIZE + .checked_add(chunk_length as usize) + .expect("chunk size addition overflow"))], + ); + + let n = cmp::min(data.len(), chunk.len()); + data[..n].copy_from_slice(&chunk[..n]); + recv_state.buffer.copy_from_slice(&chunk[n..]); + + Ok(n) +} diff --git a/p2p/src/transport.rs b/p2p/src/transport.rs index 9f731ae9b..fc35f3a37 100644 --- a/p2p/src/transport.rs +++ b/p2p/src/transport.rs @@ -16,7 +16,7 @@ where /// List of addresses to be communicated as publicly reachable to other nodes, which in turn /// can use that to share with third parties. /// - /// TODO(xla): Dependning on where this information is going to be disseminated it might be + /// TODO(xla): Depending on where this information is going to be disseminated it might be /// better placed in a higher-level protocol. What stands in opposition to that is the fact /// that advertised addresses will be helpful for hole punching and other involved network /// traversals. @@ -140,6 +140,6 @@ where /// /// # Errors /// - /// * If resource allocation fails for lack of priviliges or being not available. + /// * If resource allocation fails for lack of privileges or being not available. fn bind(self, bind_info: BindInfo) -> Result<(Self::Endpoint, Self::Incoming)>; } diff --git a/release.sh b/release.sh index 3fd1bf424..828815aab 100755 --- a/release.sh +++ b/release.sh @@ -36,7 +36,7 @@ set -e # A space-separated list of all the crates we want to publish, in the order in # which they must be published. It's important to respect this order, since # each subsequent crate depends on one or more of the preceding ones. -DEFAULT_CRATES="tendermint-proto tendermint tendermint-abci tendermint-rpc tendermint-p2p tendermint-light-client tendermint-light-client-js tendermint-testgen" +DEFAULT_CRATES="tendermint-proto tendermint-std-ext tendermint tendermint-abci tendermint-rpc tendermint-p2p tendermint-light-client tendermint-light-client-js tendermint-testgen" # Allows us to override the crates we want to publish. CRATES=${*:-${DEFAULT_CRATES}} diff --git a/std-ext/Cargo.toml b/std-ext/Cargo.toml new file mode 100644 index 000000000..979b750ac --- /dev/null +++ b/std-ext/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "tendermint-std-ext" +version = "0.21.0" +edition = "2018" +license = "Apache-2.0" +homepage = "https://www.tendermint.com/" +repository = "https://github.com/informalsystems/tendermint-rs" +readme = "README.md" +keywords = ["blockchain", "cosmos", "tendermint"] +categories = ["development-tools"] +authors = ["Informal Systems "] + +description = """ + tendermint-std-ext contains extensions to the Rust standard library for use + from tendermint-rs. + """ + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/std-ext/README.md b/std-ext/README.md new file mode 100644 index 000000000..145ecd2b8 --- /dev/null +++ b/std-ext/README.md @@ -0,0 +1,24 @@ +[![Crate][crate-image]][crate-link] +[![Docs][docs-image]][docs-link] + +See the [repo root] for build status, license, rust version, etc. + +# tendermint-std-ext + +Extensions to the [Rust standard library][std] for use by Tendermint in Rust. + +## Documentation + +See documentation on [crates.io][docs-link]. + +[//]: # (badges) + +[crate-image]: https://img.shields.io/crates/v/tendermint-std-ext.svg +[crate-link]: https://crates.io/crates/tendermint-std-ext +[docs-image]: https://docs.rs/tendermint-std-ext/badge.svg +[docs-link]: https://docs.rs/tendermint-std-ext/ + +[//]: # (general links) + +[repo root]: https://github.com/informalsystems/tendermint-rs +[std]: https://doc.rust-lang.org/std/ diff --git a/std-ext/src/lib.rs b/std-ext/src/lib.rs new file mode 100644 index 000000000..6fb7389c2 --- /dev/null +++ b/std-ext/src/lib.rs @@ -0,0 +1,8 @@ +//! Extensions to the [Rust standard library][std] for use by [tendermint-rs]. +//! +//! [std]: https://doc.rust-lang.org/std/ +//! [tendermint-rs]: https://github.com/informalsystems/tendermint-rs/ + +mod try_clone; + +pub use try_clone::TryClone; diff --git a/std-ext/src/try_clone.rs b/std-ext/src/try_clone.rs new file mode 100644 index 000000000..2ea599a76 --- /dev/null +++ b/std-ext/src/try_clone.rs @@ -0,0 +1,27 @@ +//! Rust standard library types that can be fallibly cloned. + +use std::net::TcpStream; + +/// Types that can be cloned where success is not guaranteed can implement this +/// trait. +pub trait TryClone: Sized { + /// The type of error that can be returned when an attempted clone + /// operation fails. + type Error: std::error::Error; + + /// Attempt to clone this instance. + /// + /// # Errors + /// Can fail if the underlying instance cannot be cloned (e.g. the OS could + /// be out of file descriptors, or some low-level OS-specific error could + /// be produced). + fn try_clone(&self) -> Result; +} + +impl TryClone for TcpStream { + type Error = std::io::Error; + + fn try_clone(&self) -> Result { + TcpStream::try_clone(self) + } +} diff --git a/tendermint/src/abci/tag.rs b/tendermint/src/abci/tag.rs index 657a19eec..e610c6d34 100644 --- a/tendermint/src/abci/tag.rs +++ b/tendermint/src/abci/tag.rs @@ -82,7 +82,7 @@ mod test { #[test] fn tag_serde() { let json = r#"{"key": "cGFja2V0X3RpbWVvdXRfaGVpZ2h0", "value": "MC00ODQw"}"#; - let tag: Tag = serde_json::from_str(&json).unwrap(); + let tag: Tag = serde_json::from_str(json).unwrap(); assert_eq!("packet_timeout_height", tag.key.0); assert_eq!("0-4840", tag.value.0); } diff --git a/tendermint/src/chain/id.rs b/tendermint/src/chain/id.rs index b6a75e465..312a7d89f 100644 --- a/tendermint/src/chain/id.rs +++ b/tendermint/src/chain/id.rs @@ -55,7 +55,7 @@ impl Id { /// Get the chain ID as a raw bytes. pub fn as_bytes(&self) -> &[u8] { - &self.0.as_str().as_bytes() + self.0.as_str().as_bytes() } } diff --git a/test/Cargo.toml b/test/Cargo.toml index d9d2dad9a..f9bbaa5a8 100644 --- a/test/Cargo.toml +++ b/test/Cargo.toml @@ -15,7 +15,7 @@ test = true [dev-dependencies] ed25519-dalek = "1" -eyre = "0.6" +flex-error = "0.4.2" flume = "0.10" rand_core = { version = "0.5", features = ["std"] } readwrite = "^0.1.1" diff --git a/test/src/test/unit/p2p/secret_connection.rs b/test/src/test/unit/p2p/secret_connection.rs index 505be5f84..797a29d60 100644 --- a/test/src/test/unit/p2p/secret_connection.rs +++ b/test/src/test/unit/p2p/secret_connection.rs @@ -1,5 +1,6 @@ use std::io::Read as _; use std::io::Write as _; +use std::net::{TcpListener, TcpStream}; use std::thread; use ed25519_dalek::{self as ed25519}; @@ -19,16 +20,12 @@ fn test_handshake() { let (pipe1, pipe2) = pipe::async_bipipe_buffered(); let peer1 = thread::spawn(|| { - let mut csprng = OsRng {}; - let privkey1: ed25519::Keypair = ed25519::Keypair::generate(&mut csprng); - let conn1 = SecretConnection::new(pipe2, privkey1, Version::V0_34); + let conn1 = new_peer_conn(pipe2); assert!(conn1.is_ok()); }); let peer2 = thread::spawn(|| { - let mut csprng = OsRng {}; - let privkey2: ed25519::Keypair = ed25519::Keypair::generate(&mut csprng); - let conn2 = SecretConnection::new(pipe1, privkey2, Version::V0_34); + let conn2 = new_peer_conn(pipe1); assert!(conn2.is_ok()); }); @@ -43,10 +40,7 @@ fn test_read_write_single_message() { let (pipe1, pipe2) = pipe::async_bipipe_buffered(); let sender = thread::spawn(move || { - let mut csprng = OsRng {}; - let privkey1: ed25519::Keypair = ed25519::Keypair::generate(&mut csprng); - let mut conn1 = - SecretConnection::new(pipe2, privkey1, Version::V0_34).expect("handshake to succeed"); + let mut conn1 = new_peer_conn(pipe2).expect("handshake to succeed"); conn1 .write_all(MESSAGE.as_bytes()) @@ -54,10 +48,7 @@ fn test_read_write_single_message() { }); let receiver = thread::spawn(move || { - let mut csprng = OsRng {}; - let privkey2: ed25519::Keypair = ed25519::Keypair::generate(&mut csprng); - let mut conn2 = - SecretConnection::new(pipe1, privkey2, Version::V0_34).expect("handshake to succeed"); + let mut conn2 = new_peer_conn(pipe1).expect("handshake to succeed"); let mut buf = [0; MESSAGE.len()]; conn2 @@ -111,3 +102,90 @@ fn test_sort() { assert_eq!(t1, *t3); assert_eq!(t2, *t4); } + +#[test] +fn test_split_secret_connection() { + const MESSAGES_1_TO_2: &[&str] = &["one", "three", "five", "seven"]; + const MESSAGES_2_TO_1: &[&str] = &["two", "four", "six", "eight"]; + let peer1_listener = TcpListener::bind("127.0.0.1:0").expect("to be able to bind to 127.0.0.1"); + let peer1_addr = peer1_listener.local_addr().unwrap(); + println!("peer1 bound to {:?}", peer1_addr); + + let peer1 = thread::spawn(move || { + let stream = peer1_listener + .incoming() + .next() + .unwrap() + .expect("an incoming TCP stream from peer 2"); + let mut conn_to_peer2 = new_peer_conn(stream).expect("handshake to succeed"); + println!("peer1 handshake concluded"); + for msg_counter in 0..MESSAGES_1_TO_2.len() { + // Peer 1 sends first + conn_to_peer2 + .write_all(MESSAGES_1_TO_2[msg_counter].as_bytes()) + .expect("to write message successfully to peer 2"); + // Peer 1 expects a response + let mut buf = [0u8; 10]; + let br = conn_to_peer2 + .read(&mut buf) + .expect("to read a message from peer 2"); + let msg = String::from_utf8_lossy(&buf[0..br]).to_string(); + println!("Got message from peer2: {}", msg); + assert_eq!(msg, MESSAGES_2_TO_1[msg_counter]); + } + }); + + // Peer 2 attempts to initiate the secret connection to peer 1 + let peer2_to_peer1 = TcpStream::connect(peer1_addr).expect("to be able to connect to peer 1"); + println!("peer2 connected to peer1"); + let conn_to_peer1 = new_peer_conn(peer2_to_peer1).expect("handshake to succeed"); + println!("peer2 handshake concluded"); + + let (mut write_conn, mut read_conn) = conn_to_peer1 + .split() + .expect("to be able to clone the underlying TcpStream"); + let (write_tx, write_rx) = std::sync::mpsc::channel::(); + + // We spawn a standalone thread that makes use of peer2's secret connection + // purely to write outgoing messages. + let peer2_writer = thread::spawn(move || { + for _ in 0..MESSAGES_2_TO_1.len() { + let msg = write_rx + .recv() + .expect("to successfully receive a message to be sent to peer1"); + write_conn + .write_all(msg.as_bytes()) + .expect("to be able to write to peer 1"); + } + }); + + for msg_counter in 0..MESSAGES_2_TO_1.len() { + // Wait for peer 1 to send first + let mut buf = [0u8; 10]; + let br = read_conn + .read(&mut buf) + .expect("to receive a message from peer 1"); + let msg = String::from_utf8_lossy(&buf[0..br]).to_string(); + println!("Got message from peer1: {}", msg); + assert_eq!(msg, MESSAGES_1_TO_2[msg_counter]); + write_tx + .send(MESSAGES_2_TO_1[msg_counter].to_string()) + .expect("to be able to communicate with peer2's writer thread"); + } + + peer2_writer + .join() + .expect("peer 2's writer thread to run to completion"); + peer1.join().expect("peer 1's thread to run to completion") +} + +fn new_peer_conn( + io_handler: IoHandler, +) -> Result, tendermint_p2p::error::Error> +where + IoHandler: std::io::Read + std::io::Write + Send + Sync, +{ + let mut csprng = OsRng {}; + let privkey1: ed25519::Keypair = ed25519::Keypair::generate(&mut csprng); + SecretConnection::new(io_handler, privkey1, Version::V0_34) +} diff --git a/testgen/src/tester.rs b/testgen/src/tester.rs index cc03d340c..0a9dc2114 100644 --- a/testgen/src/tester.rs +++ b/testgen/src/tester.rs @@ -269,7 +269,7 @@ impl Tester { T: 'static + DeserializeOwned + UnwindSafe, F: Fn(T) + UnwindSafe + RefUnwindSafe + 'static, { - let test_fn = move |_path: &str, input: &str| match parse_as::(&input) { + let test_fn = move |_path: &str, input: &str| match parse_as::(input) { Ok(test_case) => Tester::capture_test(|| { test(test_case); }), @@ -288,7 +288,7 @@ impl Tester { { let test_env = self.env().unwrap(); let output_env = self.output_env().unwrap(); - let test_fn = move |path: &str, input: &str| match parse_as::(&input) { + let test_fn = move |path: &str, input: &str| match parse_as::(input) { Ok(test_case) => Tester::capture_test(|| { // It is OK to unwrap() here: in case of unwrapping failure, the test will fail. let dir = TempDir::new().unwrap(); @@ -311,7 +311,7 @@ impl Tester { T: 'static + DeserializeOwned, F: Fn(T) -> Vec<(String, String)> + 'static, { - let batch_fn = move |_path: &str, input: &str| match parse_as::(&input) { + let batch_fn = move |_path: &str, input: &str| match parse_as::(input) { Ok(test_batch) => Some(batch(test_batch)), Err(_) => None, }; diff --git a/testgen/src/validator_set.rs b/testgen/src/validator_set.rs index 44da67af4..0acfed561 100644 --- a/testgen/src/validator_set.rs +++ b/testgen/src/validator_set.rs @@ -48,7 +48,7 @@ impl Generator for ValidatorSet { } fn generate(&self) -> Result { - let vals = generate_validators(&self.validators.as_ref().unwrap())?; + let vals = generate_validators(self.validators.as_ref().unwrap())?; Ok(validator::Set::without_proposer(vals)) } } From ab1aae41bc05835481f8b0959bc1a1308aa92bb8 Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Sat, 7 Aug 2021 15:44:46 -0400 Subject: [PATCH 24/25] Remove unnecessary macros Signed-off-by: Thane Thomson --- light-client/src/lib.rs | 3 --- light-client/src/macros.rs | 19 ------------------- 2 files changed, 22 deletions(-) delete mode 100644 light-client/src/macros.rs diff --git a/light-client/src/lib.rs b/light-client/src/lib.rs index fd6c747cb..630d6d79f 100644 --- a/light-client/src/lib.rs +++ b/light-client/src/lib.rs @@ -33,8 +33,5 @@ pub mod types; pub(crate) mod utils; -#[doc(hidden)] -mod macros; - #[doc(hidden)] pub mod tests; diff --git a/light-client/src/macros.rs b/light-client/src/macros.rs deleted file mode 100644 index 284ebe651..000000000 --- a/light-client/src/macros.rs +++ /dev/null @@ -1,19 +0,0 @@ -//! Small macros used internally. - -/// Bail out of the current function with the given error kind. -#[macro_export] -macro_rules! bail { - ($kind:expr) => { - return Err($kind.into()) - }; -} - -/// Ensure a condition holds, returning an error if it doesn't (ala `assert`). -#[macro_export] -macro_rules! ensure { - ($cond:expr, $kind:expr) => { - if !($cond) { - return Err($kind.into()); - } - }; -} From afd718528ba95ced0e9438bbaaed1e52b9886c2e Mon Sep 17 00:00:00 2001 From: Thane Thomson Date: Sat, 7 Aug 2021 16:52:03 -0400 Subject: [PATCH 25/25] Correct error messages Signed-off-by: Thane Thomson --- light-client/src/predicates.rs | 4 ++-- light-client/src/supervisor.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/light-client/src/predicates.rs b/light-client/src/predicates.rs index 3a0ade065..a74340142 100644 --- a/light-client/src/predicates.rs +++ b/light-client/src/predicates.rs @@ -559,7 +559,7 @@ mod tests { assert_eq!(e.pre_commit_length, signed_header.commit.signatures.len()); assert_eq!(e.validator_length, val_set.validators().len()); } - _ => panic!("expected ImplementationSpecific error"), + _ => panic!("expected MismatchPreCommitLength error"), } // 4. commit.BlockIdFlagAbsent - should be "Ok" @@ -606,7 +606,7 @@ mod tests { hasher.hash_validator_set(&val_set_with_faulty_signer) ); } - _ => panic!("expected ImplementationSpecific error"), + _ => panic!("expected FaultySigner error"), } } diff --git a/light-client/src/supervisor.rs b/light-client/src/supervisor.rs index 8047f8fc5..df396d2a1 100644 --- a/light-client/src/supervisor.rs +++ b/light-client/src/supervisor.rs @@ -654,7 +654,7 @@ mod tests { }, _ => panic!("expected Rpc error"), }, - _ => panic!("expected NoWitnesses error"), + _ => panic!("expected Io error"), } }