Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: add software-upgrade and recover-client helper #4

Merged
merged 9 commits into from
Dec 6, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ ifeq (,$(shell which heighliner))
@echo heighliner not found. https://github.com/strangelove-ventures/heighliner
else
@echo "🤖 Building image..."
@heighliner build --chain noble-authority-simd --local 1> /dev/null
@heighliner build --file ./e2e/chains.yaml --chain noble-authority-simd --local
boojamya marked this conversation as resolved.
Show resolved Hide resolved
@echo "✅ Completed build!"
endif

Expand Down
124 changes: 124 additions & 0 deletions client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,35 @@ package cli
import (
"fmt"

"cosmossdk.io/x/upgrade/plan"
upgradetypes "cosmossdk.io/x/upgrade/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
authclient "github.com/cosmos/cosmos-sdk/x/auth/client"
clienttypes "github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
"github.com/noble-assets/authority/types"
"github.com/spf13/cobra"
)

const (
FlagUpgradeHeight = "upgrade-height"
FlagUpgradeInfo = "upgrade-info"
FlagNoValidate = "no-validate"
FlagNoChecksumRequired = "no-checksum-required"
FlagDaemonName = "daemon-name"
)

func GetTxCmd() *cobra.Command {
cmd := &cobra.Command{
Use: types.ModuleName,
Short: fmt.Sprintf("Transactions commands for the %s module", types.ModuleName),
}

cmd.AddCommand(NewCmdExecute())
cmd.AddCommand(NewCmdSubmitUpgrade())
cmd.AddCommand(newSubmitRecoverClientCmd())
boojamya marked this conversation as resolved.
Show resolved Hide resolved

return cmd
}
Expand Down Expand Up @@ -54,3 +68,113 @@ func NewCmdExecute() *cobra.Command {

return cmd
}

// NewCmdSoftwareUpgrade is a helper for scheduling a software upgrade.
//
// This command has been adapted from the Cosmos SDK implementation.
// https://github.com/cosmos/cosmos-sdk/blob/x/upgrade/v0.1.4/x/upgrade/client/cli/tx.go#L46-L133
func NewCmdSubmitUpgrade() *cobra.Command {
cmd := &cobra.Command{
Use: "software-upgrade <name> [--upgrade-height <height>] [--upgrade-info <info>] [flags]",
Args: cobra.ExactArgs(1),
Short: "Helper for scheduling a software upgrade",
Long: "Helper for scheduling a software upgrade.\n\n" +
"You can additionally include upgrade info via a flag to reference pre-built binaries, documentation, etc.\n" +
"https://docs.cosmos.network/main/build/tooling/cosmovisor",
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

name := args[0]
p, err := parsePlan(cmd.Flags(), name)
if err != nil {
return err
}

noValidate, err := cmd.Flags().GetBool(FlagNoValidate)
if err != nil {
return err
}

if !noValidate {
daemonName, err := cmd.Flags().GetString(FlagDaemonName)
if err != nil {
return err
}

noChecksum, err := cmd.Flags().GetBool(FlagNoChecksumRequired)
if err != nil {
return err
}

var planInfo *plan.Info
if planInfo, err = plan.ParseInfo(p.Info, plan.ParseOptionEnforceChecksum(!noChecksum)); err != nil {
return err
}

if err = planInfo.ValidateFull(daemonName); err != nil {
return err
}
}

msgExecute := types.NewMsgExecute(
clientCtx.FromAddress.String(),
[]sdk.Msg{
&upgradetypes.MsgSoftwareUpgrade{
Authority: types.ModuleAddress.String(),
Plan: p,
},
})

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msgExecute)
},
}

cmd.Flags().Int64(FlagUpgradeHeight, 0, "The height at which the upgrade must happen")
cmd.Flags().String(FlagUpgradeInfo, "", "Info for the upgrade plan such as new version download urls, etc.")
cmd.Flags().Bool(FlagNoValidate, false, "Skip validation of the upgrade info (dangerous!)")
cmd.Flags().Bool(FlagNoChecksumRequired, false, "Skip requirement of checksums for binaries in the upgrade info")
cmd.Flags().String(FlagDaemonName, getDefaultDaemonName(), "The name of the executable being upgraded (for upgrade-info validation). Default is the DAEMON_NAME env var if set, or else this executable")

flags.AddTxFlagsToCmd(cmd)

return cmd
}

// NewCmdRecoverClient is a helper for recovering an expired client.
//
// This command has been adapted from the IBC-Go implementation.
// https://github.com/cosmos/ibc-go/blob/v8.5.2/modules/core/02-client/client/cli/tx.go#L248-L303
func newSubmitRecoverClientCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "recover-client [subject-client-id] [substitute-client-id] [flags]",
Args: cobra.ExactArgs(2),
Short: "Helper for recovering an expired client",
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}

subjectClientID, substituteClientID := args[0], args[1]

authority := types.ModuleAddress.String()

msg := clienttypes.NewMsgRecoverClient(authority, subjectClientID, substituteClientID)

if err = msg.ValidateBasic(); err != nil {
return fmt.Errorf("error validating %T: %w", clienttypes.MsgRecoverClient{}, err)
}

