Skip to content

Commit

Permalink
Support for memo field (#1434)
Browse files Browse the repository at this point in the history
* Added initial support for feature #1433

* Comment for Memo type

* Derive Default and add Display instance for Memo

* Use Memo type directly

* Documented the memo prefix

Co-authored-by: Romain Ruetschi <[email protected]>
  • Loading branch information
adizere and romac authored Oct 12, 2021
1 parent 17b4c7e commit 271cffd
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 6 deletions.
6 changes: 6 additions & 0 deletions config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ trusting_period = '14days'
# Warning: This is an advanced feature! Modify with caution.
trust_threshold = { numerator = '1', denominator = '3' }

# Specify a string that Hermes will use as a memo for each transaction it submits
# to this chain. The string is limited to 50 characters. Default: '' (empty).
# Note: Hermes will append to the string defined here additional
# operational debugging information, e.g., relayer build version.
memo_prefix = ''

# This section specifies the filters for policy based relaying.
# Default: no policy/ filters
# The section is ignored if the global 'filter' option is set to 'false'.
Expand Down
9 changes: 8 additions & 1 deletion relayer-cli/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,14 @@ impl Configurable<Config> for CliCmd {
///
/// This can be safely deleted if you don't want to override config
/// settings from command-line options.
fn process_config(&self, config: Config) -> Result<Config, FrameworkError> {
fn process_config(&self, mut config: Config) -> Result<Config, FrameworkError> {
// Alter the memo for all chains to include a suffix with Hermes build details
let web = "https://hermes.informal.systems";
let suffix = format!("{} {} ({})", CliCmd::name(), CliCmd::version(), web);
for ccfg in config.chains.iter_mut() {
ccfg.memo_prefix.apply_suffix(&suffix);
}

match self {
CliCmd::Tx(cmd) => cmd.override_config(config),
// CliCmd::Help(cmd) => cmd.override_config(config),
Expand Down
14 changes: 10 additions & 4 deletions relayer/src/chain/cosmos.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ use ibc_proto::ibc::core::connection::v1::{
QueryClientConnectionsRequest, QueryConnectionsRequest,
};

use crate::error::Error;
use crate::event::monitor::{EventMonitor, EventReceiver};
use crate::keyring::{KeyEntry, KeyRing, Store};
use crate::light_client::tendermint::LightClient as TmLightClient;
use crate::light_client::LightClient;
use crate::light_client::Verified;
use crate::{chain::QueryResponse, event::monitor::TxMonitorCmd};
use crate::{config::types::Memo, error::Error};
use crate::{
config::{AddressType, ChainConfig, GasPrice},
sdk_error::sdk_error_from_tx_sync_error_code,
Expand Down Expand Up @@ -229,7 +229,7 @@ impl CosmosSdkChain {

debug!("[{}] default fee: {}", self.id(), PrettyFee(&default_fee));

let (body, body_buf) = tx_body_and_bytes(proto_msgs)?;
let (body, body_buf) = tx_body_and_bytes(proto_msgs, self.tx_memo())?;

let (auth_info, auth_buf) = auth_info_and_bytes(signer_info.clone(), default_fee.clone())?;
let signed_doc = self.signed_doc(body_buf.clone(), auth_buf, account_seq)?;
Expand Down Expand Up @@ -644,6 +644,11 @@ impl CosmosSdkChain {
.trusting_period
.unwrap_or(2 * unbonding_period / 3)
}

/// Returns the preconfigured memo to be used for every submitted transaction
fn tx_memo(&self) -> &Memo {
&self.config.memo_prefix
}
}

fn empty_event_present(events: &[IbcEvent]) -> bool {
Expand Down Expand Up @@ -1987,15 +1992,16 @@ fn auth_info_and_bytes(signer_info: SignerInfo, fee: Fee) -> Result<(AuthInfo, V
Ok((auth_info, auth_buf))
}

fn tx_body_and_bytes(proto_msgs: Vec<Any>) -> Result<(TxBody, Vec<u8>), Error> {
fn tx_body_and_bytes(proto_msgs: Vec<Any>, memo: &Memo) -> Result<(TxBody, Vec<u8>), Error> {
// Create TxBody
let body = TxBody {
messages: proto_msgs.to_vec(),
memo: "".to_string(),
memo: memo.to_string(),
timeout_height: 0_u64,
extension_options: Vec::<Any>::new(),
non_critical_extension_options: Vec::<Any>::new(),
};

// A protobuf serialization of a TxBody
let mut body_buf = Vec::new();
prost::Message::encode(&body, &mut body_buf).unwrap();
Expand Down
1 change: 1 addition & 0 deletions relayer/src/chain/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,7 @@ pub mod test_utils {
trust_threshold: Default::default(),
packet_filter: PacketFilter::default(),
address_type: AddressType::default(),
memo_prefix: Default::default(),
}
}
}
4 changes: 3 additions & 1 deletion relayer/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use tendermint_light_client::types::TrustThreshold;
use ibc::ics24_host::identifier::{ChainId, ChannelId, PortId};
use ibc::timestamp::ZERO_DURATION;

use crate::config::types::{MaxMsgNum, MaxTxSize};
use crate::config::types::{MaxMsgNum, MaxTxSize, Memo};
use crate::error::Error;

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
Expand Down Expand Up @@ -320,6 +320,8 @@ pub struct ChainConfig {
pub clock_drift: Duration,
#[serde(default, with = "humantime_serde")]
pub trusting_period: Option<Duration>,
#[serde(default)]
pub memo_prefix: Memo,

// these two need to be last otherwise we run into `ValueAfterTable` error when serializing to TOML
#[serde(default)]
Expand Down
62 changes: 62 additions & 0 deletions relayer/src/config/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
//! Implements defaults, as well as serializing and
//! deserializing with upper-bound verification.
use core::fmt;

use serde::de::Unexpected;
use serde::{de::Error, Deserialize, Deserializer, Serialize, Serializer};

Expand Down Expand Up @@ -99,3 +101,63 @@ impl From<MaxTxSize> for usize {
m.0
}
}

/// A memo domain-type.
///
/// Hermes uses this type to populate the `tx.memo` field for
/// each transaction it submits.
/// The memo can be configured on a per-chain basis.
///
#[derive(Clone, Debug, Default)]
pub struct Memo(String);

impl Memo {
const MAX_LEN: usize = 50;

pub fn apply_suffix(&mut self, suffix: &str) {
// Add a separator if the memo
// is pre-populated with some content already.
if !self.0.is_empty() {
self.0.push_str(" | ");
}

self.0.push_str(suffix);
}

pub fn as_str(&self) -> &str {
&self.0
}
}

impl<'de> Deserialize<'de> for Memo {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let m = String::deserialize(deserializer)?;

if m.len() > Self::MAX_LEN {
return Err(D::Error::invalid_length(
m.len(),
&format!("a string length of at most {}", Self::MAX_LEN).as_str(),
));
}

Ok(Memo(m))
}
}

impl Serialize for Memo {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.0.serialize(serializer)
}
}

impl fmt::Display for Memo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.as_str())
}
}

0 comments on commit 271cffd

Please sign in to comment.