diff --git a/chain/cosmos/config.go b/chain/cosmos/config.go new file mode 100644 index 000000000..01b04b2e9 --- /dev/null +++ b/chain/cosmos/config.go @@ -0,0 +1,23 @@ +package cosmos + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func SetSDKConfig(bech32Prefix string) *sdk.Config { + var ( + bech32MainPrefix = bech32Prefix + bech32PrefixAccAddr = bech32MainPrefix + bech32PrefixAccPub = bech32MainPrefix + sdk.PrefixPublic + bech32PrefixValAddr = bech32MainPrefix + sdk.PrefixValidator + sdk.PrefixOperator + bech32PrefixValPub = bech32MainPrefix + sdk.PrefixValidator + sdk.PrefixOperator + sdk.PrefixPublic + bech32PrefixConsAddr = bech32MainPrefix + sdk.PrefixValidator + sdk.PrefixConsensus + bech32PrefixConsPub = bech32MainPrefix + sdk.PrefixValidator + sdk.PrefixConsensus + sdk.PrefixPublic + ) + + cfg := sdk.GetConfig() + cfg.SetBech32PrefixForAccount(bech32PrefixAccAddr, bech32PrefixAccPub) + cfg.SetBech32PrefixForValidator(bech32PrefixValAddr, bech32PrefixValPub) + cfg.SetBech32PrefixForConsensusNode(bech32PrefixConsAddr, bech32PrefixConsPub) + return cfg +} diff --git a/chain/cosmos/cosmos_chain.go b/chain/cosmos/cosmos_chain.go index 7e04a0cd9..7db8d9d09 100644 --- a/chain/cosmos/cosmos_chain.go +++ b/chain/cosmos/cosmos_chain.go @@ -1057,7 +1057,7 @@ func (c *CosmosChain) Height(ctx context.Context) (uint64, error) { // Acknowledgements implements ibc.Chain, returning all acknowledgments in block at height func (c *CosmosChain) Acknowledgements(ctx context.Context, height uint64) ([]ibc.PacketAcknowledgement, error) { var acks []*chanTypes.MsgAcknowledgement - err := rangeBlockMessages(ctx, c.cfg.EncodingConfig.InterfaceRegistry, c.getFullNode().Client, height, func(msg types.Msg) bool { + err := RangeBlockMessages(ctx, c.cfg.EncodingConfig.InterfaceRegistry, c.getFullNode().Client, height, func(msg types.Msg) bool { found, ok := msg.(*chanTypes.MsgAcknowledgement) if ok { acks = append(acks, found) @@ -1090,7 +1090,7 @@ func (c *CosmosChain) Acknowledgements(ctx context.Context, height uint64) ([]ib // Timeouts implements ibc.Chain, returning all timeouts in block at height func (c *CosmosChain) Timeouts(ctx context.Context, height uint64) ([]ibc.PacketTimeout, error) { var timeouts []*chanTypes.MsgTimeout - err := rangeBlockMessages(ctx, c.cfg.EncodingConfig.InterfaceRegistry, c.getFullNode().Client, height, func(msg types.Msg) bool { + err := RangeBlockMessages(ctx, c.cfg.EncodingConfig.InterfaceRegistry, c.getFullNode().Client, height, func(msg types.Msg) bool { found, ok := msg.(*chanTypes.MsgTimeout) if ok { timeouts = append(timeouts, found) diff --git a/chain/cosmos/genesis.go b/chain/cosmos/genesis.go index 75e13dcdb..a803bb4af 100644 --- a/chain/cosmos/genesis.go +++ b/chain/cosmos/genesis.go @@ -15,6 +15,10 @@ type GenesisKV struct { Value interface{} `json:"value"` } +func NewGenesisKV(key string, value interface{}) GenesisKV { + return GenesisKV{Key: key, Value: value} +} + func ModifyGenesis(genesisKV []GenesisKV) func(ibc.ChainConfig, []byte) ([]byte, error) { return func(chainConfig ibc.ChainConfig, genbz []byte) ([]byte, error) { g := make(map[string]interface{}) diff --git a/chain/cosmos/query.go b/chain/cosmos/query.go index b5a7ceb82..4e5cb4684 100644 --- a/chain/cosmos/query.go +++ b/chain/cosmos/query.go @@ -13,9 +13,9 @@ type blockClient interface { Block(ctx context.Context, height *int64) (*tmtypes.ResultBlock, error) } -// rangeBlockMessages iterates through all a block's transactions and each transaction's messages yielding to f. +// RangeBlockMessages iterates through all a block's transactions and each transaction's messages yielding to f. // Return true from f to stop iteration. -func rangeBlockMessages(ctx context.Context, interfaceRegistry codectypes.InterfaceRegistry, client blockClient, height uint64, done func(sdk.Msg) bool) error { +func RangeBlockMessages(ctx context.Context, interfaceRegistry codectypes.InterfaceRegistry, client blockClient, height uint64, done func(sdk.Msg) bool) error { h := int64(height) block, err := client.Block(ctx, &h) if err != nil { diff --git a/conformance/test.go b/conformance/test.go index bf8b1f668..afcacea44 100644 --- a/conformance/test.go +++ b/conformance/test.go @@ -445,6 +445,10 @@ func testPacketRelaySuccess( req.NoError(err, "failed to get acknowledgement on destination chain") req.NoError(dstAck.Validate(), "invalid acknowledgement on destination chain") + // Even though we poll for the ack, there may be timing issues where balances are not fully reconciled yet. + // So we have a small buffer here. + require.NoError(t, testutil.WaitForBlocks(ctx, 5, srcChain, dstChain)) + // get ibc denom for dst denom on src chain dstDenomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom(channels[i].PortID, channels[i].ChannelID, dstDenom)) srcIbcDenom := dstDenomTrace.IBCDenom() @@ -496,7 +500,7 @@ func testPacketRelayFail( // Even though we poll for the timeout, there may be timing issues where balances are not fully reconciled yet. // So we have a small buffer here. - require.NoError(t, testutil.WaitForBlocks(ctx, 2, srcChain, dstChain)) + require.NoError(t, testutil.WaitForBlocks(ctx, 5, srcChain, dstChain)) // get ibc denom for src denom on dst chain srcDenomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom(channels[i].Counterparty.PortID, channels[i].Counterparty.ChannelID, srcDenom)) @@ -525,6 +529,10 @@ func testPacketRelayFail( req.NoError(err, "failed to get timeout packet on destination chain") req.NoError(timeout.Validate(), "invalid timeout packet on destination chain") + // Even though we poll for the timeout, there may be timing issues where balances are not fully reconciled yet. + // So we have a small buffer here. + require.NoError(t, testutil.WaitForBlocks(ctx, 5, srcChain, dstChain)) + // get ibc denom for dst denom on src chain dstDenomTrace := transfertypes.ParseDenomTrace(transfertypes.GetPrefixedDenom(channels[i].PortID, channels[i].ChannelID, dstDenom)) srcIbcDenom := dstDenomTrace.IBCDenom() diff --git a/examples/cosmos/README.md b/examples/cosmos/README.md new file mode 100644 index 000000000..cce1e3b0e --- /dev/null +++ b/examples/cosmos/README.md @@ -0,0 +1,3 @@ +# More Examples + +[State Sync](https://github.com/CosmosContracts/juno/blob/reece/add-state-sync-test/interchaintest/state_sync_test.go) diff --git a/examples/cosmos/chain_export_test.go b/examples/cosmos/chain_export_test.go index 67c91d580..6549bd5ef 100644 --- a/examples/cosmos/chain_export_test.go +++ b/examples/cosmos/chain_export_test.go @@ -15,10 +15,8 @@ import ( ) func TestJunoStateExport(t *testing.T) { - // SDK v45 - CosmosChainStateExportTest(t, "juno", "v15.0.0") // SDK v47 - CosmosChainStateExportTest(t, "juno", "v16.0.0") + CosmosChainStateExportTest(t, "juno", "v17.0.0") } func CosmosChainStateExportTest(t *testing.T, name, version string) { diff --git a/examples/cosmos/chain_miscellaneous_test.go b/examples/cosmos/chain_miscellaneous_test.go index 23d974968..c29bbc0a8 100644 --- a/examples/cosmos/chain_miscellaneous_test.go +++ b/examples/cosmos/chain_miscellaneous_test.go @@ -2,9 +2,14 @@ package cosmos_test import ( "context" + "fmt" "testing" "cosmossdk.io/math" + + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/strangelove-ventures/interchaintest/v7" "github.com/strangelove-ventures/interchaintest/v7/chain/cosmos" "github.com/strangelove-ventures/interchaintest/v7/ibc" @@ -16,10 +21,6 @@ func TestICTestMiscellaneous(t *testing.T) { CosmosChainTestMiscellaneous(t, "juno", "v16.0.0") } -const ( - initialBalance = 100_000_000 -) - func CosmosChainTestMiscellaneous(t *testing.T, name, version string) { if testing.Short() { t.Skip("skipping in short mode") @@ -28,13 +29,25 @@ func CosmosChainTestMiscellaneous(t *testing.T, name, version string) { numVals := 1 numFullNodes := 0 + cosmos.SetSDKConfig("juno") + + sdk47Genesis := []cosmos.GenesisKV{ + cosmos.NewGenesisKV("app_state.gov.params.voting_period", "15s"), + cosmos.NewGenesisKV("app_state.gov.params.max_deposit_period", "10s"), + cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.denom", "ujuno"), + cosmos.NewGenesisKV("app_state.gov.params.min_deposit.0.amount", "1"), + } + cf := interchaintest.NewBuiltinChainFactory(zaptest.NewLogger(t), []*interchaintest.ChainSpec{ { Name: name, ChainName: name, Version: version, ChainConfig: ibc.ChainConfig{ - Denom: "ujuno", + Denom: "ujuno", + Bech32Prefix: "juno", + CoinType: "118", + ModifyGenesis: cosmos.ModifyGenesis(sdk47Genesis), }, NumValidators: &numVals, NumFullNodes: &numFullNodes, @@ -62,13 +75,22 @@ func CosmosChainTestMiscellaneous(t *testing.T, name, version string) { _ = ic.Close() }) - users := interchaintest.GetAndFundTestUsers(t, ctx, "default", int64(initialBalance), chain, chain) - - TokenFactory(ctx, t, chain, users) - BuildDependencies(ctx, t, chain) + users := interchaintest.GetAndFundTestUsers(t, ctx, "default", int64(10_000_000_000), chain, chain) + + testBuildDependencies(ctx, t, chain) + testWalletKeys(ctx, t, chain) + testSendingTokens(ctx, t, chain, users) + testFindTxs(ctx, t, chain, users) + testPollForBalance(ctx, t, chain, users) + testRangeBlockMessages(ctx, t, chain, users) + testBroadcaster(ctx, t, chain, users) + testQueryCmd(ctx, t, chain) + testHasCommand(ctx, t, chain) + testTokenFactory(ctx, t, chain, users) + testAddingNode(ctx, t, chain) } -func BuildDependencies(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { +func testBuildDependencies(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { deps := chain.Validators[0].GetBuildInformation(ctx) sdkVer := "v0.47.3" @@ -106,7 +128,193 @@ func BuildDependencies(ctx context.Context, t *testing.T, chain *cosmos.CosmosCh } } -func TokenFactory(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { +func testWalletKeys(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { + // create a general key + randKey := "randkey123" + err := chain.CreateKey(ctx, randKey) + require.NoError(t, err) + + // verify key was created properly + _, err = chain.GetAddress(ctx, randKey) + require.NoError(t, err) + + // recover a key + // juno1hj5fveer5cjtn4wd6wstzugjfdxzl0xps73ftl + keyName := "key-abc" + testMnemonic := "decorate bright ozone fork gallery riot bus exhaust worth way bone indoor calm squirrel merry zero scheme cotton until shop any excess stage laundry" + wallet, err := chain.BuildWallet(ctx, keyName, testMnemonic) + require.NoError(t, err) + + // verify + addr, err := chain.GetAddress(ctx, keyName) + require.NoError(t, err) + require.Equal(t, wallet.Address(), addr) + + tn := chain.Validators[0] + a, err := tn.KeyBech32(ctx, "key-abc", "val") + require.NoError(t, err) + require.Equal(t, a, "junovaloper1hj5fveer5cjtn4wd6wstzugjfdxzl0xp0r8xsx") + + a, err = tn.KeyBech32(ctx, "key-abc", "acc") + require.NoError(t, err) + require.Equal(t, a, wallet.FormattedAddress()) + + a, err = tn.AccountKeyBech32(ctx, "key-abc") + require.NoError(t, err) + require.Equal(t, a, wallet.FormattedAddress()) +} + +func testSendingTokens(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + _, err := chain.GetBalance(ctx, users[0].FormattedAddress(), chain.Config().Denom) + require.NoError(t, err) + b2, err := chain.GetBalance(ctx, users[1].FormattedAddress(), chain.Config().Denom) + require.NoError(t, err) + + sendAmt := int64(1) + _, err = sendTokens(ctx, chain, users[0], users[1], "", sendAmt) + require.NoError(t, err) + + b2New, err := chain.GetBalance(ctx, users[1].FormattedAddress(), chain.Config().Denom) + require.NoError(t, err) + + require.Equal(t, b2.Add(math.NewInt(sendAmt)), b2New) +} + +func testFindTxs(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + height, _ := chain.Height(ctx) + + _, err := sendTokens(ctx, chain, users[0], users[1], "", 1) + require.NoError(t, err) + + txs, err := chain.FindTxs(ctx, height+1) + require.NoError(t, err) + require.NotEmpty(t, txs) + require.Equal(t, txs[0].Events[0].Type, "coin_spent") +} + +func testPollForBalance(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + bal2, err := chain.GetBalance(ctx, users[1].FormattedAddress(), chain.Config().Denom) + require.NoError(t, err) + + amt := ibc.WalletAmount{ + Address: users[1].FormattedAddress(), + Denom: chain.Config().Denom, + Amount: math.NewInt(1), + } + + delta := uint64(3) + + ch := make(chan error) + go func() { + new := amt + new.Amount = bal2.Add(math.NewInt(1)) + ch <- cosmos.PollForBalance(ctx, chain, delta, new) + }() + + err = chain.SendFunds(ctx, users[0].KeyName(), amt) + require.NoError(t, err) + require.NoError(t, <-ch) +} + +func testRangeBlockMessages(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + height, _ := chain.Height(ctx) + + _, err := sendTokens(ctx, chain, users[0], users[1], "", 1) + require.NoError(t, err) + + var bankMsgs []*banktypes.MsgSend + err = cosmos.RangeBlockMessages(ctx, chain.Config().EncodingConfig.InterfaceRegistry, chain.Validators[0].Client, height+1, func(msg sdk.Msg) bool { + found, ok := msg.(*banktypes.MsgSend) + if ok { + bankMsgs = append(bankMsgs, found) + } + return false + }) + require.NoError(t, err) +} + +func testAddingNode(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { + // This should be tested last or else Txs will fail on the new full node. + nodesAmt := len(chain.Nodes()) + chain.AddFullNodes(ctx, nil, 1) + require.Equal(t, nodesAmt+1, len(chain.Nodes())) +} + +func testBroadcaster(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { + from := users[0].FormattedAddress() + addr1 := "juno190g5j8aszqhvtg7cprmev8xcxs6csra7xnk3n3" + addr2 := "juno1a53udazy8ayufvy0s434pfwjcedzqv34q7p7vj" + + c1 := sdk.NewCoins(sdk.NewCoin(chain.Config().Denom, math.NewInt(1))) + c2 := sdk.NewCoins(sdk.NewCoin(chain.Config().Denom, math.NewInt(2))) + + b := cosmos.NewBroadcaster(t, chain) + + in := banktypes.Input{ + Address: from, + Coins: c1.Add(c2[0]), + } + out := []banktypes.Output{ + { + Address: addr1, + Coins: c1, + }, + { + Address: addr2, + Coins: c2, + }, + } + + txResp, err := cosmos.BroadcastTx( + ctx, + b, + users[0], + banktypes.NewMsgMultiSend([]banktypes.Input{in}, out), + ) + require.NoError(t, err) + require.NotEmpty(t, txResp.TxHash) + fmt.Printf("txResp: %+v\n", txResp) + + updatedBal1, err := chain.GetBalance(ctx, addr1, chain.Config().Denom) + require.NoError(t, err) + require.Equal(t, math.NewInt(1), updatedBal1) + + updatedBal2, err := chain.GetBalance(ctx, addr2, chain.Config().Denom) + require.NoError(t, err) + require.Equal(t, math.NewInt(2), updatedBal2) +} + +func testQueryCmd(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { + tn := chain.Validators[0] + stdout, stderr, err := tn.ExecQuery(ctx, "slashing", "params") + require.NoError(t, err) + require.NotEmpty(t, stdout) + require.Empty(t, stderr) +} + +func testHasCommand(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) { + tn := chain.Validators[0] + res := tn.HasCommand(ctx, "query") + require.True(t, res) + + if tn.IsAboveSDK47(ctx) { + require.True(t, tn.HasCommand(ctx, "genesis")) + } else { + // 45 does not have this + require.False(t, tn.HasCommand(ctx, "genesis")) + } + + require.True(t, tn.HasCommand(ctx, "tx", "ibc")) + require.True(t, tn.HasCommand(ctx, "q", "ibc")) + require.True(t, tn.HasCommand(ctx, "keys")) + require.True(t, tn.HasCommand(ctx, "help")) + require.True(t, tn.HasCommand(ctx, "tx", "bank", "send")) + + require.False(t, tn.HasCommand(ctx, "tx", "bank", "send2notrealcmd")) + require.False(t, tn.HasCommand(ctx, "incorrectcmd")) +} + +func testTokenFactory(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) { user := users[0] user2 := users[1] @@ -168,6 +376,21 @@ func TokenFactory(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, } +// helpers +func sendTokens(ctx context.Context, chain *cosmos.CosmosChain, from, to ibc.Wallet, token string, amount int64) (ibc.WalletAmount, error) { + if token == "" { + token = chain.Config().Denom + } + + sendAmt := ibc.WalletAmount{ + Address: to.FormattedAddress(), + Denom: token, + Amount: math.NewInt(amount), + } + err := chain.SendFunds(ctx, from.KeyName(), sendAmt) + return sendAmt, err +} + func validateBalance(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, user ibc.Wallet, tfDenom string, expected int64) { balance, err := chain.GetBalance(ctx, user.FormattedAddress(), tfDenom) require.NoError(t, err) diff --git a/examples/ibc/README.md b/examples/ibc/README.md new file mode 100644 index 000000000..3c2e45380 --- /dev/null +++ b/examples/ibc/README.md @@ -0,0 +1,23 @@ +# More Examples + +Interchain Accounts + +* [interchain_accounts demo](https://gist.github.com/Reecepbcups/8ec46ad83f6c9c1a152c10ab25774335) +* [ibc-go](https://github.com/cosmos/ibc-go/blob/main/e2e/tests/interchain_accounts/base_test.go) + +Interchain Queries + +* [interchain_queries demo](https://gist.github.com/Reecepbcups/d2a1155aaa3a95f5f6daf672e081e8b1) + +Packet Forward Middleware Test: + +* [Noble](https://github.com/strangelove-ventures/noble/blob/main/interchaintest/packet_forward_test.go) +* [Juno](https://github.com/CosmosContracts/juno/blob/main/interchaintest/module_pfm_test.go) + +IBC Client Update + +* [ibc-go](https://github.com/cosmos/ibc-go/blob/main/e2e/tests/core/02-client/client_test.go) + +IBC Transfers (ICS-20) + +* [ibc-go](https://github.com/cosmos/ibc-go/blob/main/e2e/tests/transfer/base_test.go) diff --git a/examples/ibc/learn_ibc_test.go b/examples/ibc/learn_ibc_test.go index e96403c7a..aba96a726 100644 --- a/examples/ibc/learn_ibc_test.go +++ b/examples/ibc/learn_ibc_test.go @@ -6,9 +6,12 @@ import ( "testing" "time" - "cosmossdk.io/math" transfertypes "github.com/cosmos/ibc-go/v7/modules/apps/transfer/types" + + "cosmossdk.io/math" + clienttypes "github.com/cosmos/ibc-go/v7/modules/core/02-client/types" "github.com/strangelove-ventures/interchaintest/v7" + "github.com/strangelove-ventures/interchaintest/v7/chain/cosmos" "github.com/strangelove-ventures/interchaintest/v7/ibc" "github.com/strangelove-ventures/interchaintest/v7/testreporter" "github.com/stretchr/testify/require" @@ -93,6 +96,9 @@ func TestLearn(t *testing.T) { require.NoError(t, err) osmoChannelID := osmoChannelInfo[0].ChannelID + height, err := osmosis.Height(ctx) + require.NoError(t, err) + // Send Transaction amountToSend := math.NewInt(1_000_000) dstAddress := osmosisUser.FormattedAddress() @@ -122,4 +128,13 @@ func TestLearn(t *testing.T) { osmosUserBalNew, err := osmosis.GetBalance(ctx, osmosisUser.FormattedAddress(), dstIbcDenom) require.NoError(t, err) require.True(t, osmosUserBalNew.Equal(amountToSend)) + + // Validate light client + chain := osmosis.(*cosmos.CosmosChain) + reg := chain.Config().EncodingConfig.InterfaceRegistry + msg, err := cosmos.PollForMessage[*clienttypes.MsgUpdateClient](ctx, chain, reg, height, height+10, nil) + require.NoError(t, err) + + require.Equal(t, "07-tendermint-0", msg.ClientId) + require.NotEmpty(t, msg.Signer) }