msgExecute := types.NewMsgExecute(clientCtx.FromAddress.String(), []sdk.Msg{msg})

return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msgExecute)
},
}

flags.AddTxFlagsToCmd(cmd)

return cmd
}
39 changes: 39 additions & 0 deletions client/cli/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package cli
boojamya marked this conversation as resolved.
Show resolved Hide resolved

import (
"os"
"path/filepath"

"github.com/spf13/pflag"

"cosmossdk.io/x/upgrade/types"
)

// parsePlan is copied from the Cosmos SDK because it is not exported.
//
// https://github.com/cosmos/cosmos-sdk/blob/x/upgrade/v0.1.4/x/upgrade/client/cli/parse.go#L9-L21
func parsePlan(fs *pflag.FlagSet, name string) (types.Plan, error) {
height, err := fs.GetInt64(FlagUpgradeHeight)
if err != nil {
return types.Plan{}, err
}

info, err := fs.GetString(FlagUpgradeInfo)
if err != nil {
return types.Plan{}, err
}

return types.Plan{Name: name, Height: height, Info: info}, nil
}

// getDefaultDaemonName is copied from the Cosmos SDK because it is not exported.
//
// https://github.com/cosmos/cosmos-sdk/blob/x/upgrade/v0.1.4/x/upgrade/client/cli/tx.go#L184-L194
func getDefaultDaemonName() string {
// DAEMON_NAME is specifically used here to correspond with the Cosmovisor setup env vars.
name := os.Getenv("DAEMON_NAME")
if len(name) == 0 {
_, name = filepath.Split(os.Args[0])
}
return name
}
File renamed without changes.
26 changes: 0 additions & 26 deletions e2e/clients.json

This file was deleted.

52 changes: 40 additions & 12 deletions e2e/clients_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ package e2e

