diff --git a/config.example.toml b/config.example.toml index cd5d760..92b9407 100644 --- a/config.example.toml +++ b/config.example.toml @@ -5,6 +5,16 @@ aliases = "cosmos-transactions-bot-aliases.toml" # Defaults to "Etc/GMT", so UTC+0 timezone = "Europe/Moscow" +# Prometheus metrics configuration. +[metrics] +# Whether to enable Prometheus metrics. Defaults to true. +enabled = true +# Prometheus metrics listen address. +# If you are running this bot on a same server than your Prometheus instance is, +# you might want to switch it to "127.0.0.1:" so it won't be accessible from outside. +# Defaults to ":9580" (so, port 9580 on all interfaces and accessible from outside). +listen-addr = ":9580" + # Logging configuration [log] # Log level. Set to "debug" or "trace" to make it more verbose, or to "warn"/"error" @@ -106,6 +116,10 @@ filters = ["message.action = '/cosmos.staking.v1beta1.MsgBeginRedelegate'"] [[chains]] # Chain codename, required. name = "cosmos" +# Chain ID. Optional, if omitted, multichain denoms matching would not work +# (like, if you want to filter transactions that are transferring tokens via IBC +# from one chain to another). +chain-id = "cosmoshub-4" # Chain pretty name, optional. If provided, would be used in reports, if not, # codename would be used. pretty-name = "Cosmos Hub" @@ -165,6 +179,7 @@ validator-link-pattern = "https://mintscan.io/cosmos/validators/%s" # There can be multiple chains. [[chains]] name = "sentinel" +chain-id = "sentinelhub-2" pretty-name = "Sentinel" tendermint-nodes = ["https://rpc.sentinel.quokkastake.io:443"] api-nodes = ["https://api.sentinel.quokkastake.io"] diff --git a/pkg/app.go b/pkg/app.go index 2a19b3e..1148636 100644 --- a/pkg/app.go +++ b/pkg/app.go @@ -23,7 +23,7 @@ type App struct { Chains []*configTypes.Chain NodesManager *nodesManagerPkg.NodesManager Reporters reportersPkg.Reporters - DataFetchers map[string]*data_fetcher.DataFetcher + DataFetcher *data_fetcher.DataFetcher Filterer *filtererPkg.Filterer MetricsManager *metricsPkg.Manager @@ -51,10 +51,12 @@ func NewApp(config *config.AppConfig, version string) *App { ) } - dataFetchers := make(map[string]*data_fetcher.DataFetcher, len(config.Chains)) - for _, chain := range config.Chains { - dataFetchers[chain.Name] = data_fetcher.NewDataFetcher(logger, chain, aliasManager, metricsManager) - } + dataFetcher := data_fetcher.NewDataFetcher( + logger, + config, + aliasManager, + metricsManager, + ) filterer := filtererPkg.NewFilterer(logger, config, metricsManager) @@ -63,7 +65,7 @@ func NewApp(config *config.AppConfig, version string) *App { Chains: config.Chains, Reporters: reporters, NodesManager: nodesManager, - DataFetchers: dataFetchers, + DataFetcher: dataFetcher, Filterer: filterer, MetricsManager: metricsManager, Version: version, @@ -92,8 +94,6 @@ func (a *App) Start() { for { select { case rawReport := <-a.NodesManager.Channel: - fetcher, _ := a.DataFetchers[rawReport.Chain.Name] - reportablesForReporters := a.Filterer.GetReportableForReporters(rawReport) if len(reportablesForReporters) == 0 { @@ -113,7 +113,7 @@ func (a *App) Start() { Str("hash", report.Reportable.GetHash()). Msg("Got report") - rawReport.Reportable.GetAdditionalData(fetcher) + rawReport.Reportable.GetAdditionalData(a.DataFetcher) reporter := a.Reporters.FindByName(reporterName) diff --git a/pkg/config/toml_config/chain.go b/pkg/config/toml_config/chain.go index b1628e7..41e1634 100644 --- a/pkg/config/toml_config/chain.go +++ b/pkg/config/toml_config/chain.go @@ -10,6 +10,7 @@ import ( type Chain struct { Name string `toml:"name"` PrettyName string `toml:"pretty-name"` + ChainID string `toml:"chain-id"` TendermintNodes []string `toml:"tendermint-nodes"` APINodes []string `toml:"api-nodes"` Queries []string `default:"[\"tx.height > 1\"]" toml:"queries"` @@ -76,6 +77,7 @@ func (c *Chain) ToAppConfigChain() *types.Chain { return &types.Chain{ Name: c.Name, PrettyName: c.PrettyName, + ChainID: c.ChainID, TendermintNodes: c.TendermintNodes, APINodes: c.APINodes, Queries: queries, @@ -89,6 +91,7 @@ func FromAppConfigChain(c *types.Chain) *Chain { chain := &Chain{ Name: c.Name, PrettyName: c.PrettyName, + ChainID: c.ChainID, TendermintNodes: c.TendermintNodes, APINodes: c.APINodes, Denoms: TomlConfigDenomsFrom(c.Denoms), diff --git a/pkg/config/types/chain.go b/pkg/config/types/chain.go index c3d0a33..e9489a1 100644 --- a/pkg/config/types/chain.go +++ b/pkg/config/types/chain.go @@ -20,9 +20,20 @@ func (c Chains) FindByName(name string) *Chain { return nil } +func (c Chains) FindByChainID(chainID string) *Chain { + for _, chain := range c { + if chain.ChainID == chainID { + return chain + } + } + + return nil +} + type Chain struct { Name string PrettyName string + ChainID string TendermintNodes []string APINodes []string Queries []query.Query @@ -98,15 +109,25 @@ func (c Chain) GetBlockLink(height int64) Link { func (c *Chain) DisplayWarnings(logger *zerolog.Logger) { if len(c.Denoms) == 0 { - logger.Warn().Str("chain", c.Name).Msg("No denoms set, prices in USD won't be displayed.") + logger.Warn(). + Str("chain", c.Name). + Msg("No denoms set, prices in USD won't be displayed.") } else { for _, denom := range c.Denoms { denom.DisplayWarnings(c, logger) } } + if c.ChainID == "" { + logger.Warn(). + Str("chain", c.Name). + Msg("chain-id is not set, multichain denom matching won't work.") + } + if c.Explorer == nil { - logger.Warn().Str("chain", c.Name).Msg("Explorer config not set, links won't be generated.") + logger.Warn(). + Str("chain", c.Name). + Msg("Explorer config not set, links won't be generated.") } else { c.Explorer.DisplayWarnings(logger, c) } diff --git a/pkg/converter/converter.go b/pkg/converter/converter.go index f88317f..d5e2cdc 100644 --- a/pkg/converter/converter.go +++ b/pkg/converter/converter.go @@ -84,6 +84,10 @@ func (c *Converter) ParseEvent(event jsonRpcTypes.RPCResponse, nodeURL string) t return nil } + c.Logger.Trace(). + Str("values", fmt.Sprintf("%+v", resultEvent.Events)). + Msg("Event values") + eventDataTx, ok := resultEvent.Data.(tendermintTypes.EventDataTx) if !ok { c.Logger.Debug().Msg("Could not convert tx result to EventDataTx.") diff --git a/pkg/data_fetcher/data_fetcher.go b/pkg/data_fetcher/data_fetcher.go index b36f288..423dc85 100644 --- a/pkg/data_fetcher/data_fetcher.go +++ b/pkg/data_fetcher/data_fetcher.go @@ -2,9 +2,13 @@ package data_fetcher import ( "fmt" + configPkg "main/pkg/config" "main/pkg/metrics" amountPkg "main/pkg/types/amount" "strconv" + "strings" + + transferTypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" "main/pkg/alias_manager" "main/pkg/cache" @@ -20,32 +24,34 @@ import ( type DataFetcher struct { Logger zerolog.Logger Cache *cache.Cache - Chain *configTypes.Chain + Config *configPkg.AppConfig PriceFetchers map[string]priceFetchers.PriceFetcher AliasManager *alias_manager.AliasManager MetricsManager *metrics.Manager - TendermintApiClients []*api.TendermintApiClient + TendermintApiClients map[string][]*api.TendermintApiClient } func NewDataFetcher( logger *zerolog.Logger, - chain *configTypes.Chain, + config *configPkg.AppConfig, aliasManager *alias_manager.AliasManager, metricsManager *metrics.Manager, ) *DataFetcher { - tendermintApiClients := make([]*api.TendermintApiClient, len(chain.APINodes)) - for index, node := range chain.APINodes { - tendermintApiClients[index] = api.NewTendermintApiClient(logger, node, chain) + tendermintApiClients := make(map[string][]*api.TendermintApiClient, len(config.Chains)) + for _, chain := range config.Chains { + tendermintApiClients[chain.Name] = make([]*api.TendermintApiClient, len(chain.APINodes)) + for index, node := range chain.APINodes { + tendermintApiClients[chain.Name][index] = api.NewTendermintApiClient(logger, node, chain) + } } return &DataFetcher{ Logger: logger.With(). Str("component", "data_fetcher"). - Str("chain", chain.Name). Logger(), Cache: cache.NewCache(logger), PriceFetchers: map[string]priceFetchers.PriceFetcher{}, - Chain: chain, + Config: config, TendermintApiClients: tendermintApiClients, AliasManager: aliasManager, MetricsManager: metricsManager, @@ -65,11 +71,17 @@ func (f *DataFetcher) GetPriceFetcher(info *configTypes.DenomInfo) priceFetchers return nil } -func (f *DataFetcher) GetDenomPriceKey(denomInfo *configTypes.DenomInfo) string { - return fmt.Sprintf("%s_price_%s", f.Chain.Name, denomInfo.Denom) +func (f *DataFetcher) GetDenomPriceKey( + chain *configTypes.Chain, + denomInfo *configTypes.DenomInfo, +) string { + return fmt.Sprintf("%s_price_%s", chain.Name, denomInfo.Denom) } -func (f *DataFetcher) MaybeGetCachedPrice(denomInfo *configTypes.DenomInfo) (float64, bool) { - cacheKey := f.GetDenomPriceKey(denomInfo) +func (f *DataFetcher) MaybeGetCachedPrice( + chain *configTypes.Chain, + denomInfo *configTypes.DenomInfo, +) (float64, bool) { + cacheKey := f.GetDenomPriceKey(chain, denomInfo) if cachedPrice, cachedPricePresent := f.Cache.Get(cacheKey); cachedPricePresent { if cachedPriceFloat, ok := cachedPrice.(float64); ok { @@ -83,21 +95,25 @@ func (f *DataFetcher) MaybeGetCachedPrice(denomInfo *configTypes.DenomInfo) (flo return 0, false } -func (f *DataFetcher) SetCachedPrice(denomInfo *configTypes.DenomInfo, notCachedPrice float64) { - cacheKey := f.GetDenomPriceKey(denomInfo) +func (f *DataFetcher) SetCachedPrice( + chain *configTypes.Chain, + denomInfo *configTypes.DenomInfo, + notCachedPrice float64, +) { + cacheKey := f.GetDenomPriceKey(chain, denomInfo) f.Cache.Set(cacheKey, notCachedPrice) } -func (f *DataFetcher) PopulateAmount(amount *amountPkg.Amount) { - f.PopulateAmounts(amountPkg.Amounts{amount}) +func (f *DataFetcher) PopulateAmount(chain *configTypes.Chain, amount *amountPkg.Amount) { + f.PopulateAmounts(chain, amountPkg.Amounts{amount}) } -func (f *DataFetcher) PopulateAmounts(amounts amountPkg.Amounts) { +func (f *DataFetcher) PopulateAmounts(chain *configTypes.Chain, amounts amountPkg.Amounts) { denomsToQueryByPriceFetcher := make(map[string]configTypes.DenomInfos) // 1. Getting cached prices. for _, amount := range amounts { - denomInfo := f.Chain.Denoms.Find(amount.BaseDenom) + denomInfo := chain.Denoms.Find(amount.BaseDenom) if denomInfo == nil { continue } @@ -105,7 +121,7 @@ func (f *DataFetcher) PopulateAmounts(amounts amountPkg.Amounts) { amount.ConvertDenom(denomInfo.DisplayDenom, denomInfo.DenomCoefficient) // If we've found cached price, then using it. - if price, cached := f.MaybeGetCachedPrice(denomInfo); cached { + if price, cached := f.MaybeGetCachedPrice(chain, denomInfo); cached { amount.AddUSDPrice(price) continue } @@ -133,7 +149,7 @@ func (f *DataFetcher) PopulateAmounts(amounts amountPkg.Amounts) { return } - uncachedPrices := make(map[string]float64, 0) + uncachedPrices := make(map[string]float64) // 3. Fetching all prices by price fetcher. for priceFetcherKey, priceFetcher := range f.PriceFetchers { @@ -151,7 +167,7 @@ func (f *DataFetcher) PopulateAmounts(amounts amountPkg.Amounts) { // Saving it to cache for denomInfo, price := range prices { - f.SetCachedPrice(denomInfo, price) + f.SetCachedPrice(chain, denomInfo, price) uncachedPrices[denomInfo.Denom] = price } @@ -168,8 +184,8 @@ func (f *DataFetcher) PopulateAmounts(amounts amountPkg.Amounts) { } } -func (f *DataFetcher) GetValidator(address string) (*responses.Validator, bool) { - keyName := f.Chain.Name + "_validator_" + address +func (f *DataFetcher) GetValidator(chain *configTypes.Chain, address string) (*responses.Validator, bool) { + keyName := chain.Name + "_validator_" + address if cachedValidator, cachedValidatorPresent := f.Cache.Get(keyName); cachedValidatorPresent { if cachedValidatorParsed, ok := cachedValidator.(*responses.Validator); ok { @@ -180,9 +196,9 @@ func (f *DataFetcher) GetValidator(address string) (*responses.Validator, bool) return nil, false } - for _, node := range f.TendermintApiClients { + for _, node := range f.TendermintApiClients[chain.Name] { notCachedValidator, err, queryInfo := node.GetValidator(address) - f.MetricsManager.LogTendermintQuery(f.Chain.Name, queryInfo, QueryInfo.QueryTypeValidator) + f.MetricsManager.LogTendermintQuery(chain.Name, queryInfo, QueryInfo.QueryTypeValidator) if err != nil { f.Logger.Error().Msg("Error fetching validator") continue @@ -197,11 +213,12 @@ func (f *DataFetcher) GetValidator(address string) (*responses.Validator, bool) } func (f *DataFetcher) GetRewardsAtBlock( + chain *configTypes.Chain, delegator string, validator string, block int64, ) ([]responses.Reward, bool) { - keyName := f.Chain.Name + "_rewards_" + delegator + "_" + validator + "_" + strconv.FormatInt(block, 10) + keyName := chain.Name + "_rewards_" + delegator + "_" + validator + "_" + strconv.FormatInt(block, 10) if cachedRewards, cachedRewardsPresent := f.Cache.Get(keyName); cachedRewardsPresent { if cachedRewardsParsed, ok := cachedRewards.([]responses.Reward); ok { @@ -212,9 +229,9 @@ func (f *DataFetcher) GetRewardsAtBlock( return []responses.Reward{}, false } - for _, node := range f.TendermintApiClients { + for _, node := range f.TendermintApiClients[chain.Name] { notCachedValidator, err, queryInfo := node.GetDelegatorsRewardsAtBlock(delegator, validator, block-1) - f.MetricsManager.LogTendermintQuery(f.Chain.Name, queryInfo, QueryInfo.QueryTypeRewards) + f.MetricsManager.LogTendermintQuery(chain.Name, queryInfo, QueryInfo.QueryTypeRewards) if err != nil { f.Logger.Error().Err(err).Msg("Error fetching rewards") continue @@ -229,10 +246,11 @@ func (f *DataFetcher) GetRewardsAtBlock( } func (f *DataFetcher) GetCommissionAtBlock( + chain *configTypes.Chain, validator string, block int64, ) ([]responses.Commission, bool) { - keyName := f.Chain.Name + "_commission_" + validator + "_" + strconv.FormatInt(block, 10) + keyName := chain.Name + "_commission_" + validator + "_" + strconv.FormatInt(block, 10) if cachedCommission, cachedCommissionPresent := f.Cache.Get(keyName); cachedCommissionPresent { if cachedCommissionParsed, ok := cachedCommission.([]responses.Commission); ok { @@ -243,9 +261,9 @@ func (f *DataFetcher) GetCommissionAtBlock( return []responses.Commission{}, false } - for _, node := range f.TendermintApiClients { + for _, node := range f.TendermintApiClients[chain.Name] { notCachedEntry, err, queryInfo := node.GetValidatorCommissionAtBlock(validator, block-1) - f.MetricsManager.LogTendermintQuery(f.Chain.Name, queryInfo, QueryInfo.QueryTypeCommission) + f.MetricsManager.LogTendermintQuery(chain.Name, queryInfo, QueryInfo.QueryTypeCommission) if err != nil { f.Logger.Error().Err(err).Msg("Error fetching commission") continue @@ -259,8 +277,8 @@ func (f *DataFetcher) GetCommissionAtBlock( return []responses.Commission{}, false } -func (f *DataFetcher) GetProposal(id string) (*responses.Proposal, bool) { - keyName := f.Chain.Name + "_proposal_" + id +func (f *DataFetcher) GetProposal(chain *configTypes.Chain, id string) (*responses.Proposal, bool) { + keyName := chain.Name + "_proposal_" + id if cachedEntry, cachedEntryPresent := f.Cache.Get(keyName); cachedEntryPresent { if cachedEntryParsed, ok := cachedEntry.(*responses.Proposal); ok { @@ -271,9 +289,9 @@ func (f *DataFetcher) GetProposal(id string) (*responses.Proposal, bool) { return nil, false } - for _, node := range f.TendermintApiClients { + for _, node := range f.TendermintApiClients[chain.Name] { notCachedEntry, err, queryInfo := node.GetProposal(id) - f.MetricsManager.LogTendermintQuery(f.Chain.Name, queryInfo, QueryInfo.QueryTypeProposal) + f.MetricsManager.LogTendermintQuery(chain.Name, queryInfo, QueryInfo.QueryTypeProposal) if err != nil { f.Logger.Error().Err(err).Msg("Error fetching proposal") continue @@ -287,8 +305,8 @@ func (f *DataFetcher) GetProposal(id string) (*responses.Proposal, bool) { return nil, false } -func (f *DataFetcher) GetStakingParams() (*responses.StakingParams, bool) { - keyName := f.Chain.Name + "_staking_params" +func (f *DataFetcher) GetStakingParams(chain *configTypes.Chain) (*responses.StakingParams, bool) { + keyName := chain.Name + "_staking_params" if cachedEntry, cachedEntryPresent := f.Cache.Get(keyName); cachedEntryPresent { if cachedEntryParsed, ok := cachedEntry.(*responses.StakingParams); ok { @@ -299,9 +317,9 @@ func (f *DataFetcher) GetStakingParams() (*responses.StakingParams, bool) { return nil, false } - for _, node := range f.TendermintApiClients { + for _, node := range f.TendermintApiClients[chain.Name] { notCachedEntry, err, queryInfo := node.GetStakingParams() - f.MetricsManager.LogTendermintQuery(f.Chain.Name, queryInfo, QueryInfo.QueryTypeStakingParams) + f.MetricsManager.LogTendermintQuery(chain.Name, queryInfo, QueryInfo.QueryTypeStakingParams) if err != nil { f.Logger.Error().Err(err).Msg("Error fetching staking params") @@ -316,10 +334,123 @@ func (f *DataFetcher) GetStakingParams() (*responses.StakingParams, bool) { return nil, false } +func (f *DataFetcher) GetIbcRemoteChainID( + chain *configTypes.Chain, + channel string, + port string, +) (string, bool) { + keyName := chain.Name + "_channel_" + channel + "_port_" + port + + if cachedEntry, cachedEntryPresent := f.Cache.Get(keyName); cachedEntryPresent { + if cachedEntryParsed, ok := cachedEntry.(string); ok { + return cachedEntryParsed, true + } + + f.Logger.Error().Msg("Could not convert cached IBC channel to string") + return "", false + } + + var ( + ibcChannel *responses.IbcChannel + ibcClientState *responses.IbcIdentifiedClientState + ) + + for _, node := range f.TendermintApiClients[chain.Name] { + ibcChannelResponse, err, queryInfo := node.GetIbcChannel(channel, port) + f.MetricsManager.LogTendermintQuery(chain.Name, queryInfo, QueryInfo.QueryTypeIbcChannel) + if err != nil { + f.Logger.Error().Err(err).Msg("Error fetching IBC channel") + continue + } + + ibcChannel = ibcChannelResponse + break + } + + if ibcChannel == nil { + f.Logger.Error().Msg("Could not connect to any nodes to get IBC channel") + return "", false + } + + if len(ibcChannel.ConnectionHops) != 1 { + f.Logger.Error(). + Int("len", len(ibcChannel.ConnectionHops)). + Msg("Could not connect to any nodes to get IBC channel") + return "", false + } + + for _, node := range f.TendermintApiClients[chain.Name] { + ibcChannelClientStateResponse, err, queryInfo := node.GetIbcConnectionClientState(ibcChannel.ConnectionHops[0]) + f.MetricsManager.LogTendermintQuery(chain.Name, queryInfo, QueryInfo.QueryTypeIbcConnectionClientState) + if err != nil { + f.Logger.Error().Err(err).Msg("Error fetching IBC client state") + continue + } + + ibcClientState = ibcChannelClientStateResponse + break + } + + if ibcClientState == nil { + f.Logger.Error().Msg("Could not connect to any nodes to get IBC client state") + return "", false + } + + f.Cache.Set(keyName, ibcClientState.ClientState.ChainId) + return ibcClientState.ClientState.ChainId, true +} + +func (f *DataFetcher) GetDenomTrace( + chain *configTypes.Chain, + denom string, +) (*transferTypes.DenomTrace, bool) { + denomSplit := strings.Split(denom, "/") + if len(denomSplit) != 2 || denomSplit[0] != transferTypes.DenomPrefix { + f.Logger.Error().Msg("Invalid IBC prefix provided") + return nil, false + } + + denomHash := denomSplit[1] + + keyName := chain.Name + "_denom_trace_" + denom + + if cachedEntry, cachedEntryPresent := f.Cache.Get(keyName); cachedEntryPresent { + if cachedEntryParsed, ok := cachedEntry.(*transferTypes.DenomTrace); ok { + return cachedEntryParsed, true + } + + f.Logger.Error().Msg("Could not convert cached staking params to *types.DenomTrace") + return nil, false + } + + for _, node := range f.TendermintApiClients[chain.Name] { + notCachedEntry, err, queryInfo := node.GetIbcDenomTrace(denomHash) + f.MetricsManager.LogTendermintQuery(chain.Name, queryInfo, QueryInfo.QueryTypeIbcDenomTrace) + + if err != nil { + f.Logger.Error().Err(err).Msg("Error fetching IBC denom trace") + continue + } + + f.Cache.Set(keyName, notCachedEntry) + return notCachedEntry, true + } + + f.Logger.Error().Msg("Could not connect to any nodes to get IBC denom trace") + return nil, false +} + func (f *DataFetcher) GetAliasManager() *alias_manager.AliasManager { return f.AliasManager } -func (f *DataFetcher) GetChain() *configTypes.Chain { - return f.Chain +func (f *DataFetcher) FindChainById( + chainID string, +) (*configTypes.Chain, bool) { + chain := f.Config.Chains.FindByChainID(chainID) + if chain == nil { + return nil, false + } + + return chain, true } diff --git a/pkg/messages/msg_acknowledgement.go b/pkg/messages/msg_acknowledgement.go index 27225a1..231e3ed 100644 --- a/pkg/messages/msg_acknowledgement.go +++ b/pkg/messages/msg_acknowledgement.go @@ -19,6 +19,8 @@ type MsgAcknowledgement struct { Sender configTypes.Link Receiver configTypes.Link Signer configTypes.Link + + Chain *configTypes.Chain } func ParseMsgAcknowledgement(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -37,6 +39,7 @@ func ParseMsgAcknowledgement(data []byte, chain *configTypes.Chain, height int64 Sender: chain.GetWalletLink(packetData.Sender), Receiver: configTypes.Link{Value: packetData.Receiver}, Signer: chain.GetWalletLink(parsedMessage.Signer), + Chain: chain, }, nil } @@ -45,12 +48,12 @@ func (m MsgAcknowledgement) Type() string { } func (m *MsgAcknowledgement) GetAdditionalData(fetcher types.DataFetcher) { - fetcher.PopulateAmount(m.Token) - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.Sender.Value); alias != "" { + fetcher.PopulateAmount(m.Chain, m.Token) + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Sender.Value); alias != "" { m.Sender.Title = alias } - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.Signer.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Signer.Value); alias != "" { m.Signer.Title = alias } } diff --git a/pkg/messages/msg_begin_redelegate.go b/pkg/messages/msg_begin_redelegate.go index 9c19356..a8862cc 100644 --- a/pkg/messages/msg_begin_redelegate.go +++ b/pkg/messages/msg_begin_redelegate.go @@ -18,6 +18,8 @@ type MsgBeginRedelegate struct { ValidatorSrcAddress configTypes.Link ValidatorDstAddress configTypes.Link Amount *amount.Amount + + Chain *configTypes.Chain } func ParseMsgBeginRedelegate(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -31,6 +33,7 @@ func ParseMsgBeginRedelegate(data []byte, chain *configTypes.Chain, height int64 ValidatorSrcAddress: chain.GetValidatorLink(parsedMessage.ValidatorSrcAddress), ValidatorDstAddress: chain.GetValidatorLink(parsedMessage.ValidatorDstAddress), Amount: amount.AmountFrom(parsedMessage.Amount), + Chain: chain, }, nil } @@ -39,16 +42,16 @@ func (m MsgBeginRedelegate) Type() string { } func (m *MsgBeginRedelegate) GetAdditionalData(fetcher types.DataFetcher) { - if validator, found := fetcher.GetValidator(m.ValidatorSrcAddress.Value); found { + if validator, found := fetcher.GetValidator(m.Chain, m.ValidatorSrcAddress.Value); found { m.ValidatorSrcAddress.Title = validator.Description.Moniker } - if validator, found := fetcher.GetValidator(m.ValidatorDstAddress.Value); found { + if validator, found := fetcher.GetValidator(m.Chain, m.ValidatorDstAddress.Value); found { m.ValidatorDstAddress.Title = validator.Description.Moniker } - fetcher.PopulateAmount(m.Amount) + fetcher.PopulateAmount(m.Chain, m.Amount) - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.DelegatorAddress.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.DelegatorAddress.Value); alias != "" { m.DelegatorAddress.Title = alias } } diff --git a/pkg/messages/msg_delegate.go b/pkg/messages/msg_delegate.go index a711b31..977f78a 100644 --- a/pkg/messages/msg_delegate.go +++ b/pkg/messages/msg_delegate.go @@ -17,6 +17,8 @@ type MsgDelegate struct { DelegatorAddress configTypes.Link ValidatorAddress configTypes.Link Amount *amount.Amount + + Chain *configTypes.Chain } func ParseMsgDelegate(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -29,6 +31,7 @@ func ParseMsgDelegate(data []byte, chain *configTypes.Chain, height int64) (type DelegatorAddress: chain.GetWalletLink(parsedMessage.DelegatorAddress), ValidatorAddress: chain.GetValidatorLink(parsedMessage.ValidatorAddress), Amount: amount.AmountFrom(parsedMessage.Amount), + Chain: chain, }, nil } @@ -37,14 +40,14 @@ func (m MsgDelegate) Type() string { } func (m *MsgDelegate) GetAdditionalData(fetcher types.DataFetcher) { - validator, found := fetcher.GetValidator(m.ValidatorAddress.Value) + validator, found := fetcher.GetValidator(m.Chain, m.ValidatorAddress.Value) if found { m.ValidatorAddress.Title = validator.Description.Moniker } - fetcher.PopulateAmount(m.Amount) + fetcher.PopulateAmount(m.Chain, m.Amount) - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.DelegatorAddress.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.DelegatorAddress.Value); alias != "" { m.DelegatorAddress.Title = alias } } diff --git a/pkg/messages/msg_exec.go b/pkg/messages/msg_exec.go index c110266..67458e1 100644 --- a/pkg/messages/msg_exec.go +++ b/pkg/messages/msg_exec.go @@ -17,6 +17,8 @@ type MsgExec struct { Grantee configTypes.Link RawMessages []*codecTypes.Any Messages []types.Message + + Chain *configTypes.Chain } func ParseMsgExec(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -29,6 +31,7 @@ func ParseMsgExec(data []byte, chain *configTypes.Chain, height int64) (types.Me Grantee: chain.GetWalletLink(parsedMessage.Grantee), Messages: make([]types.Message, 0), RawMessages: parsedMessage.Msgs, + Chain: chain, }, nil } @@ -37,7 +40,7 @@ func (m MsgExec) Type() string { } func (m *MsgExec) GetAdditionalData(fetcher types.DataFetcher) { - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.Grantee.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Grantee.Value); alias != "" { m.Grantee.Title = alias } diff --git a/pkg/messages/msg_grant.go b/pkg/messages/msg_grant.go index 9c5aebb..d0e28df 100644 --- a/pkg/messages/msg_grant.go +++ b/pkg/messages/msg_grant.go @@ -30,6 +30,8 @@ type MsgGrant struct { GrantType string Expiration *time.Time Authorization Authorization + + Chain *configTypes.Chain } func ParseStakeAuthorization(authorization *codecTypes.Any, chain *configTypes.Chain) (Authorization, error) { @@ -88,6 +90,7 @@ func ParseMsgGrant(data []byte, chain *configTypes.Chain, height int64) (types.M GrantType: parsedMessage.Grant.Authorization.TypeUrl, Expiration: parsedMessage.Grant.Expiration, Authorization: authorization, + Chain: chain, }, nil } @@ -96,11 +99,11 @@ func (m MsgGrant) Type() string { } func (m *MsgGrant) GetAdditionalData(fetcher types.DataFetcher) { - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.Grantee.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Grantee.Value); alias != "" { m.Grantee.Title = alias } - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.Granter.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Granter.Value); alias != "" { m.Granter.Title = alias } } diff --git a/pkg/messages/msg_multi_send.go b/pkg/messages/msg_multi_send.go index 9d6b1d2..7265404 100644 --- a/pkg/messages/msg_multi_send.go +++ b/pkg/messages/msg_multi_send.go @@ -22,6 +22,8 @@ type MultiSendEntry struct { type MsgMultiSend struct { Inputs []MultiSendEntry Outputs []MultiSendEntry + + Chain *configTypes.Chain } func ParseMsgMultiSend(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -43,6 +45,7 @@ func ParseMsgMultiSend(data []byte, chain *configTypes.Chain, height int64) (typ Amount: utils.Map(output.Coins, amount.AmountFrom), } }), + Chain: chain, }, nil } @@ -52,19 +55,19 @@ func (m MsgMultiSend) Type() string { func (m *MsgMultiSend) GetAdditionalData(fetcher types.DataFetcher) { for _, input := range m.Inputs { - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, input.Address.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, input.Address.Value); alias != "" { input.Address.Title = alias } - fetcher.PopulateAmounts(input.Amount) + fetcher.PopulateAmounts(m.Chain, input.Amount) } for _, output := range m.Outputs { - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, output.Address.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, output.Address.Value); alias != "" { output.Address.Title = alias } - fetcher.PopulateAmounts(output.Amount) + fetcher.PopulateAmounts(m.Chain, output.Amount) } } diff --git a/pkg/messages/msg_recv_packet.go b/pkg/messages/msg_recv_packet.go index 968337c..060477b 100644 --- a/pkg/messages/msg_recv_packet.go +++ b/pkg/messages/msg_recv_packet.go @@ -16,6 +16,8 @@ import ( type MsgRecvPacket struct { Signer configTypes.Link Packet types.Message + + Chain *configTypes.Chain } func ParseMsgRecvPacket(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -34,6 +36,7 @@ func ParseMsgRecvPacket(data []byte, chain *configTypes.Chain, height int64) (ty return &MsgRecvPacket{ Signer: chain.GetWalletLink(parsedMessage.Signer), Packet: parsedPacket, + Chain: chain, }, nil } @@ -42,7 +45,7 @@ func (m MsgRecvPacket) Type() string { } func (m *MsgRecvPacket) GetAdditionalData(fetcher types.DataFetcher) { - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.Signer.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Signer.Value); alias != "" { m.Signer.Title = alias } diff --git a/pkg/messages/msg_revoke.go b/pkg/messages/msg_revoke.go index eac0999..28b8030 100644 --- a/pkg/messages/msg_revoke.go +++ b/pkg/messages/msg_revoke.go @@ -17,6 +17,8 @@ type MsgRevoke struct { Grantee configTypes.Link MsgType string Authorization Authorization + + Chain *configTypes.Chain } func ParseMsgRevoke(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -29,6 +31,7 @@ func ParseMsgRevoke(data []byte, chain *configTypes.Chain, height int64) (types. Grantee: chain.GetWalletLink(parsedMessage.Grantee), Granter: chain.GetWalletLink(parsedMessage.Granter), MsgType: parsedMessage.MsgTypeUrl, + Chain: chain, }, nil } @@ -37,11 +40,11 @@ func (m MsgRevoke) Type() string { } func (m *MsgRevoke) GetAdditionalData(fetcher types.DataFetcher) { - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.Grantee.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Grantee.Value); alias != "" { m.Grantee.Title = alias } - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.Granter.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Granter.Value); alias != "" { m.Granter.Title = alias } } diff --git a/pkg/messages/msg_send.go b/pkg/messages/msg_send.go index d94fb88..5cf6f15 100644 --- a/pkg/messages/msg_send.go +++ b/pkg/messages/msg_send.go @@ -18,6 +18,8 @@ type MsgSend struct { From configTypes.Link To configTypes.Link Amount amount.Amounts + + Chain *configTypes.Chain } func ParseMsgSend(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -30,6 +32,7 @@ func ParseMsgSend(data []byte, chain *configTypes.Chain, height int64) (types.Me From: chain.GetWalletLink(parsedMessage.FromAddress), To: chain.GetWalletLink(parsedMessage.ToAddress), Amount: utils.Map(parsedMessage.Amount, amount.AmountFrom), + Chain: chain, }, nil } @@ -38,13 +41,13 @@ func (m MsgSend) Type() string { } func (m *MsgSend) GetAdditionalData(fetcher types.DataFetcher) { - fetcher.PopulateAmounts(m.Amount) + fetcher.PopulateAmounts(m.Chain, m.Amount) - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.From.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.From.Value); alias != "" { m.From.Title = alias } - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.To.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.To.Value); alias != "" { m.To.Title = alias } } diff --git a/pkg/messages/msg_set_withdraw_address.go b/pkg/messages/msg_set_withdraw_address.go index 7d86110..50a3455 100644 --- a/pkg/messages/msg_set_withdraw_address.go +++ b/pkg/messages/msg_set_withdraw_address.go @@ -15,6 +15,8 @@ import ( type MsgSetWithdrawAddress struct { DelegatorAddress configTypes.Link WithdrawAddress configTypes.Link + + Chain *configTypes.Chain } func ParseMsgSetWithdrawAddress(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -26,6 +28,7 @@ func ParseMsgSetWithdrawAddress(data []byte, chain *configTypes.Chain, height in return &MsgSetWithdrawAddress{ DelegatorAddress: chain.GetWalletLink(parsedMessage.DelegatorAddress), WithdrawAddress: chain.GetValidatorLink(parsedMessage.WithdrawAddress), + Chain: chain, }, nil } @@ -34,11 +37,11 @@ func (m MsgSetWithdrawAddress) Type() string { } func (m *MsgSetWithdrawAddress) GetAdditionalData(fetcher types.DataFetcher) { - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.DelegatorAddress.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.DelegatorAddress.Value); alias != "" { m.DelegatorAddress.Title = alias } - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.WithdrawAddress.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.WithdrawAddress.Value); alias != "" { m.WithdrawAddress.Title = alias } } diff --git a/pkg/messages/msg_timeout.go b/pkg/messages/msg_timeout.go index 54ddd62..771f803 100644 --- a/pkg/messages/msg_timeout.go +++ b/pkg/messages/msg_timeout.go @@ -16,6 +16,8 @@ import ( type MsgTimeout struct { Signer configTypes.Link Packet types.Message + + Chain *configTypes.Chain } func ParseMsgTimeout(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -24,16 +26,17 @@ func ParseMsgTimeout(data []byte, chain *configTypes.Chain, height int64) (types return nil, err } - packet, err := packet.ParsePacket(parsedMessage.Packet, chain) + parsedPacket, err := packet.ParsePacket(parsedMessage.Packet, chain) if err != nil { return nil, err - } else if packet == nil { + } else if parsedPacket == nil { return nil, nil } return &MsgTimeout{ Signer: chain.GetWalletLink(parsedMessage.Signer), - Packet: packet, + Packet: parsedPacket, + Chain: chain, }, nil } @@ -42,7 +45,7 @@ func (m MsgTimeout) Type() string { } func (m *MsgTimeout) GetAdditionalData(fetcher types.DataFetcher) { - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.Signer.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Signer.Value); alias != "" { m.Signer.Title = alias } diff --git a/pkg/messages/msg_transfer.go b/pkg/messages/msg_transfer.go index a52a2c9..e01dce4 100644 --- a/pkg/messages/msg_transfer.go +++ b/pkg/messages/msg_transfer.go @@ -17,6 +17,11 @@ type MsgTransfer struct { Token *amount.Amount Sender configTypes.Link Receiver configTypes.Link + + SrcChannel string + SrcPort string + + Chain *configTypes.Chain } func ParseMsgTransfer(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -26,9 +31,12 @@ func ParseMsgTransfer(data []byte, chain *configTypes.Chain, height int64) (type } return &MsgTransfer{ - Token: amount.AmountFrom(parsedMessage.Token), - Sender: chain.GetWalletLink(parsedMessage.Sender), - Receiver: configTypes.Link{Value: parsedMessage.Receiver}, + Token: amount.AmountFrom(parsedMessage.Token), + Sender: chain.GetWalletLink(parsedMessage.Sender), + Receiver: configTypes.Link{Value: parsedMessage.Receiver}, + SrcChannel: parsedMessage.SourceChannel, + SrcPort: parsedMessage.SourcePort, + Chain: chain, }, nil } @@ -37,13 +45,66 @@ func (m MsgTransfer) Type() string { } func (m *MsgTransfer) GetAdditionalData(fetcher types.DataFetcher) { - fetcher.PopulateAmount(m.Token) + m.FetchRemoteChainData(fetcher) + + fetcher.PopulateAmount(m.Chain, m.Token) - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.Sender.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Sender.Value); alias != "" { m.Sender.Title = alias } } +func (m *MsgTransfer) FetchRemoteChainData(fetcher types.DataFetcher) { + // p.Receiver is always someone from the remote chain, so we need to fetch the data + // from cross-chain. + // p.Sender is on native chain, so we can use p.Chain to generate links + // and denoms and prices. + + // If it's an IBC token (like, withdraw on Osmosis) - we need to figure out what + // the original denom is, to convert it, and also take the remote chain for links + // generation. + // If it's a native token - just take the denom from the current chain, but also fetch + // the remote chain for links generation. + var trace ibcTypes.DenomTrace + if m.Token.IsIbcToken() { + externalTrace, found := fetcher.GetDenomTrace(m.Chain, m.Token.Denom) + if !found { + return + } + trace = *externalTrace + } else { + trace = ibcTypes.ParseDenomTrace(m.Token.Denom) + } + + m.Token.Denom = trace.BaseDenom + m.Token.BaseDenom = trace.BaseDenom + + if trace.IsNativeDenom() { + fetcher.PopulateAmount(m.Chain, m.Token) + } + + originalChainID, fetched := fetcher.GetIbcRemoteChainID(m.Chain, m.SrcChannel, m.SrcPort) + + if !fetched { + return + } + + chain, found := fetcher.FindChainById(originalChainID) + if !found { + return + } + + if !trace.IsNativeDenom() { + fetcher.PopulateAmount(chain, m.Token) + } + + m.Receiver = chain.GetWalletLink(m.Receiver.Value) + + if alias := fetcher.GetAliasManager().Get(chain.Name, m.Receiver.Value); alias != "" { + m.Receiver.Title = alias + } +} + func (m *MsgTransfer) GetValues() event.EventValues { return []event.EventValue{ event.From(cosmosTypes.EventTypeMessage, cosmosTypes.AttributeKeyAction, m.Type()), diff --git a/pkg/messages/msg_undelegate.go b/pkg/messages/msg_undelegate.go index 55906c1..bd9e8b6 100644 --- a/pkg/messages/msg_undelegate.go +++ b/pkg/messages/msg_undelegate.go @@ -20,6 +20,8 @@ type MsgUndelegate struct { ValidatorAddress configTypes.Link UndelegateFinishTime time.Time Amount *amount.Amount + + Chain *configTypes.Chain } func ParseMsgUndelegate(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -32,6 +34,7 @@ func ParseMsgUndelegate(data []byte, chain *configTypes.Chain, height int64) (ty DelegatorAddress: chain.GetWalletLink(parsedMessage.DelegatorAddress), ValidatorAddress: chain.GetValidatorLink(parsedMessage.ValidatorAddress), Amount: amount.AmountFrom(parsedMessage.Amount), + Chain: chain, }, nil } @@ -40,17 +43,17 @@ func (m MsgUndelegate) Type() string { } func (m *MsgUndelegate) GetAdditionalData(fetcher types.DataFetcher) { - if validator, found := fetcher.GetValidator(m.ValidatorAddress.Value); found { + if validator, found := fetcher.GetValidator(m.Chain, m.ValidatorAddress.Value); found { m.ValidatorAddress.Title = validator.Description.Moniker } - if stakingParams, found := fetcher.GetStakingParams(); found { + if stakingParams, found := fetcher.GetStakingParams(m.Chain); found { m.UndelegateFinishTime = time.Now().Add(stakingParams.UnbondingTime.Duration) } - fetcher.PopulateAmount(m.Amount) + fetcher.PopulateAmount(m.Chain, m.Amount) - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.DelegatorAddress.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.DelegatorAddress.Value); alias != "" { m.DelegatorAddress.Title = alias } } diff --git a/pkg/messages/msg_update_client.go b/pkg/messages/msg_update_client.go index 4dd652f..367f704 100644 --- a/pkg/messages/msg_update_client.go +++ b/pkg/messages/msg_update_client.go @@ -15,6 +15,8 @@ import ( type MsgUpdateClient struct { ClientID string Signer configTypes.Link + + Chain *configTypes.Chain } func ParseMsgUpdateClient(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -26,6 +28,7 @@ func ParseMsgUpdateClient(data []byte, chain *configTypes.Chain, height int64) ( return &MsgUpdateClient{ ClientID: parsedMessage.ClientId, Signer: chain.GetWalletLink(parsedMessage.Signer), + Chain: chain, }, nil } @@ -34,7 +37,7 @@ func (m MsgUpdateClient) Type() string { } func (m *MsgUpdateClient) GetAdditionalData(fetcher types.DataFetcher) { - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.Signer.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Signer.Value); alias != "" { m.Signer.Title = alias } } diff --git a/pkg/messages/msg_vote.go b/pkg/messages/msg_vote.go index fd229c0..8dba1a0 100644 --- a/pkg/messages/msg_vote.go +++ b/pkg/messages/msg_vote.go @@ -22,6 +22,8 @@ type MsgVote struct { ProposalID configTypes.Link Proposal *responses.Proposal Option cosmosGovTypes.VoteOption + + Chain *configTypes.Chain } func ParseMsgVote(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -34,6 +36,7 @@ func ParseMsgVote(data []byte, chain *configTypes.Chain, height int64) (types.Me Voter: chain.GetWalletLink(parsedMessage.Voter), ProposalID: chain.GetProposalLink(strconv.FormatUint(parsedMessage.ProposalId, 10)), Option: parsedMessage.Option, + Chain: chain, }, nil } @@ -42,7 +45,7 @@ func (m MsgVote) Type() string { } func (m *MsgVote) GetAdditionalData(fetcher types.DataFetcher) { - proposal, found := fetcher.GetProposal(m.ProposalID.Value) + proposal, found := fetcher.GetProposal(m.Chain, m.ProposalID.Value) if found { m.Proposal = proposal m.ProposalID.Title = fmt.Sprintf("#%s: %s", m.ProposalID.Value, proposal.Content.Title) @@ -50,7 +53,7 @@ func (m *MsgVote) GetAdditionalData(fetcher types.DataFetcher) { m.ProposalID.Title = fmt.Sprintf("#%s", m.ProposalID.Value) } - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.Voter.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.Voter.Value); alias != "" { m.Voter.Title = alias } } diff --git a/pkg/messages/msg_withdraw_delegator_reward.go b/pkg/messages/msg_withdraw_delegator_reward.go index 6bcd4a8..be3d100 100644 --- a/pkg/messages/msg_withdraw_delegator_reward.go +++ b/pkg/messages/msg_withdraw_delegator_reward.go @@ -18,6 +18,8 @@ type MsgWithdrawDelegatorReward struct { ValidatorAddress configTypes.Link Height int64 Amount []*amount.Amount + + Chain *configTypes.Chain } func ParseMsgWithdrawDelegatorReward(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -30,6 +32,7 @@ func ParseMsgWithdrawDelegatorReward(data []byte, chain *configTypes.Chain, heig DelegatorAddress: chain.GetWalletLink(parsedMessage.DelegatorAddress), ValidatorAddress: chain.GetValidatorLink(parsedMessage.ValidatorAddress), Height: height, + Chain: chain, }, nil } @@ -39,6 +42,7 @@ func (m MsgWithdrawDelegatorReward) Type() string { func (m *MsgWithdrawDelegatorReward) GetAdditionalData(fetcher types.DataFetcher) { rewards, found := fetcher.GetRewardsAtBlock( + m.Chain, m.DelegatorAddress.Value, m.ValidatorAddress.Value, m.Height, @@ -50,14 +54,14 @@ func (m *MsgWithdrawDelegatorReward) GetAdditionalData(fetcher types.DataFetcher m.Amount[index] = amount.AmountFromString(reward.Amount, reward.Denom) } - fetcher.PopulateAmounts(m.Amount) + fetcher.PopulateAmounts(m.Chain, m.Amount) } - if validator, found := fetcher.GetValidator(m.ValidatorAddress.Value); found { + if validator, found := fetcher.GetValidator(m.Chain, m.ValidatorAddress.Value); found { m.ValidatorAddress.Title = validator.Description.Moniker } - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, m.DelegatorAddress.Value); alias != "" { + if alias := fetcher.GetAliasManager().Get(m.Chain.Name, m.DelegatorAddress.Value); alias != "" { m.DelegatorAddress.Title = alias } } diff --git a/pkg/messages/msg_withdraw_validator_commission.go b/pkg/messages/msg_withdraw_validator_commission.go index 3f51319..3c766c6 100644 --- a/pkg/messages/msg_withdraw_validator_commission.go +++ b/pkg/messages/msg_withdraw_validator_commission.go @@ -17,6 +17,8 @@ type MsgWithdrawValidatorCommission struct { ValidatorAddress configTypes.Link Height int64 Amount []*amount.Amount + + Chain *configTypes.Chain } func ParseMsgWithdrawValidatorCommission(data []byte, chain *configTypes.Chain, height int64) (types.Message, error) { @@ -28,6 +30,7 @@ func ParseMsgWithdrawValidatorCommission(data []byte, chain *configTypes.Chain, return &MsgWithdrawValidatorCommission{ ValidatorAddress: chain.GetValidatorLink(parsedMessage.ValidatorAddress), Height: height, + Chain: chain, }, nil } @@ -37,6 +40,7 @@ func (m MsgWithdrawValidatorCommission) Type() string { func (m *MsgWithdrawValidatorCommission) GetAdditionalData(fetcher types.DataFetcher) { rewards, found := fetcher.GetCommissionAtBlock( + m.Chain, m.ValidatorAddress.Value, m.Height, ) @@ -47,10 +51,10 @@ func (m *MsgWithdrawValidatorCommission) GetAdditionalData(fetcher types.DataFet m.Amount[index] = amount.AmountFromString(reward.Amount, reward.Denom) } - fetcher.PopulateAmounts(m.Amount) + fetcher.PopulateAmounts(m.Chain, m.Amount) } - if validator, found := fetcher.GetValidator(m.ValidatorAddress.Value); found { + if validator, found := fetcher.GetValidator(m.Chain, m.ValidatorAddress.Value); found { m.ValidatorAddress.Title = validator.Description.Moniker } } diff --git a/pkg/messages/packet/fungible_token_packet.go b/pkg/messages/packet/fungible_token_packet.go index 5750387..9ebf4fe 100644 --- a/pkg/messages/packet/fungible_token_packet.go +++ b/pkg/messages/packet/fungible_token_packet.go @@ -8,6 +8,7 @@ import ( cosmosTypes "github.com/cosmos/cosmos-sdk/types" cosmosBankTypes "github.com/cosmos/cosmos-sdk/x/bank/types" + ibcChannelTypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" codecTypes "github.com/cosmos/cosmos-sdk/codec/types" ibcTypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" @@ -17,16 +18,29 @@ type FungibleTokenPacket struct { Token *amount.Amount Sender configTypes.Link Receiver configTypes.Link + + SrcPort string + SrcChannel string + DstPort string + DstChannel string + + Chain *configTypes.Chain } func ParseFungibleTokenPacket( packetData ibcTypes.FungibleTokenPacketData, + packet ibcChannelTypes.Packet, chain *configTypes.Chain, ) types.Message { return &FungibleTokenPacket{ - Token: amount.AmountFromString(packetData.Amount, packetData.Denom), - Sender: configTypes.Link{Value: packetData.Sender}, - Receiver: chain.GetWalletLink(packetData.Receiver), + Token: amount.AmountFromString(packetData.Amount, packetData.Denom), + Sender: configTypes.Link{Value: packetData.Sender}, + Receiver: chain.GetWalletLink(packetData.Receiver), + SrcPort: packet.SourcePort, + SrcChannel: packet.SourceChannel, + DstPort: packet.DestinationPort, + DstChannel: packet.DestinationChannel, + Chain: chain, } } @@ -35,16 +49,42 @@ func (p FungibleTokenPacket) Type() string { } func (p *FungibleTokenPacket) GetAdditionalData(fetcher types.DataFetcher) { + p.FetchRemoteChainData(fetcher) + + if alias := fetcher.GetAliasManager().Get(p.Chain.Name, p.Receiver.Value); alias != "" { + p.Receiver.Title = alias + } +} + +func (p *FungibleTokenPacket) FetchRemoteChainData(fetcher types.DataFetcher) { + // p.Sender is always someone from the remote chain, so we need to fetch the data + // from cross-chain. + // p.Receiver is on native chain, so we can use p.Chain to generate links + // and denoms and prices. + trace := ibcTypes.ParseDenomTrace(p.Token.Denom) p.Token.Denom = trace.BaseDenom + p.Token.BaseDenom = trace.BaseDenom - fetcher.PopulateAmount(p.Token) + if !trace.IsNativeDenom() { + return + } - if alias := fetcher.GetAliasManager().Get(fetcher.GetChain().Name, p.Receiver.Value); alias != "" { - p.Receiver.Title = alias + originalChainID, fetched := fetcher.GetIbcRemoteChainID(p.Chain, p.DstChannel, p.DstPort) + + if !fetched { + return } -} + if chain, found := fetcher.FindChainById(originalChainID); found { + fetcher.PopulateAmount(chain, p.Token) + p.Sender = chain.GetWalletLink(p.Sender.Value) + + if alias := fetcher.GetAliasManager().Get(chain.Name, p.Receiver.Value); alias != "" { + p.Sender.Title = alias + } + } +} func (p *FungibleTokenPacket) GetValues() event.EventValues { return []event.EventValue{ event.From(ibcTypes.EventTypePacket, cosmosBankTypes.AttributeKeyReceiver, p.Receiver.Value), diff --git a/pkg/messages/packet/interchain_accounts_packet.go b/pkg/messages/packet/interchain_accounts_packet.go index 71a661c..71a3106 100644 --- a/pkg/messages/packet/interchain_accounts_packet.go +++ b/pkg/messages/packet/interchain_accounts_packet.go @@ -20,6 +20,8 @@ type InterchainAccountsPacket struct { TxMessagesCount int TxRawMessages []*codecTypes.Any TxMessages []types.Message + + Chain *configTypes.Chain } func ParseInterchainAccountsPacket( @@ -28,13 +30,13 @@ func ParseInterchainAccountsPacket( ) (types.Message, error) { cdc := codec.NewLegacyAmino() interfaceRegistry := codecTypes.NewInterfaceRegistry() - codec := codec.NewProtoCodec(interfaceRegistry) + protoCodec := codec.NewProtoCodec(interfaceRegistry) std.RegisterLegacyAminoCodec(cdc) std.RegisterInterfaces(interfaceRegistry) var cosmosTx icaTypes.CosmosTx - if err := codec.Unmarshal(packetData.Data, &cosmosTx); err != nil { + if err := protoCodec.Unmarshal(packetData.Data, &cosmosTx); err != nil { return nil, err } @@ -44,6 +46,7 @@ func ParseInterchainAccountsPacket( TxMessagesCount: len(cosmosTx.Messages), TxRawMessages: cosmosTx.Messages, TxMessages: make([]types.Message, 0), + Chain: chain, }, nil } diff --git a/pkg/messages/packet/packet.go b/pkg/messages/packet/packet.go index 9199add..adde7dc 100644 --- a/pkg/messages/packet/packet.go +++ b/pkg/messages/packet/packet.go @@ -9,17 +9,17 @@ import ( ibcChannelTypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types" ) -func ParsePacket(packet ibcChannelTypes.Packet, chain *configTypes.Chain) (types.Message, error) { +func ParsePacket(message ibcChannelTypes.Packet, chain *configTypes.Chain) (types.Message, error) { // Fungible token transfer var fungiblePacketData ibcTypes.FungibleTokenPacketData - err := ibcTypes.ModuleCdc.UnmarshalJSON(packet.Data, &fungiblePacketData) + err := ibcTypes.ModuleCdc.UnmarshalJSON(message.Data, &fungiblePacketData) if err == nil { - return ParseFungibleTokenPacket(fungiblePacketData, chain), nil + return ParseFungibleTokenPacket(fungiblePacketData, message, chain), nil } // ICA packet var icaPacketData icaTypes.InterchainAccountPacketData - err = ibcTypes.ModuleCdc.UnmarshalJSON(packet.Data, &icaPacketData) + err = ibcTypes.ModuleCdc.UnmarshalJSON(message.Data, &icaPacketData) if err == nil { return ParseInterchainAccountsPacket(icaPacketData, chain) } diff --git a/pkg/tendermint/api/api_client.go b/pkg/tendermint/api/api_client.go index adbe0cc..359cc75 100644 --- a/pkg/tendermint/api/api_client.go +++ b/pkg/tendermint/api/api_client.go @@ -8,6 +8,8 @@ import ( "strconv" "time" + "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + configTypes "main/pkg/config/types" "main/pkg/types/responses" @@ -114,6 +116,49 @@ func (c *TendermintApiClient) GetStakingParams() (*responses.StakingParams, erro return &response.Params, nil, queryInfo } +func (c *TendermintApiClient) GetIbcChannel( + channel string, + port string, +) (*responses.IbcChannel, error, query_info.QueryInfo) { + url := fmt.Sprintf("/ibc/core/channel/v1/channels/%s/ports/%s", channel, port) + + var response *responses.IbcChannelResponse + err, queryInfo := c.Get(url, &response) + if err != nil { + return nil, err, queryInfo + } + + return &response.Channel, nil, queryInfo +} + +func (c *TendermintApiClient) GetIbcConnectionClientState( + connectionID string, +) (*responses.IbcIdentifiedClientState, error, query_info.QueryInfo) { + url := fmt.Sprintf("/ibc/core/connection/v1/connections/%s/client_state", connectionID) + + var response *responses.IbcClientStateResponse + err, queryInfo := c.Get(url, &response) + if err != nil { + return nil, err, queryInfo + } + + return &response.IdentifiedClientState, nil, queryInfo +} + +func (c *TendermintApiClient) GetIbcDenomTrace( + hash string, +) (*types.DenomTrace, error, query_info.QueryInfo) { + url := fmt.Sprintf("/ibc/apps/transfer/v1/denom_traces/%s", hash) + + var response *responses.IbcDenomTraceResponse + err, queryInfo := c.Get(url, &response) + if err != nil { + return nil, err, queryInfo + } + + return &response.DenomTrace, nil, queryInfo +} + func (c *TendermintApiClient) Get(url string, target interface{}) (error, query_info.QueryInfo) { return c.GetWithHeaders(url, target, map[string]string{}) } diff --git a/pkg/tendermint/ws/websocket_client.go b/pkg/tendermint/ws/websocket_client.go index a0502e9..720a5b6 100644 --- a/pkg/tendermint/ws/websocket_client.go +++ b/pkg/tendermint/ws/websocket_client.go @@ -21,7 +21,7 @@ import ( type TendermintWebsocketClient struct { Logger zerolog.Logger - Chain configTypes.Chain + Chain *configTypes.Chain MetricsManager *metricsPkg.Manager URL string Queries []query.Query @@ -48,7 +48,7 @@ func NewTendermintClient( Logger(), MetricsManager: metricsManager, URL: url, - Chain: *chain, + Chain: chain, Queries: chain.Queries, Active: false, Channel: make(chan types.Report), @@ -146,11 +146,11 @@ func (t *TendermintWebsocketClient) Resubscribe() { func (t *TendermintWebsocketClient) SubscribeToUpdates() { t.Logger.Trace().Msg("Subscribing to updates...") - for _, query := range t.Queries { - if err := t.Client.Subscribe(context.Background(), query.String()); err != nil { - t.Logger.Error().Err(err).Str("query", query.String()).Msg("Failed to subscribe to query") + for _, nodeQuery := range t.Queries { + if err := t.Client.Subscribe(context.Background(), nodeQuery.String()); err != nil { + t.Logger.Error().Err(err).Str("query", nodeQuery.String()).Msg("Failed to subscribe to query") } else { - t.Logger.Info().Str("query", query.String()).Msg("Listening for incoming transactions") + t.Logger.Info().Str("query", nodeQuery.String()).Msg("Listening for incoming transactions") } } } diff --git a/pkg/types/amount/amount.go b/pkg/types/amount/amount.go index bf35d0a..c84d9db 100644 --- a/pkg/types/amount/amount.go +++ b/pkg/types/amount/amount.go @@ -7,6 +7,8 @@ import ( "math/big" "strings" + transferTypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + cosmosTypes "github.com/cosmos/cosmos-sdk/types" ) @@ -56,6 +58,11 @@ func (a Amount) String() string { return fmt.Sprintf("%d%s", value, a.Denom) } +func (a Amount) IsIbcToken() bool { + denomSplit := strings.Split(a.Denom, "/") + return len(denomSplit) == 2 && denomSplit[0] == transferTypes.DenomPrefix +} + type Amounts []*Amount func (a Amounts) String() string { diff --git a/pkg/types/data_fetcher.go b/pkg/types/data_fetcher.go index ba19ca0..097bfd3 100644 --- a/pkg/types/data_fetcher.go +++ b/pkg/types/data_fetcher.go @@ -6,27 +6,37 @@ import ( priceFetchers "main/pkg/price_fetchers" "main/pkg/types/amount" "main/pkg/types/responses" + + transferTypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" ) -// This interface is only here to avoid a cyclic dependencyL +// This interface is only here to avoid a cyclic dependency // DataFetcher -> MetricsManager -> types -> DataFetcher. + type DataFetcher interface { GetPriceFetcher(info *configTypes.DenomInfo) priceFetchers.PriceFetcher - PopulateAmount(amount *amount.Amount) - PopulateAmounts(amount amount.Amounts) + PopulateAmount(chain *configTypes.Chain, amount *amount.Amount) + PopulateAmounts(chain *configTypes.Chain, amount amount.Amounts) - GetValidator(address string) (*responses.Validator, bool) + GetValidator(chain *configTypes.Chain, address string) (*responses.Validator, bool) GetRewardsAtBlock( + chain *configTypes.Chain, delegator string, validator string, block int64, ) ([]responses.Reward, bool) GetCommissionAtBlock( + chain *configTypes.Chain, validator string, block int64, ) ([]responses.Commission, bool) - GetProposal(id string) (*responses.Proposal, bool) - GetStakingParams() (*responses.StakingParams, bool) + GetProposal(chain *configTypes.Chain, id string) (*responses.Proposal, bool) + GetStakingParams(chain *configTypes.Chain) (*responses.StakingParams, bool) GetAliasManager() *alias_manager.AliasManager - GetChain() *configTypes.Chain + GetIbcRemoteChainID(chain *configTypes.Chain, channel, port string) (string, bool) + FindChainById(chainID string) (*configTypes.Chain, bool) + GetDenomTrace( + chain *configTypes.Chain, + denom string, + ) (*transferTypes.DenomTrace, bool) } diff --git a/pkg/types/query_info/query_info.go b/pkg/types/query_info/query_info.go index cffeb24..564e76b 100644 --- a/pkg/types/query_info/query_info.go +++ b/pkg/types/query_info/query_info.go @@ -5,11 +5,14 @@ import "time" type QueryType string const ( - QueryTypeRewards QueryType = "rewards" - QueryTypeCommission QueryType = "commission" - QueryTypeProposal QueryType = "proposal" - QueryTypeStakingParams QueryType = "staking_params" - QueryTypeValidator QueryType = "validator" + QueryTypeRewards QueryType = "rewards" + QueryTypeCommission QueryType = "commission" + QueryTypeProposal QueryType = "proposal" + QueryTypeStakingParams QueryType = "staking_params" + QueryTypeValidator QueryType = "validator" + QueryTypeIbcChannel QueryType = "ibc_channel" + QueryTypeIbcConnectionClientState QueryType = "ibc_connection_client_state" + QueryTypeIbcDenomTrace QueryType = "ibc_denom_trace" ) func GetQueryTypes() []QueryType { diff --git a/pkg/types/report.go b/pkg/types/report.go index 6862ea3..6aa523e 100644 --- a/pkg/types/report.go +++ b/pkg/types/report.go @@ -5,7 +5,7 @@ import ( ) type Report struct { - Chain types.Chain + Chain *types.Chain Subscription *types.Subscription ChainSubscription *types.ChainSubscription Node string diff --git a/pkg/types/responses/ibc_channel.go b/pkg/types/responses/ibc_channel.go new file mode 100644 index 0000000..b5e3035 --- /dev/null +++ b/pkg/types/responses/ibc_channel.go @@ -0,0 +1,9 @@ +package responses + +type IbcChannelResponse struct { + Channel IbcChannel `json:"channel"` +} + +type IbcChannel struct { + ConnectionHops []string `json:"connection_hops"` +} diff --git a/pkg/types/responses/ibc_client_state.go b/pkg/types/responses/ibc_client_state.go new file mode 100644 index 0000000..7425d19 --- /dev/null +++ b/pkg/types/responses/ibc_client_state.go @@ -0,0 +1,13 @@ +package responses + +type IbcClientStateResponse struct { + IdentifiedClientState IbcIdentifiedClientState `json:"identified_client_state"` +} + +type IbcIdentifiedClientState struct { + ClientState IbcClientState `json:"client_state"` +} + +type IbcClientState struct { + ChainId string `json:"chain_id"` +} diff --git a/pkg/types/responses/ibc_denom_trace.go b/pkg/types/responses/ibc_denom_trace.go new file mode 100644 index 0000000..e6b0e2b --- /dev/null +++ b/pkg/types/responses/ibc_denom_trace.go @@ -0,0 +1,7 @@ +package responses + +import "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + +type IbcDenomTraceResponse struct { + DenomTrace types.DenomTrace `json:"denom_trace"` +} diff --git a/templates/telegram/FungibleTokenPacket.html b/templates/telegram/FungibleTokenPacket.html index 910a55e..094f9b6 100644 --- a/templates/telegram/FungibleTokenPacket.html +++ b/templates/telegram/FungibleTokenPacket.html @@ -1,3 +1,3 @@ Sender: {{ SerializeLink .Sender }} Receiver: {{ SerializeLink .Receiver }} -Amount: {{ SerializeAmount .Token }} +Amount: {{ SerializeAmount .Token -}}