Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Cosmos SDK Module #5370

Open
wants to merge 15 commits into
base: main
Choose a base branch
from

Conversation

yjamin
Copy link

@yjamin yjamin commented Feb 4, 2025

Description

This PR adds Agent support for the upcoming Hyperlane Cosmos SDK Module, called CosmosNative, to differentiate it from the existing CosmWasm (CW) implementation.

Drive-by Fixes

None

Related Issues

  • None reported.

Backward Compatibility

  • No breaking changes—this PR extends functionality without affecting existing implementations.

Testing

The CosmosNative implementation has been tested through:

  • Automated E2E tests – Following the same approach as the CW implementation.
  • Deployed preview – Currently live on the alpha network for additional validation.

E2E Testing Details

  • They pretty much follow the same structure as the CW tests.
  • They spin up a lightweight Cosmos SDK chain (simapp) with the Hyperlane Cosmos module.

Note: The simapp includes a preview version of the Hyperlane module since it's not officially released yet.

To run the tests locally, use:

cargo test -r -p run-locally --features cosmosnative -- cosmosnative::test --nocapture

Copy link

changeset-bot bot commented Feb 4, 2025

⚠️ No Changeset found

Latest commit: daf2b1e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@yjamin yjamin changed the title Feat: Cosmos SDK Module feat: Cosmos SDK Module Feb 4, 2025
Copy link
Collaborator

@tkporter tkporter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only spent about 20 mins the other day poking around some of the first files, hopefully will have time to leave more feedback but posting for now

The biggest thing that stands out to me is just how much duplication there is with hyperlane-cosmos. I could see there being some arguments for keeping this separate but I feel like consolidating as much as possible is probably the better move. Curious what @daniel-savu @ameten think

let tip = self.get_finalized_block_number().await?;
let sequence = self
.provider
.rest()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When is rest vs grpc vs rpc appropriate? Given we have rpc and grpc for the CW implementation, I have a slight preference to just keep needing just those 2 - is that possible?

Rationale being that we have some work in progress rn to add some metrics for our Cosmos gRPC & RPC interactions, would be nice to not need to extend that work to cover rest too

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Cosmos, gRPC, REST, and RPC all communicate with the chain through ABCI. They serve as wrappers around raw ABCI queries, meaning the main differences is in convenience and probably some performance overhead.

We chose to remove gRPC since it is not fully supported on our public KYVE endpoints, but this shouldn't matter for the agents implementation. We used REST as the query client implementation, because of its simplicity and human readability.

If we want to stick with gRPC and RPC, we would need to manually implement the gRPC Query Client for the Cosmos module. This includes handling protobuf serialization for each struct, i think it would make sense to move this into a separate repository (similar to the CW queries) and add that to the monorepo.

let value = attribute
.value_str()
.map_err(HyperlaneCosmosError::from)?
.replace("\"", "");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why's this necessary? curious what a raw EventAttribute here would look like