import (
_ "embed"
"path"
"testing"
"time"

"cosmossdk.io/math"

Expand All @@ -19,15 +19,12 @@ import (
"github.com/stretchr/testify/require"
)

//go:embed clients.json
var Clients []byte

// TestClientSubstitution tests the module's ability to substitute IBC clients.
func TestClientSubstitution(t *testing.T) {
t.Parallel()

var wrapper Wrapper
ctx, reporter, rly := Suite(t, &wrapper, true)
ctx, logger, reporter, rly := Suite(t, &wrapper, true)
validator := wrapper.chain.Validators[0]

nobleChainID, gaiaChainID := wrapper.chain.Config().ChainID, wrapper.gaia.Config().ChainID
Expand All @@ -36,10 +33,18 @@ func TestClientSubstitution(t *testing.T) {
err := rly.GeneratePath(ctx, reporter, nobleChainID, gaiaChainID, pathName)
require.NoError(t, err)

tp := 20 * time.Second
err = rly.CreateClient(ctx, reporter, nobleChainID, gaiaChainID, pathName, ibc.CreateClientOptions{
TrustingPeriod: "30s",
TrustingPeriod: tp.String(),
})
require.NoError(t, err)

nobleClients, err := rly.GetClients(ctx, reporter, nobleChainID)
require.NoError(t, err)
require.Len(t, nobleClients, 2) // ignore 09-localhost client

nobleClientToExpire := nobleClients[0]

boojamya marked this conversation as resolved.
Show resolved Hide resolved
err = rly.CreateClient(ctx, reporter, gaiaChainID, nobleChainID, pathName, ibc.CreateClientOptions{})
require.NoError(t, err)
require.NoError(t, testutil.WaitForBlocks(ctx, 1, wrapper.chain, wrapper.gaia))
Expand All @@ -51,8 +56,12 @@ func TestClientSubstitution(t *testing.T) {
err = rly.CreateChannel(ctx, reporter, pathName, ibc.DefaultChannelOpts())
require.NoError(t, err)

timer := time.NewTimer(tp + 2*time.Second)

users := interchaintest.GetAndFundTestUsers(t, ctx, "user", math.NewInt(5_000_000), wrapper.chain, wrapper.gaia)
require.NoError(t, testutil.WaitForBlocks(ctx, 10, wrapper.chain, wrapper.gaia))

logger.Info("waiting for client to expire...")
<-timer.C

_, err = validator.SendIBCTransfer(ctx, "channel-0", users[0].KeyName(), ibc.WalletAmount{
Address: users[1].FormattedAddress(),
Expand All @@ -61,13 +70,32 @@ func TestClientSubstitution(t *testing.T) {
}, ibc.TransferOptions{})
require.ErrorContains(t, err, "client state is not active")

res := rly.Exec(ctx, reporter, []string{"rly", "tx", "client", nobleChainID, gaiaChainID, pathName, "--override", "--home", rly.HomeDir()}, nil)
require.NoError(t, res.Err)
err = rly.CreateClient(ctx, reporter, nobleChainID, gaiaChainID, pathName, ibc.CreateClientOptions{Override: true})
require.NoError(t, err)

nobleClients, err = rly.GetClients(ctx, reporter, nobleChainID)
require.NoError(t, err)
require.Len(t, nobleClients, 3) // ignore 09-localhost client

newNobleClient := nobleClients[1]

boojamya marked this conversation as resolved.
Show resolved Hide resolved
notAuthorized := interchaintest.GetAndFundTestUsers(t, ctx, "wallet", math.NewInt(100000), wrapper.chain)[0]

cmd := []string{"authority", "recover-client", nobleClientToExpire.ClientID, newNobleClient.ClientID}

// broadcast from un-authorized account
_, err = validator.ExecTx(
ctx,
notAuthorized.KeyName(),
cmd...,
)
require.ErrorContains(t, err, "signer is not authority")

require.NoError(t, validator.WriteFile(ctx, Clients, "clients.json"))
// broadcast from authorized authority account
_, err = validator.ExecTx(
ctx, wrapper.owner.KeyName(),
"authority", "execute", path.Join(validator.HomeDir(), "clients.json"),
ctx,
wrapper.owner.KeyName(),
cmd...,
)
require.NoError(t, err)

Expand Down
2 changes: 1 addition & 1 deletion e2e/fee_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestBeginBlocker(t *testing.T) {
t.Parallel()

var wrapper Wrapper
ctx, _, _ := Suite(t, &wrapper, false)
ctx, _, _, _ := Suite(t, &wrapper, false)
validator := wrapper.chain.Validators[0]

oldBalance, err := wrapper.chain.BankQueryAllBalances(ctx, wrapper.owner.FormattedAddress())
Expand Down
2 changes: 1 addition & 1 deletion e2e/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ require (
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/iavl v1.2.0 // indirect
github.com/cosmos/ibc-go/modules/capability v1.0.1 // indirect
github.com/cosmos/ibc-go/v8 v8.5.1 // indirect
github.com/cosmos/ibc-go/v8 v8.5.2 // indirect
github.com/cosmos/ics23/go v0.11.0 // indirect
github.com/cosmos/interchain-security/v5 v5.1.1 // indirect
github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect
Expand Down
4 changes: 2 additions & 2 deletions e2e/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -390,8 +390,8 @@ github.com/cosmos/iavl v1.2.0 h1:kVxTmjTh4k0Dh1VNL046v6BXqKziqMDzxo93oh3kOfM=
github.com/cosmos/iavl v1.2.0/go.mod h1:HidWWLVAtODJqFD6Hbne2Y0q3SdxByJepHUOeoH4LiI=
github.com/cosmos/ibc-go/modules/capability v1.0.1 h1:ibwhrpJ3SftEEZRxCRkH0fQZ9svjthrX2+oXdZvzgGI=
github.com/cosmos/ibc-go/modules/capability v1.0.1/go.mod h1:rquyOV262nGJplkumH+/LeYs04P3eV8oB7ZM4Ygqk4E=
github.com/cosmos/ibc-go/v8 v8.5.1 h1:3JleEMKBjRKa3FeTKt4fjg22za/qygLBo7mDkoYTNBs=
github.com/cosmos/ibc-go/v8 v8.5.1/go.mod h1:P5hkAvq0Qbg0h18uLxDVA9q1kOJ0l36htMsskiNwXbo=
github.com/cosmos/ibc-go/v8 v8.5.2 h1:27s9oeD2AxLQF3e9BQsYt9doONyZ7FwZi/qkBv6Sdks=
github.com/cosmos/ibc-go/v8 v8.5.2/go.mod h1:P5hkAvq0Qbg0h18uLxDVA9q1kOJ0l36htMsskiNwXbo=
github.com/cosmos/ics23/go v0.11.0 h1:jk5skjT0TqX5e5QJbEnwXIS2yI2vnmLOgpQPeM5RtnU=
github.com/cosmos/ics23/go v0.11.0/go.mod h1:A8OjxPE67hHST4Icw94hOxxFEJMBG031xIGF/JHNIY0=
github.com/cosmos/interchain-security/v5 v5.1.1 h1:xmRRMeE4xoc+JAZUh0XzXFYWaGBtzFFj5SETuOgnEnY=
Expand Down
2 changes: 1 addition & 1 deletion e2e/ownership_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func TestOwnershipTransfer(t *testing.T) {
t.Parallel()

var wrapper Wrapper
ctx, _, _ := Suite(t, &wrapper, false)
ctx, _, _, _ := Suite(t, &wrapper, false)
validator := wrapper.chain.Validators[0]

EnsureOwner(t, wrapper, ctx, wrapper.owner.FormattedAddress())
Expand Down
3 changes: 2 additions & 1 deletion e2e/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ import (
var Params []byte

// TestParameterUpdate tests the module's ability to modify module parameters.
// This test uses the "execute" command to test broadcasting arbitrary messages.
func TestParameterUpdate(t *testing.T) {
t.Parallel()

var wrapper Wrapper
ctx, _, _ := Suite(t, &wrapper, false)
ctx, _, _, _ := Suite(t, &wrapper, false)
validator := wrapper.chain.Validators[0]

EnsureParams(t, wrapper, ctx, 0)
Expand Down
Loading
Loading