Skip to content

Commit

Permalink
Move protobuf traits from cosmrs => cosmos-sdk-proto (#255)
Browse files Browse the repository at this point in the history
When trying to define an `osmosis-proto` crate (#239), we ran into the
problem that it needed to import `cosmrs` to be able to impl the
`MsgProto` trait.

We couldn't follow the same pattern as `cosmrs` defining the type URLs
for `cosmos-sdk-proto`, which it could only do because it defined the
`MsgProto` trait as well.

Knowledge of the type URLs is necessary to convert to/from `Any`, which
Cosmos SDK uses all over the place.

So far there isn't a good upstream solution to this problem in `prost`
or AFAICT in `tendermint-proto` either. There's an upstream tracking
issue for `prost` here:

tokio-rs/prost#299

The ideal solution to this problem seems to be adding a `TYPE_URL` to
`prost::Message`, and automatically populating them with `prost-build`.

Failing that, this commit introduces a `TypeUrl` trait with an
associated `TYPE_URL` const (previously provided by the `MsgProto`
trait).

The `from_any` and `to_any` methods have been moved to `MessageExt`.
  • Loading branch information
tony-iqlusion authored Jul 25, 2022
1 parent 9eb479e commit 5d3bed0
Show file tree
Hide file tree
Showing 15 changed files with 269 additions and 214 deletions.
5 changes: 5 additions & 0 deletions cosmos-sdk-proto/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@
#![forbid(unsafe_code)]
#![warn(trivial_casts, trivial_numeric_casts, unused_import_braces)]

pub mod traits;
pub mod type_urls;

pub use prost;
pub use prost_types::Any;
pub use tendermint_proto as tendermint;

/// The version (commit hash) of the Cosmos SDK used when generating this library.
Expand Down
76 changes: 76 additions & 0 deletions cosmos-sdk-proto/src/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
//! Support traits for Cosmos SDK protobufs.
pub use prost::Message;

use crate::Any;
use prost::{DecodeError, EncodeError};
use std::str::FromStr;

/// Associate a type URL with a given proto.
pub trait TypeUrl: Message {
/// Type URL value
const TYPE_URL: &'static str;
}

/// Extension trait for [`Message`].
pub trait MessageExt: Message {
/// Parse this message proto from [`Any`].
fn from_any(any: &Any) -> Result<Self, DecodeError>
where
Self: Default + Sized + TypeUrl,
{
if any.type_url == Self::TYPE_URL {
Ok(Self::decode(&*any.value)?)
} else {
let mut err = DecodeError::new(format!(
"expected type URL: \"{}\" (got: \"{}\")",
Self::TYPE_URL,
&any.type_url
));
err.push("unexpected type URL", "type_url");
Err(err)
}
}

/// Serialize this message proto as [`Any`].
fn to_any(&self) -> Result<Any, EncodeError>
where
Self: TypeUrl,
{
self.to_bytes().map(|bytes| Any {
type_url: Self::TYPE_URL.to_owned(),
value: bytes,
})
}

/// Serialize this protobuf message as a byte vector.
fn to_bytes(&self) -> Result<Vec<u8>, EncodeError>;
}

impl<M> MessageExt for M
where
M: prost::Message,
{
fn to_bytes(&self) -> Result<Vec<u8>, EncodeError> {
let mut bytes = Vec::new();
Message::encode(self, &mut bytes)?;
Ok(bytes)
}
}

/// Extension traits for optionally parsing non-empty strings.
///
/// This is a common pattern in Cosmos SDK protobufs.
pub trait ParseOptional: AsRef<str> {
/// Parse optional field.
fn parse_optional<T: FromStr>(&self) -> Result<Option<T>, T::Err> {
if self.as_ref().is_empty() {
Ok(None)
} else {
Ok(Some(self.as_ref().parse()?))
}
}
}

impl ParseOptional for str {}
impl ParseOptional for String {}
134 changes: 134 additions & 0 deletions cosmos-sdk-proto/src/type_urls.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
//! Registry of type URLs associated with various protobuf types defined in
//! this crate.
// TODO(tarcieri): leverage first-class support for type URLs in prost?
// See: https://github.com/tokio-rs/prost/issues/299

use crate::{cosmos, traits::TypeUrl};

#[cfg(feature = "cosmwasm")]
use crate::cosmwasm;

impl TypeUrl for cosmos::bank::v1beta1::MsgSend {
const TYPE_URL: &'static str = "/cosmos.bank.v1beta1.MsgSend";
}

impl TypeUrl for cosmos::bank::v1beta1::MsgMultiSend {
const TYPE_URL: &'static str = "/cosmos.bank.v1beta1.MsgMultiSend";
}

impl TypeUrl for cosmos::distribution::v1beta1::MsgSetWithdrawAddress {
const TYPE_URL: &'static str = "/cosmos.distribution.v1beta1.MsgSetWithdrawAddress";
}

impl TypeUrl for cosmos::distribution::v1beta1::MsgWithdrawDelegatorReward {
const TYPE_URL: &'static str = "/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward";
}

impl TypeUrl for cosmos::distribution::v1beta1::MsgWithdrawValidatorCommission {
const TYPE_URL: &'static str = "/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission";
}

impl TypeUrl for cosmos::distribution::v1beta1::MsgFundCommunityPool {
const TYPE_URL: &'static str = "/cosmos.distribution.v1beta1.MsgFundCommunityPool";
}

impl TypeUrl for cosmos::feegrant::v1beta1::MsgGrantAllowance {
const TYPE_URL: &'static str = "/cosmos.feegrant.v1beta1.MsgGrantAllowance";
}

impl TypeUrl for cosmos::feegrant::v1beta1::MsgRevokeAllowance {
const TYPE_URL: &'static str = "/cosmos.feegrant.v1beta1.MsgRevokeAllowance";
}

impl TypeUrl for cosmos::feegrant::v1beta1::BasicAllowance {
const TYPE_URL: &'static str = "/cosmos.feegrant.v1beta1.BasicAllowance";
}

impl TypeUrl for cosmos::feegrant::v1beta1::PeriodicAllowance {
const TYPE_URL: &'static str = "/cosmos.feegrant.v1beta1.PeriodicAllowance";
}

impl TypeUrl for cosmos::feegrant::v1beta1::AllowedMsgAllowance {
const TYPE_URL: &'static str = "/cosmos.feegrant.v1beta1.AllowedMsgAllowance";
}

impl TypeUrl for cosmos::staking::v1beta1::MsgDelegate {
const TYPE_URL: &'static str = "/cosmos.staking.v1beta1.MsgDelegate";
}

impl TypeUrl for cosmos::staking::v1beta1::MsgUndelegate {
const TYPE_URL: &'static str = "/cosmos.staking.v1beta1.MsgUndelegate";
}

impl TypeUrl for cosmos::staking::v1beta1::MsgBeginRedelegate {
const TYPE_URL: &'static str = "/cosmos.staking.v1beta1.MsgBeginRedelegate";
}

impl TypeUrl for cosmos::base::abci::v1beta1::MsgData {
const TYPE_URL: &'static str = "/cosmos.base.v1beta1.abci.MsgData";
}

impl TypeUrl for cosmos::base::abci::v1beta1::TxMsgData {
const TYPE_URL: &'static str = "/cosmos.base.v1beta1.abci.TxMsgData";
}

#[cfg(feature = "cosmwasm")]
impl TypeUrl for cosmwasm::wasm::v1::MsgStoreCode {
const TYPE_URL: &'static str = "/cosmwasm.wasm.v1.MsgStoreCode";
}

#[cfg(feature = "cosmwasm")]
impl TypeUrl for cosmwasm::wasm::v1::MsgInstantiateContract {
const TYPE_URL: &'static str = "/cosmwasm.wasm.v1.MsgInstantiateContract";
}

#[cfg(feature = "cosmwasm")]
impl TypeUrl for cosmwasm::wasm::v1::MsgExecuteContract {
const TYPE_URL: &'static str = "/cosmwasm.wasm.v1.MsgExecuteContract";
}

#[cfg(feature = "cosmwasm")]
impl TypeUrl for cosmwasm::wasm::v1::MsgMigrateContract {
const TYPE_URL: &'static str = "/cosmwasm.wasm.v1.MsgMigrateContract";
}

#[cfg(feature = "cosmwasm")]
impl TypeUrl for cosmwasm::wasm::v1::MsgUpdateAdmin {
const TYPE_URL: &'static str = "/cosmwasm.wasm.v1.MsgUpdateAdmin";
}

#[cfg(feature = "cosmwasm")]
impl TypeUrl for cosmwasm::wasm::v1::MsgClearAdmin {
const TYPE_URL: &'static str = "/cosmwasm.wasm.v1.MsgClearAdmin";
}

#[cfg(feature = "cosmwasm")]
impl TypeUrl for cosmwasm::wasm::v1::MsgStoreCodeResponse {
const TYPE_URL: &'static str = "/cosmwasm.wasm.v1.MsgStoreCodeResponse";
}

#[cfg(feature = "cosmwasm")]
impl TypeUrl for cosmwasm::wasm::v1::MsgInstantiateContractResponse {
const TYPE_URL: &'static str = "/cosmwasm.wasm.v1.MsgInstantiateContractResponse";
}

#[cfg(feature = "cosmwasm")]
impl TypeUrl for cosmwasm::wasm::v1::MsgExecuteContractResponse {
const TYPE_URL: &'static str = "/cosmwasm.wasm.v1.MsgExecuteContractResponse";
}

#[cfg(feature = "cosmwasm")]
impl TypeUrl for cosmwasm::wasm::v1::MsgMigrateContractResponse {
const TYPE_URL: &'static str = "/cosmwasm.wasm.v1.MsgMigrateContractResponse";
}

#[cfg(feature = "cosmwasm")]
impl TypeUrl for cosmwasm::wasm::v1::MsgUpdateAdminResponse {
const TYPE_URL: &'static str = "/cosmwasm.wasm.v1.MsgUpdateAdminResponse";
}

#[cfg(feature = "cosmwasm")]
impl TypeUrl for cosmwasm::wasm::v1::MsgClearAdminResponse {
const TYPE_URL: &'static str = "/cosmwasm.wasm.v1.MsgClearAdminResponse";
}
4 changes: 3 additions & 1 deletion cosmrs/src/cosmwasm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
pub use crate::proto::cosmwasm::wasm::v1::AccessType;
use crate::{
prost_ext::ParseOptional, proto, tx::Msg, AccountId, Coin, Error, ErrorReport, Result,
proto::{self, traits::ParseOptional},
tx::Msg,
AccountId, Coin, Error, ErrorReport, Result,
};
use cosmos_sdk_proto::cosmwasm::wasm::v1::ContractCodeHistoryOperationType;

Expand Down
5 changes: 4 additions & 1 deletion cosmrs/src/crypto/legacy_amino.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Legacy Amino support.
use super::PublicKey;
use crate::{prost_ext::MessageExt, proto, Any, Error, ErrorReport, Result};
use crate::{
proto::{self, traits::MessageExt},
Any, Error, ErrorReport, Result,
};
use eyre::WrapErr;
use prost::Message;

Expand Down
18 changes: 11 additions & 7 deletions cosmrs/src/crypto/public_key.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
//! Public keys
use crate::{prost_ext::MessageExt, proto, AccountId, Error, ErrorReport, Result};
use crate::{
proto::{
self,
traits::{Message, MessageExt},
},
AccountId, Any, Error, ErrorReport, Result,
};
use eyre::WrapErr;
use prost::Message;
use prost_types::Any;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use subtle_encoding::base64;
Expand Down Expand Up @@ -57,13 +61,13 @@ impl PublicKey {
tendermint::PublicKey::Ed25519(_) => proto::cosmos::crypto::secp256k1::PubKey {
key: self.to_bytes(),
}
.to_bytes(),
.to_bytes()?,
tendermint::PublicKey::Secp256k1(_) => proto::cosmos::crypto::secp256k1::PubKey {
key: self.to_bytes(),
}
.to_bytes(),
_ => Err(Error::Crypto.into()),
}?;
.to_bytes()?,
_ => return Err(Error::Crypto.into()),
};

Ok(Any {
type_url: self.type_url().to_owned(),
Expand Down
1 change: 0 additions & 1 deletion cosmrs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ pub mod dev;

mod base;
mod error;
mod prost_ext;

pub use crate::{
base::{AccountId, Coin, Denom},
Expand Down
39 changes: 0 additions & 39 deletions cosmrs/src/prost_ext.rs

This file was deleted.

7 changes: 5 additions & 2 deletions cosmrs/src/tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,12 +117,15 @@ pub use self::{
body::Body,
fee::Fee,
mode_info::ModeInfo,
msg::{Msg, MsgProto},
msg::Msg,
raw::Raw,
sign_doc::SignDoc,
signer_info::{SignerInfo, SignerPublicKey},
};
pub use crate::{proto::cosmos::tx::signing::v1beta1::SignMode, ErrorReport};
pub use crate::{
proto::{cosmos::tx::signing::v1beta1::SignMode, traits::MessageExt},
ErrorReport,
};
pub use tendermint::abci::{transaction::Hash, Gas};

use crate::{proto, Error, Result};
Expand Down
7 changes: 5 additions & 2 deletions cosmrs/src/tx/auth_info.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! Auth info.
use super::{Fee, SignerInfo};
use crate::{prost_ext::MessageExt, proto, Error, ErrorReport, Result};
use crate::{
proto::{self, traits::MessageExt},
Error, ErrorReport, Result,
};

/// [`AuthInfo`] describes the fee and signer modes that are used to sign a transaction.
#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -29,7 +32,7 @@ impl AuthInfo {

/// Encode this type using Protocol Buffers.
pub fn into_bytes(self) -> Result<Vec<u8>> {
self.into_proto().to_bytes()
Ok(self.into_proto().to_bytes()?)
}
}

Expand Down
7 changes: 5 additions & 2 deletions cosmrs/src/tx/body.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
//! Transaction bodies.
use crate::{prost_ext::MessageExt, proto, ErrorReport, Result};
use crate::{
proto::{self, traits::MessageExt},
ErrorReport, Result,
};
use prost_types::Any;
use tendermint::block;

Expand Down Expand Up @@ -63,7 +66,7 @@ impl Body {

/// Encode this type using Protocol Buffers.
pub fn into_bytes(self) -> Result<Vec<u8>> {
self.into_proto().to_bytes()
Ok(self.into_proto().to_bytes()?)
}
}

Expand Down
Loading

0 comments on commit 5d3bed0

Please sign in to comment.