async fn get_ism(&self) -> ChainResult<Option<ISM>> {
let isms = self.provider.rest().isms(ReorgPeriod::None).await?;
for ism in isms {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should be able to do .into_iter() to not need to clone

/// metadata offchain fetching and onchain formatting standard.
async fn module_type(&self) -> ChainResult<ModuleType> {
let isms = self.provider.rest().isms(ReorgPeriod::None).await?;
for ism in isms {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

/// This error can then be converted into the broader error type
/// in hyperlane-core using the `From` trait impl
#[derive(Debug, thiserror::Error)]
pub enum HyperlaneCosmosError {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thoughts on reusing this with hyperlane-cosmos?


#[derive(Debug, Eq, PartialEq)]
/// An event parsed from the RPC response.
pub struct ParsedEvent<T: PartialEq> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lots of opportunity for reuse with hyperlane-cosmos

tracing = { workspace = true }
tracing-futures = { workspace = true }
url = { workspace = true }
prost = "0.13.4"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we can reference it in workspace?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, will look into it

hex = { workspace = true }
http = { workspace = true }
hyperlane-core = { path = "../../hyperlane-core", features = ["async"] }
hyperlane-cosmwasm-interface.workspace = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
hyperlane-cosmwasm-interface.workspace = true
hyperlane-cosmwasm-interface = { workspace = true }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why some dependencies are remove. We don't use them anymore?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not too sure. I only used the cli for managing dependencies. Can re-add them if we want to be sure.

@@ -110,7 +110,7 @@ impl<T: Debug + Clone + Sync + Send + Indexable + 'static> ForwardSequenceAwareS
/// If there are no logs to index, returns `None`.
/// If there are logs to index, returns the range of logs, either by sequence or block number
/// depending on the mode.
#[instrument(ret)]
// #[instrument(ret)]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why this annotation was removed?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, just forgot to remove the comment again.

Comment on lines 129 to 132
// limit this to 1
if start < 1 {
return 1..=current_indexing_snapshot.at_block;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it will work for all chains. Presumably, Ethereum genesis block has height 0.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, good point, that was just annoying when testing with a local chain.

This will probably never be the case in a real world deployment, however i think it would make sense to add an optional additional property like genesis_height or deployment_height in IndexSettings that defines on what height the mailbox has been deployed and this should be the lower bound of each indexer, as there can't be any events prior to that height.

The default value of that property can then be set by the protocol type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just for visibility: RoutingISM and AggregationISM are not implemented

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, they are not supported by the Cosmos Module implementation yet. Will add a comment for clarity 👍

Comment on lines 19 to 20
// always write the 0th index
if index > curr || index == 0 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder what is the issue which requires this change?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't seem to replicate the issue anymore, nor did i fully understand it at that time.
When i first started the implementation, the syncer wouldn't write the 0th index. That was probably due to a faulty implementation of latest_checkpoint in our merkle_tree_hook. A lot has changed since then, i would just remove it again.

@@ -1515,7 +1515,7 @@
"protocol": "ethereum",
"rpcUrls": [
{
"http": "http://127.0.0.1:8545"
"http": "http://0.0.0.0:8545"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this change was intentional?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, not intentional, will remove. Thanks 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it will make sense to have the functionality shared by CosmosWasm and CosmosNative implementations to be extracted into separate module and re-used?

Example: sender_and_nonce method in CosmosProvider and CosmosNativeProvider. There can be more examples like this.

Comment on lines 1 to 43
use std::{io::Cursor, ops::Deref};

use cosmrs::{
crypto::PublicKey,
proto::{
cosmos::{
auth::v1beta1::QueryAccountRequest,
bank::v1beta1::{QueryBalanceRequest, QueryBalanceResponse},
base::abci::v1beta1::TxResponse,
},
tendermint::types::Block,
},
tx::{SequenceNumber, SignerInfo, SignerPublicKey},
AccountId, Any, Coin, Tx,
};
use derive_new::new;

use super::{rest::RestProvider, RpcProvider};
use crate::{
ConnectionConf, CosmosAccountId, CosmosAddress, CosmosAmount, HyperlaneCosmosError, Signer,
};
use hyperlane_core::{
h512_to_bytes,
rpc_clients::{BlockNumberGetter, FallbackProvider},
utils::{self, to_atto},
AccountAddressType, BlockInfo, ChainCommunicationError, ChainInfo, ChainResult,
ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider,
HyperlaneProviderError, LogMeta, ModuleType, RawHyperlaneMessage, TxnInfo, TxnReceiptInfo,
H256, H512, U256,
};
use itertools::Itertools;
use prost::Message;
use reqwest::Error;
use serde::{de::DeserializeOwned, Deserialize};
use tendermint::{hash::Algorithm, Hash};
use tendermint_rpc::{
client::CompatMode,
endpoint::{block, block_results, tx},
Client, HttpClient,
};
use time::OffsetDateTime;
use tonic::async_trait;
use tracing::{debug, trace, warn};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is worth to do everywhere.

It will be great to format imports as follows.

  1. std (and empty line)
  2. external crates (and empty line)
  3. internal crates (like hyperlane_core) (and empty line)
  4. crate:: imports (and empty line)
  5. super:: imports (and empty line)
Suggested change
use std::{io::Cursor, ops::Deref};
use cosmrs::{
crypto::PublicKey,
proto::{
cosmos::{
auth::v1beta1::QueryAccountRequest,
bank::v1beta1::{QueryBalanceRequest, QueryBalanceResponse},
base::abci::v1beta1::TxResponse,
},
tendermint::types::Block,
},
tx::{SequenceNumber, SignerInfo, SignerPublicKey},
AccountId, Any, Coin, Tx,
};
use derive_new::new;
use super::{rest::RestProvider, RpcProvider};
use crate::{
ConnectionConf, CosmosAccountId, CosmosAddress, CosmosAmount, HyperlaneCosmosError, Signer,
};
use hyperlane_core::{
h512_to_bytes,
rpc_clients::{BlockNumberGetter, FallbackProvider},
utils::{self, to_atto},
AccountAddressType, BlockInfo, ChainCommunicationError, ChainInfo, ChainResult,
ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider,
HyperlaneProviderError, LogMeta, ModuleType, RawHyperlaneMessage, TxnInfo, TxnReceiptInfo,
H256, H512, U256,
};
use itertools::Itertools;
use prost::Message;
use reqwest::Error;
use serde::{de::DeserializeOwned, Deserialize};
use tendermint::{hash::Algorithm, Hash};
use tendermint_rpc::{
client::CompatMode,
endpoint::{block, block_results, tx},
Client, HttpClient,
};
use time::OffsetDateTime;
use tonic::async_trait;
use tracing::{debug, trace, warn};
use std::{io::Cursor, ops::Deref};
use cosmrs::{
crypto::PublicKey,
proto::{
cosmos::{
auth::v1beta1::QueryAccountRequest,
bank::v1beta1::{QueryBalanceRequest, QueryBalanceResponse},
base::abci::v1beta1::TxResponse,
},
tendermint::types::Block,
},
tx::{SequenceNumber, SignerInfo, SignerPublicKey},
AccountId, Any, Coin, Tx,
};
use derive_new::new;
use itertools::Itertools;
use prost::Message;
use reqwest::Error;
use serde::{de::DeserializeOwned, Deserialize};
use tendermint::{hash::Algorithm, Hash};
use tendermint_rpc::{
client::CompatMode,
endpoint::{block, block_results, tx},
Client, HttpClient,
};
use time::OffsetDateTime;
use tonic::async_trait;
use tracing::{debug, trace, warn};
use hyperlane_core::{
h512_to_bytes,
rpc_clients::{BlockNumberGetter, FallbackProvider},
utils::{self, to_atto},
AccountAddressType, BlockInfo, ChainCommunicationError, ChainInfo, ChainResult,
ContractLocator, HyperlaneChain, HyperlaneDomain, HyperlaneMessage, HyperlaneProvider,
HyperlaneProviderError, LogMeta, ModuleType, RawHyperlaneMessage, TxnInfo, TxnReceiptInfo,
H256, H512, U256,
};
use crate::{
ConnectionConf, CosmosAccountId, CosmosAddress, CosmosAmount, HyperlaneCosmosError, Signer,
};
use super::{rest::RestProvider, RpcProvider};

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to know, will adapt this convention 👍

@ameten
Copy link
Contributor

ameten commented Feb 26, 2025

I would agree with @tkporter in this comment that reducing the duplication of the code will be great since it will make the review of the change much easier.

It will be ideal to have the refactoring of the re-used parts as a separate PR and, if it too hard, at least as a separate commit so that it can be reviewed without other CosmosNative changes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: In Review
Development

Successfully merging this pull request may close these issues.

3 participants