diff --git a/cmd/feegrant.go b/cmd/feegrant.go index 3d0c86421..edb66d800 100644 --- a/cmd/feegrant.go +++ b/cmd/feegrant.go @@ -26,6 +26,7 @@ func feegrantConfigureBaseCmd(a *appState) *cobra.Command { func feegrantConfigureBasicCmd(a *appState) *cobra.Command { var numGrantees int var update bool + var delete bool var updateGrantees bool var grantees []string @@ -61,6 +62,19 @@ func feegrantConfigureBasicCmd(a *appState) *cobra.Command { return fmt.Errorf("could not get granter key from '%s'", granterKeyOrAddr) } + if delete { + fmt.Printf("Deleting %s feegrant configuration\n", chain) + + cfgErr := a.performConfigLockingOperation(cmd.Context(), func() error { + chain := a.config.Chains[chain] + oldProv := chain.ChainProvider.(*cosmos.CosmosProvider) + oldProv.PCfg.FeeGrants = nil + return nil + }) + cobra.CheckErr(cfgErr) + return nil + } + if prov.PCfg.FeeGrants != nil && granterKey != prov.PCfg.FeeGrants.GranterKey && !update { return fmt.Errorf("you specified granter '%s' which is different than configured feegranter '%s', but you did not specify the --overwrite-granter flag", granterKeyOrAddr, prov.PCfg.FeeGrants.GranterKey) } else if prov.PCfg.FeeGrants != nil && granterKey != prov.PCfg.FeeGrants.GranterKey && update { @@ -126,11 +140,13 @@ func feegrantConfigureBasicCmd(a *appState) *cobra.Command { return nil }, } + + cmd.Flags().BoolVar(&delete, "delete", false, "delete the feegrant configuration") cmd.Flags().BoolVar(&update, "overwrite-granter", false, "allow overwriting the existing granter") cmd.Flags().BoolVar(&updateGrantees, "overwrite-grantees", false, "allow overwriting existing grantees") cmd.Flags().IntVar(&numGrantees, "num-grantees", 10, "number of grantees that will be feegranted with basic allowances") - cmd.Flags().StringSliceVar(&grantees, "grantees", []string{}, "comma separated list of grantee key names (keys are created if they do not exist)") - cmd.MarkFlagsMutuallyExclusive("num-grantees", "grantees") + cmd.Flags().StringSliceVar(&grantees, "grantees", nil, "comma separated list of grantee key names (keys are created if they do not exist)") + cmd.MarkFlagsMutuallyExclusive("num-grantees", "grantees", "delete") memoFlag(a.viper, cmd) return cmd diff --git a/docs/advanced_usage.md b/docs/advanced_usage.md index 1b6c37595..bcfd1d2ac 100644 --- a/docs/advanced_usage.md +++ b/docs/advanced_usage.md @@ -51,7 +51,33 @@ Use cases for configuring the `--time-threshold` flag: \* It is not mandatory for relayers to include the `MsgUpdateClient` when relaying packets, however most, if not all relayers currently do. ---- +## Feegrants + +Feegrant configurations can be applied to each chain in the relayer. Note that Osmosis does not support Feegrants. + + - When feegrants are enabled, TXs will be signed in round robin by the grantees. + - Feegrants reduce sequencing error rates by using many signing addresses instead of a single signer, especially when broadcast-mode is set to single. + - Feegrants are especially useful when relaying on multiple paths with the same wallet. + - Funds are held on a single address, the "granter". + +For example, configure feegrants for Kujira: +- `rly chains configure feegrant basicallowance kujira default --num-grantees 10` +- Note: above, `default` is the key that will need to contain funds (the granter) +- 10 grantees will be configured, so those 10 address will sign TXs in round robin order. + + +You may also choose to specify the exact names of your grantees: +- `rly chains configure feegrant basicallowance kujira default --grantees "kuji1,kuji2,kuji3"` + +Rerunning the feegrant command will simply confirm your configuration is correct, e.g. "Valid grant found for granter `addr` and grantee `addr2`" but will not create additional TXs on chain. Rerunning the feegrant command can therefore be a good way to check what addresses exist. +To remove the feegrant configuration: +- `rly chains configure feegrant basicallowance kujira --delete` + + + + +--- + [<-- Create Path Across Chains](create-path-across-chain.md) - [Troubleshooting -->](./troubleshooting.md) diff --git a/interchaintest/acc_cache_test.go b/interchaintest/acc_cache_test.go index a2d0fbbc2..3c255bbaa 100644 --- a/interchaintest/acc_cache_test.go +++ b/interchaintest/acc_cache_test.go @@ -4,7 +4,6 @@ import ( "testing" "github.com/cometbft/cometbft/crypto/ed25519" - "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" ) @@ -14,7 +13,7 @@ import ( // This will cause the AccAddress.String() to print out unexpected prefixes. // If this function fails you are on an unsafe SDK version that should NOT be used with the relayer. func TestAccCacheBugfix(t *testing.T) { - types.SetAddrCacheEnabled(false) + sdk.SetAddrCacheEnabled(false) // Use a random key priv := ed25519.GenPrivKey() diff --git a/interchaintest/feegrant_test.go b/interchaintest/feegrant_test.go index d120c15b5..6c35f2793 100644 --- a/interchaintest/feegrant_test.go +++ b/interchaintest/feegrant_test.go @@ -93,429 +93,444 @@ func TestRelayerFeeGrant(t *testing.T) { // GasAdjustment: 1.3, // }} - // Chain Factory - cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{ - {Name: "gaia", ChainName: "gaia", Version: "v7.0.3", NumValidators: &nv, NumFullNodes: &nf}, - {Name: "osmosis", ChainName: "osmosis", Version: "v14.0.1", NumValidators: &nv, NumFullNodes: &nf}, - }) - - chains, err := cf.Chains(t.Name()) - require.NoError(t, err) - gaia, osmosis := chains[0], chains[1] - - // Relayer Factory to construct relayer - r := NewRelayerFactory(RelayerConfig{ - Processor: relayer.ProcessorEvents, - InitialBlockHistory: 100, - }).Build(t, nil, "") - - processor.PathProcMessageCollector = make(chan *processor.PathProcessorMessageResp, 10000) - - // Prep Interchain - const ibcPath = "gaia-osmosis" - ic := interchaintest.NewInterchain(). - AddChain(gaia). - AddChain(osmosis). - AddRelayer(r, "relayer"). - AddLink(interchaintest.InterchainLink{ - Chain1: gaia, - Chain2: osmosis, - Relayer: r, - Path: ibcPath, - }) - - // Reporter/logs - rep := testreporter.NewNopReporter() - eRep := rep.RelayerExecReporter(t) - - client, network := interchaintest.DockerSetup(t) - - // Build interchain - require.NoError(t, ic.Build(ctx, eRep, interchaintest.InterchainBuildOptions{ - TestName: t.Name(), - Client: client, - NetworkID: network, - - SkipPathCreation: false, - })) - - t.Parallel() - - // Get Channel ID - gaiaChans, err := r.GetChannels(ctx, eRep, gaia.Config().ChainID) - require.NoError(t, err) - gaiaChannel := gaiaChans[0] - osmosisChannel := gaiaChans[0].Counterparty - - // Create and Fund User Wallets - fundAmount := int64(10_000_000) - - // Tiny amount of funding, not enough to pay for a single TX fee (the GRANTER should be paying the fee) - granteeFundAmount := int64(10) - granteeKeyPrefix := "grantee1" - grantee2KeyPrefix := "grantee2" - grantee3KeyPrefix := "grantee3" - granterKeyPrefix := "default" - - mnemonicAny := genMnemonic(t) - gaiaGranterWallet, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, granterKeyPrefix, mnemonicAny, int64(fundAmount), gaia) - require.NoError(t, err) - - mnemonicAny = genMnemonic(t) - gaiaGranteeWallet, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, granteeKeyPrefix, mnemonicAny, int64(granteeFundAmount), gaia) - require.NoError(t, err) - - mnemonicAny = genMnemonic(t) - gaiaGrantee2Wallet, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, grantee2KeyPrefix, mnemonicAny, int64(granteeFundAmount), gaia) - require.NoError(t, err) - - mnemonicAny = genMnemonic(t) - gaiaGrantee3Wallet, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, grantee3KeyPrefix, mnemonicAny, int64(granteeFundAmount), gaia) - require.NoError(t, err) - - mnemonicAny = genMnemonic(t) - osmosisUser, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, "recipient", mnemonicAny, int64(fundAmount), osmosis) - require.NoError(t, err) - - mnemonicAny = genMnemonic(t) - gaiaUser, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, "recipient", mnemonicAny, int64(fundAmount), gaia) - require.NoError(t, err) - - mnemonic := gaiaGranterWallet.Mnemonic() - fmt.Printf("Wallet mnemonic: %s\n", mnemonic) - - rand.Seed(time.Now().UnixNano()) - - //IBC chain config is unrelated to RELAYER config so this step is necessary - if err := r.RestoreKey(ctx, - eRep, - gaia.Config(), - gaiaGranterWallet.KeyName(), - gaiaGranterWallet.Mnemonic(), - ); err != nil { - t.Fatalf("failed to restore granter key to relayer for chain %s: %s", gaia.Config().ChainID, err.Error()) + var tests = [][]*interchaintest.ChainSpec{ + { + {Name: "gaia", ChainName: "gaia", Version: "v7.0.3", NumValidators: &nv, NumFullNodes: &nf}, + {Name: "osmosis", ChainName: "osmosis", Version: "v14.0.1", NumValidators: &nv, NumFullNodes: &nf}, + }, + { + {Name: "gaia", ChainName: "gaia", Version: "v7.0.3", NumValidators: &nv, NumFullNodes: &nf}, + {Name: "kujira", ChainName: "kujira", Version: "v0.8.7", NumValidators: &nv, NumFullNodes: &nf}, + }, } - //IBC chain config is unrelated to RELAYER config so this step is necessary - if err := r.RestoreKey(ctx, - eRep, - gaia.Config(), - gaiaGranteeWallet.KeyName(), - gaiaGranteeWallet.Mnemonic(), - ); err != nil { - t.Fatalf("failed to restore granter key to relayer for chain %s: %s", gaia.Config().ChainID, err.Error()) - } + for _, tt := range tests { + testname := fmt.Sprintf("%s,%s", tt[0].Name, tt[1].Name) + t.Run(testname, func(t *testing.T) { + + // Chain Factory + cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), tt) + + chains, err := cf.Chains(t.Name()) + require.NoError(t, err) + gaia, osmosis := chains[0], chains[1] + + // Relayer Factory to construct relayer + r := NewRelayerFactory(RelayerConfig{ + Processor: relayer.ProcessorEvents, + InitialBlockHistory: 100, + }).Build(t, nil, "") + + processor.PathProcMessageCollector = make(chan *processor.PathProcessorMessageResp, 10000) + + // Prep Interchain + const ibcPath = "gaia-osmosis" + ic := interchaintest.NewInterchain(). + AddChain(gaia). + AddChain(osmosis). + AddRelayer(r, "relayer"). + AddLink(interchaintest.InterchainLink{ + Chain1: gaia, + Chain2: osmosis, + Relayer: r, + Path: ibcPath, + }) + + // Reporter/logs + rep := testreporter.NewNopReporter() + eRep := rep.RelayerExecReporter(t) + + client, network := interchaintest.DockerSetup(t) + + // Build interchain + require.NoError(t, ic.Build(ctx, eRep, interchaintest.InterchainBuildOptions{ + TestName: t.Name(), + Client: client, + NetworkID: network, + + SkipPathCreation: false, + })) + + t.Parallel() + + // Get Channel ID + gaiaChans, err := r.GetChannels(ctx, eRep, gaia.Config().ChainID) + require.NoError(t, err) + gaiaChannel := gaiaChans[0] + osmosisChannel := gaiaChans[0].Counterparty + + // Create and Fund User Wallets + fundAmount := int64(10_000_000) + + // Tiny amount of funding, not enough to pay for a single TX fee (the GRANTER should be paying the fee) + granteeFundAmount := int64(10) + granteeKeyPrefix := "grantee1" + grantee2KeyPrefix := "grantee2" + grantee3KeyPrefix := "grantee3" + granterKeyPrefix := "default" + + mnemonicAny := genMnemonic(t) + gaiaGranterWallet, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, granterKeyPrefix, mnemonicAny, int64(fundAmount), gaia) + require.NoError(t, err) + + mnemonicAny = genMnemonic(t) + gaiaGranteeWallet, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, granteeKeyPrefix, mnemonicAny, int64(granteeFundAmount), gaia) + require.NoError(t, err) + + mnemonicAny = genMnemonic(t) + gaiaGrantee2Wallet, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, grantee2KeyPrefix, mnemonicAny, int64(granteeFundAmount), gaia) + require.NoError(t, err) + + mnemonicAny = genMnemonic(t) + gaiaGrantee3Wallet, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, grantee3KeyPrefix, mnemonicAny, int64(granteeFundAmount), gaia) + require.NoError(t, err) + + mnemonicAny = genMnemonic(t) + osmosisUser, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, "recipient", mnemonicAny, int64(fundAmount), osmosis) + require.NoError(t, err) + + mnemonicAny = genMnemonic(t) + gaiaUser, err := interchaintest.GetAndFundTestUserWithMnemonic(ctx, "recipient", mnemonicAny, int64(fundAmount), gaia) + require.NoError(t, err) + + mnemonic := gaiaGranterWallet.Mnemonic() + fmt.Printf("Wallet mnemonic: %s\n", mnemonic) + + rand.Seed(time.Now().UnixNano()) + + //IBC chain config is unrelated to RELAYER config so this step is necessary + if err := r.RestoreKey(ctx, + eRep, + gaia.Config(), + gaiaGranterWallet.KeyName(), + gaiaGranterWallet.Mnemonic(), + ); err != nil { + t.Fatalf("failed to restore granter key to relayer for chain %s: %s", gaia.Config().ChainID, err.Error()) + } - //IBC chain config is unrelated to RELAYER config so this step is necessary - if err := r.RestoreKey(ctx, - eRep, - gaia.Config(), - gaiaGrantee2Wallet.KeyName(), - gaiaGrantee2Wallet.Mnemonic(), - ); err != nil { - t.Fatalf("failed to restore granter key to relayer for chain %s: %s", gaia.Config().ChainID, err.Error()) - } + //IBC chain config is unrelated to RELAYER config so this step is necessary + if err := r.RestoreKey(ctx, + eRep, + gaia.Config(), + gaiaGranteeWallet.KeyName(), + gaiaGranteeWallet.Mnemonic(), + ); err != nil { + t.Fatalf("failed to restore granter key to relayer for chain %s: %s", gaia.Config().ChainID, err.Error()) + } - //IBC chain config is unrelated to RELAYER config so this step is necessary - if err := r.RestoreKey(ctx, - eRep, - gaia.Config(), - gaiaGrantee3Wallet.KeyName(), - gaiaGrantee3Wallet.Mnemonic(), - ); err != nil { - t.Fatalf("failed to restore granter key to relayer for chain %s: %s", gaia.Config().ChainID, err.Error()) - } + //IBC chain config is unrelated to RELAYER config so this step is necessary + if err := r.RestoreKey(ctx, + eRep, + gaia.Config(), + gaiaGrantee2Wallet.KeyName(), + gaiaGrantee2Wallet.Mnemonic(), + ); err != nil { + t.Fatalf("failed to restore granter key to relayer for chain %s: %s", gaia.Config().ChainID, err.Error()) + } - //IBC chain config is unrelated to RELAYER config so this step is necessary - if err := r.RestoreKey(ctx, - eRep, - osmosis.Config(), - osmosisUser.KeyName(), - osmosisUser.Mnemonic(), - ); err != nil { - t.Fatalf("failed to restore granter key to relayer for chain %s: %s", osmosis.Config().ChainID, err.Error()) - } + //IBC chain config is unrelated to RELAYER config so this step is necessary + if err := r.RestoreKey(ctx, + eRep, + gaia.Config(), + gaiaGrantee3Wallet.KeyName(), + gaiaGrantee3Wallet.Mnemonic(), + ); err != nil { + t.Fatalf("failed to restore granter key to relayer for chain %s: %s", gaia.Config().ChainID, err.Error()) + } - //IBC chain config is unrelated to RELAYER config so this step is necessary - if err := r.RestoreKey(ctx, - eRep, - osmosis.Config(), - gaiaUser.KeyName(), - gaiaUser.Mnemonic(), - ); err != nil { - t.Fatalf("failed to restore granter key to relayer for chain %s: %s", gaia.Config().ChainID, err.Error()) - } + //IBC chain config is unrelated to RELAYER config so this step is necessary + if err := r.RestoreKey(ctx, + eRep, + osmosis.Config(), + osmosisUser.KeyName(), + osmosisUser.Mnemonic(), + ); err != nil { + t.Fatalf("failed to restore granter key to relayer for chain %s: %s", osmosis.Config().ChainID, err.Error()) + } - gaiaGranteeAddr := gaiaGranteeWallet.FormattedAddress() - gaiaGrantee2Addr := gaiaGrantee2Wallet.FormattedAddress() - gaiaGrantee3Addr := gaiaGrantee3Wallet.FormattedAddress() - gaiaGranterAddr := gaiaGranterWallet.FormattedAddress() + //IBC chain config is unrelated to RELAYER config so this step is necessary + if err := r.RestoreKey(ctx, + eRep, + osmosis.Config(), + gaiaUser.KeyName(), + gaiaUser.Mnemonic(), + ); err != nil { + t.Fatalf("failed to restore granter key to relayer for chain %s: %s", gaia.Config().ChainID, err.Error()) + } - granteeCsv := gaiaGranteeWallet.KeyName() + "," + gaiaGrantee2Wallet.KeyName() + "," + gaiaGrantee3Wallet.KeyName() + gaiaGranteeAddr := gaiaGranteeWallet.FormattedAddress() + gaiaGrantee2Addr := gaiaGrantee2Wallet.FormattedAddress() + gaiaGrantee3Addr := gaiaGrantee3Wallet.FormattedAddress() + gaiaGranterAddr := gaiaGranterWallet.FormattedAddress() - //You MUST run the configure feegrant command prior to starting the relayer, otherwise it'd be like you never set it up at all (within this test) - //Note that Gaia supports feegrants, but Osmosis does not (x/feegrant module, or any compatible module, is not included in Osmosis SDK app modules) - localRelayer := r.(*Relayer) - res := localRelayer.sys().Run(logger, "chains", "configure", "feegrant", "basicallowance", gaia.Config().ChainID, gaiaGranterWallet.KeyName(), "--grantees", granteeCsv, "--overwrite-granter") - if res.Err != nil { - fmt.Printf("configure feegrant results: %s\n", res.Stdout.String()) - t.Fatalf("failed to rly config feegrants: %v", res.Err) - } + granteeCsv := gaiaGranteeWallet.KeyName() + "," + gaiaGrantee2Wallet.KeyName() + "," + gaiaGrantee3Wallet.KeyName() - //Map of feegranted chains and the feegrant info for the chain - feegrantedChains := map[string]*chainFeegrantInfo{} - feegrantedChains[gaia.Config().ChainID] = &chainFeegrantInfo{granter: gaiaGranterAddr, grantees: []string{gaiaGranteeAddr, gaiaGrantee2Addr, gaiaGrantee3Addr}} + //You MUST run the configure feegrant command prior to starting the relayer, otherwise it'd be like you never set it up at all (within this test) + //Note that Gaia supports feegrants, but Osmosis does not (x/feegrant module, or any compatible module, is not included in Osmosis SDK app modules) + localRelayer := r.(*Relayer) + res := localRelayer.sys().Run(logger, "chains", "configure", "feegrant", "basicallowance", gaia.Config().ChainID, gaiaGranterWallet.KeyName(), "--grantees", granteeCsv, "--overwrite-granter") + if res.Err != nil { + fmt.Printf("configure feegrant results: %s\n", res.Stdout.String()) + t.Fatalf("failed to rly config feegrants: %v", res.Err) + } - time.Sleep(14 * time.Second) //commit a couple blocks - r.StartRelayer(ctx, eRep, ibcPath) + //Map of feegranted chains and the feegrant info for the chain + feegrantedChains := map[string]*chainFeegrantInfo{} + feegrantedChains[gaia.Config().ChainID] = &chainFeegrantInfo{granter: gaiaGranterAddr, grantees: []string{gaiaGranteeAddr, gaiaGrantee2Addr, gaiaGrantee3Addr}} - // Send Transaction - amountToSend := int64(1_000) + time.Sleep(14 * time.Second) //commit a couple blocks + r.StartRelayer(ctx, eRep, ibcPath) - gaiaDstAddress := types.MustBech32ifyAddressBytes(osmosis.Config().Bech32Prefix, gaiaUser.Address()) - osmosisDstAddress := types.MustBech32ifyAddressBytes(gaia.Config().Bech32Prefix, osmosisUser.Address()) + // Send Transaction + amountToSend := int64(1_000) - gaiaHeight, err := gaia.Height(ctx) - require.NoError(t, err) + gaiaDstAddress := types.MustBech32ifyAddressBytes(osmosis.Config().Bech32Prefix, gaiaUser.Address()) + osmosisDstAddress := types.MustBech32ifyAddressBytes(gaia.Config().Bech32Prefix, osmosisUser.Address()) - osmosisHeight, err := osmosis.Height(ctx) - require.NoError(t, err) + gaiaHeight, err := gaia.Height(ctx) + require.NoError(t, err) - var eg errgroup.Group - var gaiaTx ibc.Tx + osmosisHeight, err := osmosis.Height(ctx) + require.NoError(t, err) - eg.Go(func() error { - gaiaTx, err = gaia.SendIBCTransfer(ctx, gaiaChannel.ChannelID, gaiaUser.KeyName(), ibc.WalletAmount{ - Address: gaiaDstAddress, - Denom: gaia.Config().Denom, - Amount: amountToSend, - }, - ibc.TransferOptions{}, - ) - if err != nil { - return err - } - if err := gaiaTx.Validate(); err != nil { - return err - } - - _, err = testutil.PollForAck(ctx, gaia, gaiaHeight, gaiaHeight+20, gaiaTx.Packet) - return err - }) + var eg errgroup.Group + var gaiaTx ibc.Tx - eg.Go(func() error { - tx, err := osmosis.SendIBCTransfer(ctx, osmosisChannel.ChannelID, osmosisUser.KeyName(), ibc.WalletAmount{ - Address: osmosisDstAddress, - Denom: osmosis.Config().Denom, - Amount: amountToSend, - }, - ibc.TransferOptions{}, - ) - if err != nil { - return err - } - if err := tx.Validate(); err != nil { - return err - } - _, err = testutil.PollForAck(ctx, osmosis, osmosisHeight, osmosisHeight+20, tx.Packet) - return err - }) + eg.Go(func() error { + gaiaTx, err = gaia.SendIBCTransfer(ctx, gaiaChannel.ChannelID, gaiaUser.KeyName(), ibc.WalletAmount{ + Address: gaiaDstAddress, + Denom: gaia.Config().Denom, + Amount: amountToSend, + }, + ibc.TransferOptions{}, + ) + if err != nil { + return err + } + if err := gaiaTx.Validate(); err != nil { + return err + } - eg.Go(func() error { - tx, err := osmosis.SendIBCTransfer(ctx, osmosisChannel.ChannelID, osmosisUser.KeyName(), ibc.WalletAmount{ - Address: osmosisDstAddress, - Denom: osmosis.Config().Denom, - Amount: amountToSend, - }, - ibc.TransferOptions{}, - ) - if err != nil { - return err - } - if err := tx.Validate(); err != nil { - return err - } - _, err = testutil.PollForAck(ctx, osmosis, osmosisHeight, osmosisHeight+20, tx.Packet) - return err - }) + _, err = testutil.PollForAck(ctx, gaia, gaiaHeight, gaiaHeight+20, gaiaTx.Packet) + return err + }) + + eg.Go(func() error { + tx, err := osmosis.SendIBCTransfer(ctx, osmosisChannel.ChannelID, osmosisUser.KeyName(), ibc.WalletAmount{ + Address: osmosisDstAddress, + Denom: osmosis.Config().Denom, + Amount: amountToSend, + }, + ibc.TransferOptions{}, + ) + if err != nil { + return err + } + if err := tx.Validate(); err != nil { + return err + } + _, err = testutil.PollForAck(ctx, osmosis, osmosisHeight, osmosisHeight+20, tx.Packet) + return err + }) + + eg.Go(func() error { + tx, err := osmosis.SendIBCTransfer(ctx, osmosisChannel.ChannelID, osmosisUser.KeyName(), ibc.WalletAmount{ + Address: osmosisDstAddress, + Denom: osmosis.Config().Denom, + Amount: amountToSend, + }, + ibc.TransferOptions{}, + ) + if err != nil { + return err + } + if err := tx.Validate(); err != nil { + return err + } + _, err = testutil.PollForAck(ctx, osmosis, osmosisHeight, osmosisHeight+20, tx.Packet) + return err + }) + + eg.Go(func() error { + tx, err := osmosis.SendIBCTransfer(ctx, osmosisChannel.ChannelID, osmosisUser.KeyName(), ibc.WalletAmount{ + Address: osmosisDstAddress, + Denom: osmosis.Config().Denom, + Amount: amountToSend, + }, + ibc.TransferOptions{}, + ) + if err != nil { + return err + } + if err := tx.Validate(); err != nil { + return err + } + _, err = testutil.PollForAck(ctx, osmosis, osmosisHeight, osmosisHeight+20, tx.Packet) + return err + }) + + require.NoError(t, err) + require.NoError(t, eg.Wait()) + + feegrantMsgSigners := map[string][]string{} //chain to list of signers + + for len(processor.PathProcMessageCollector) > 0 { + select { + case curr, ok := <-processor.PathProcMessageCollector: + if ok && curr.Error == nil && curr.SuccessfulTx { + cProv, cosmProv := curr.DestinationChain.(*cosmos.CosmosProvider) + if cosmProv { + chain := cProv.PCfg.ChainID + feegrantInfo, isFeegrantedChain := feegrantedChains[chain] + if isFeegrantedChain && !strings.Contains(cProv.PCfg.KeyDirectory, t.Name()) { + //This would indicate that a parallel test is inserting msgs into the queue. + //We can safely skip over any messages inserted by other test cases. + fmt.Println("Skipping PathProcessorMessageResp from unrelated Parallel test case") + continue + } - eg.Go(func() error { - tx, err := osmosis.SendIBCTransfer(ctx, osmosisChannel.ChannelID, osmosisUser.KeyName(), ibc.WalletAmount{ - Address: osmosisDstAddress, - Denom: osmosis.Config().Denom, - Amount: amountToSend, - }, - ibc.TransferOptions{}, - ) - if err != nil { - return err - } - if err := tx.Validate(); err != nil { - return err - } - _, err = testutil.PollForAck(ctx, osmosis, osmosisHeight, osmosisHeight+20, tx.Packet) - return err - }) - - require.NoError(t, err) - require.NoError(t, eg.Wait()) - - feegrantMsgSigners := map[string][]string{} //chain to list of signers - - for len(processor.PathProcMessageCollector) > 0 { - select { - case curr, ok := <-processor.PathProcMessageCollector: - if ok && curr.Error == nil && curr.SuccessfulTx { - cProv, cosmProv := curr.DestinationChain.(*cosmos.CosmosProvider) - if cosmProv { - chain := cProv.PCfg.ChainID - feegrantInfo, isFeegrantedChain := feegrantedChains[chain] - if isFeegrantedChain && !strings.Contains(cProv.PCfg.KeyDirectory, t.Name()) { - //This would indicate that a parallel test is inserting msgs into the queue. - //We can safely skip over any messages inserted by other test cases. - fmt.Println("Skipping PathProcessorMessageResp from unrelated Parallel test case") - continue - } + done := cProv.SetSDKContext() + + hash, err := hex.DecodeString(curr.Response.TxHash) + require.Nil(t, err) + txResp, err := TxWithRetry(ctx, cProv.RPCClient, hash) + require.Nil(t, err) + + require.Nil(t, err) + dc := cProv.Cdc.TxConfig.TxDecoder() + tx, err := dc(txResp.Tx) + require.Nil(t, err) + builder, err := cProv.Cdc.TxConfig.WrapTxBuilder(tx) + require.Nil(t, err) + txFinder := builder.(protoTxProvider) + fullTx := txFinder.GetProtoTx() + isFeegrantedMsg := false + + msgs := "" + msgType := "" + for _, m := range fullTx.GetMsgs() { + msgType = types.MsgTypeURL(m) + //We want all IBC transfers (on an open channel/connection) to be feegranted in round robin fashion + if msgType == "/ibc.core.channel.v1.MsgRecvPacket" || msgType == "/ibc.core.channel.v1.MsgAcknowledgement" { + isFeegrantedMsg = true + msgs += msgType + ", " + } else { + msgs += msgType + ", " + } + } - done := cProv.SetSDKContext() - - hash, err := hex.DecodeString(curr.Response.TxHash) - require.Nil(t, err) - txResp, err := TxWithRetry(ctx, cProv.RPCClient, hash) - require.Nil(t, err) - - require.Nil(t, err) - dc := cProv.Cdc.TxConfig.TxDecoder() - tx, err := dc(txResp.Tx) - require.Nil(t, err) - builder, err := cProv.Cdc.TxConfig.WrapTxBuilder(tx) - require.Nil(t, err) - txFinder := builder.(protoTxProvider) - fullTx := txFinder.GetProtoTx() - isFeegrantedMsg := false - - msgs := "" - msgType := "" - for _, m := range fullTx.GetMsgs() { - msgType = types.MsgTypeURL(m) - //We want all IBC transfers (on an open channel/connection) to be feegranted in round robin fashion - if msgType == "/ibc.core.channel.v1.MsgRecvPacket" || msgType == "/ibc.core.channel.v1.MsgAcknowledgement" { - isFeegrantedMsg = true - msgs += msgType + ", " - } else { - msgs += msgType + ", " - } - } + //It's required that TXs be feegranted in a round robin fashion for this chain and message type + if isFeegrantedChain && isFeegrantedMsg { + fmt.Printf("Msg types: %+v\n", msgs) + signers := fullTx.GetSigners() + require.Equal(t, len(signers), 1) + granter := fullTx.FeeGranter() + + //Feegranter for the TX that was signed on chain must be the relayer chain's configured feegranter + require.Equal(t, feegrantInfo.granter, granter.String()) + require.NotEmpty(t, granter) + + for _, msg := range fullTx.GetMsgs() { + msgType = types.MsgTypeURL(msg) + //We want all IBC transfers (on an open channel/connection) to be feegranted in round robin fashion + if msgType == "/ibc.core.channel.v1.MsgRecvPacket" { + c := msg.(*chantypes.MsgRecvPacket) + appData := c.Packet.GetData() + tokenTransfer := &transfertypes.FungibleTokenPacketData{} + err := tokenTransfer.Unmarshal(appData) + if err == nil { + fmt.Printf("%+v\n", tokenTransfer) + } else { + fmt.Println(string(appData)) + } + } + } - //It's required that TXs be feegranted in a round robin fashion for this chain and message type - if isFeegrantedChain && isFeegrantedMsg { - fmt.Printf("Msg types: %+v\n", msgs) - signers := fullTx.GetSigners() - require.Equal(t, len(signers), 1) - granter := fullTx.FeeGranter() - - //Feegranter for the TX that was signed on chain must be the relayer chain's configured feegranter - require.Equal(t, feegrantInfo.granter, granter.String()) - require.NotEmpty(t, granter) - - for _, msg := range fullTx.GetMsgs() { - msgType = types.MsgTypeURL(msg) - //We want all IBC transfers (on an open channel/connection) to be feegranted in round robin fashion - if msgType == "/ibc.core.channel.v1.MsgRecvPacket" { - c := msg.(*chantypes.MsgRecvPacket) - appData := c.Packet.GetData() - tokenTransfer := &transfertypes.FungibleTokenPacketData{} - err := tokenTransfer.Unmarshal(appData) - if err == nil { - fmt.Printf("%+v\n", tokenTransfer) + //Grantee for the TX that was signed on chain must be a configured grantee in the relayer's chain feegrants. + //In addition, the grantee must be used in round robin fashion + //expectedGrantee := nextGrantee(feegrantInfo) + actualGrantee := signers[0].String() + signerList, ok := feegrantMsgSigners[chain] + if ok { + signerList = append(signerList, actualGrantee) + feegrantMsgSigners[chain] = signerList } else { - fmt.Println(string(appData)) + feegrantMsgSigners[chain] = []string{actualGrantee} } + fmt.Printf("Chain: %s, msg type: %s, height: %d, signer: %s, granter: %s\n", chain, msgType, curr.Response.Height, actualGrantee, granter.String()) } + done() } - - //Grantee for the TX that was signed on chain must be a configured grantee in the relayer's chain feegrants. - //In addition, the grantee must be used in round robin fashion - //expectedGrantee := nextGrantee(feegrantInfo) - actualGrantee := signers[0].String() - signerList, ok := feegrantMsgSigners[chain] - if ok { - signerList = append(signerList, actualGrantee) - feegrantMsgSigners[chain] = signerList - } else { - feegrantMsgSigners[chain] = []string{actualGrantee} - } - fmt.Printf("Chain: %s, msg type: %s, height: %d, signer: %s, granter: %s\n", chain, msgType, curr.Response.Height, actualGrantee, granter.String()) } - done() + default: + fmt.Println("Unknown channel message") } } - default: - fmt.Println("Unknown channel message") - } - } - for chain, signers := range feegrantMsgSigners { - require.Equal(t, chain, gaia.Config().ChainID) - signerCountMap := map[string]int{} + for chain, signers := range feegrantMsgSigners { + require.Equal(t, chain, gaia.Config().ChainID) + signerCountMap := map[string]int{} - for _, signer := range signers { - count, ok := signerCountMap[signer] - if ok { - signerCountMap[signer] = count + 1 - } else { - signerCountMap[signer] = 1 - } - } + for _, signer := range signers { + count, ok := signerCountMap[signer] + if ok { + signerCountMap[signer] = count + 1 + } else { + signerCountMap[signer] = 1 + } + } - highestCount := 0 - for _, count := range signerCountMap { - if count > highestCount { - highestCount = count - } - } + highestCount := 0 + for _, count := range signerCountMap { + if count > highestCount { + highestCount = count + } + } - //At least one feegranter must have signed a TX - require.GreaterOrEqual(t, highestCount, 1) + //At least one feegranter must have signed a TX + require.GreaterOrEqual(t, highestCount, 1) - //All of the feegrantees must have signed at least one TX - expectedFeegrantInfo := feegrantedChains[chain] - require.Equal(t, len(signerCountMap), len(expectedFeegrantInfo.grantees)) + //All of the feegrantees must have signed at least one TX + expectedFeegrantInfo := feegrantedChains[chain] + require.Equal(t, len(signerCountMap), len(expectedFeegrantInfo.grantees)) - // verify that TXs were signed in a round robin fashion. - // no grantee should have signed more TXs than any other grantee (off by one is allowed). - for signer, count := range signerCountMap { - fmt.Printf("signer %s signed %d feegranted TXs \n", signer, count) - require.LessOrEqual(t, highestCount-count, 1) - } - } + // verify that TXs were signed in a round robin fashion. + // no grantee should have signed more TXs than any other grantee (off by one is allowed). + for signer, count := range signerCountMap { + fmt.Printf("signer %s signed %d feegranted TXs \n", signer, count) + require.LessOrEqual(t, highestCount-count, 1) + } + } + + // Trace IBC Denom + gaiaDenomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom(osmosisChannel.PortID, osmosisChannel.ChannelID, gaia.Config().Denom)) + gaiaIbcDenom := gaiaDenomTrace.IBCDenom() - // Trace IBC Denom - gaiaDenomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom(osmosisChannel.PortID, osmosisChannel.ChannelID, gaia.Config().Denom)) - gaiaIbcDenom := gaiaDenomTrace.IBCDenom() + osmosisDenomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom(gaiaChannel.PortID, gaiaChannel.ChannelID, osmosis.Config().Denom)) + osmosisIbcDenom := osmosisDenomTrace.IBCDenom() - osmosisDenomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom(gaiaChannel.PortID, gaiaChannel.ChannelID, osmosis.Config().Denom)) - osmosisIbcDenom := osmosisDenomTrace.IBCDenom() + // Test destination wallets have increased funds + gaiaIBCBalance, err := osmosis.GetBalance(ctx, gaiaDstAddress, gaiaIbcDenom) + require.NoError(t, err) + require.Equal(t, amountToSend, gaiaIBCBalance) - // Test destination wallets have increased funds - gaiaIBCBalance, err := osmosis.GetBalance(ctx, gaiaDstAddress, gaiaIbcDenom) - require.NoError(t, err) - require.Equal(t, amountToSend, gaiaIBCBalance) + osmosisIBCBalance, err := gaia.GetBalance(ctx, osmosisDstAddress, osmosisIbcDenom) + require.NoError(t, err) + require.Equal(t, 3*amountToSend, osmosisIBCBalance) - osmosisIBCBalance, err := gaia.GetBalance(ctx, osmosisDstAddress, osmosisIbcDenom) - require.NoError(t, err) - require.Equal(t, 3*amountToSend, osmosisIBCBalance) + // Test grantee still has exact amount expected + gaiaGranteeIBCBalance, err := gaia.GetBalance(ctx, gaiaGranteeAddr, gaia.Config().Denom) + require.NoError(t, err) + require.Equal(t, granteeFundAmount, gaiaGranteeIBCBalance) - // Test grantee still has exact amount expected - gaiaGranteeIBCBalance, err := gaia.GetBalance(ctx, gaiaGranteeAddr, gaia.Config().Denom) - require.NoError(t, err) - require.Equal(t, granteeFundAmount, gaiaGranteeIBCBalance) + // Test granter has less than they started with, meaning fees came from their account + gaiaGranterIBCBalance, err := gaia.GetBalance(ctx, gaiaGranterAddr, gaia.Config().Denom) + require.NoError(t, err) + require.Less(t, gaiaGranterIBCBalance, fundAmount) + r.StopRelayer(ctx, eRep) - // Test granter has less than they started with, meaning fees came from their account - gaiaGranterIBCBalance, err := gaia.GetBalance(ctx, gaiaGranterAddr, gaia.Config().Denom) - require.NoError(t, err) - require.Less(t, gaiaGranterIBCBalance, fundAmount) - r.StopRelayer(ctx, eRep) + }) + } } func TxWithRetry(ctx context.Context, client rpcclient.Client, hash []byte) (*ctypes.ResultTx, error) { diff --git a/main.go b/main.go index 2dd1b6e4d..958a04ad7 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,15 @@ package main -import "github.com/cosmos/relayer/v2/cmd" +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/relayer/v2/cmd" +) func main() { cmd.Execute() } + +func init() { + //prevent incorrect bech32 address prefixed addresses when calling AccAddress.String() + sdk.SetAddrCacheEnabled(false) +} diff --git a/relayer/chains/cosmos/feegrant.go b/relayer/chains/cosmos/feegrant.go index 92b14b6d3..e2ffbab45 100644 --- a/relayer/chains/cosmos/feegrant.go +++ b/relayer/chains/cosmos/feegrant.go @@ -8,6 +8,7 @@ import ( "strconv" "time" + "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -290,30 +291,16 @@ func (cc *CosmosProvider) EnsureBasicGrants(ctx context.Context, memo string) (* } if len(msgs) > 0 { - //Make sure the granter has funds on chain, if not, we can't even pay TX fees. - //Also, depending how the config was initialized, the key might only exist locally, not on chain. - balance, err := cc.QueryBalanceWithAddress(ctx, granterAddr) - if err != nil { - return nil, err - } - - //Check to ensure the feegranter has funds on chain that can pay TX fees - weBroke := true - gasDenom, err := getGasTokenDenom(cc.PCfg.GasPrices) - if err != nil { - return nil, err - } + cliCtx := client.Context{}.WithClient(cc.RPCClient). + WithInterfaceRegistry(cc.Cdc.InterfaceRegistry). + WithChainID(cc.PCfg.ChainID). + WithCodec(cc.Cdc.Marshaler). + WithFromAddress(granterAcc) - for _, coin := range balance { - if coin.Denom == gasDenom { - if coin.Amount.GT(sdk.ZeroInt()) { - weBroke = false - } - } - } + granterExists := cc.EnsureExists(cliCtx, granterAcc) == nil - //Feegranter can pay TX fees - if !weBroke { + //Feegranter exists on chain + if granterExists { txResp, err := cc.SubmitTxAwaitResponse(ctx, msgs, memo, 0, granterKey) if err != nil { fmt.Printf("Error: SubmitTxAwaitResponse: %s", err.Error()) @@ -326,7 +313,7 @@ func (cc *CosmosProvider) EnsureBasicGrants(ctx context.Context, memo string) (* fmt.Printf("TX succeeded, %d new grants configured, %d grants already in place. TX hash: %s\n", grantsNeeded, numGrantees-grantsNeeded, txResp.TxResponse.TxHash) return txResp.TxResponse, err } else { - return nil, fmt.Errorf("granter %s does not have funds on chain in fee denom '%s' (no TXs submitted)", granterKey, gasDenom) + return nil, fmt.Errorf("granter %s does not exist on chain", granterKey) } } else { fmt.Printf("All grantees (%d total) already had valid feegrants. Feegrant configuration verified.\n", numGrantees)