Skip to content

Commit

Permalink
feat: add software-upgrade and recover-client helper (#4)
Browse files Browse the repository at this point in the history
Co-authored-by: John Letey <[email protected]>
  • Loading branch information
boojamya and johnletey authored Dec 6, 2024
1 parent c8100bb commit 63853fe
Show file tree
Hide file tree
Showing 20 changed files with 341 additions and 139 deletions.
2 changes: 2 additions & 0 deletions .changelog/v1.0.1/improvements/4-add-helper-commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- Add helper commands for broadcasting software upgrade and recover client
messages. ([\#4](https://github.com/noble-assets/authority/pull/4))
3 changes: 3 additions & 0 deletions .changelog/v1.0.1/summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
*Dec 6, 2024*

This is a non-consensus breaking patch release to the v1 line.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
# CHANGELOG

## v1.0.1

*Dec 6, 2024*

This is a non-consensus breaking patch release to the v1 line.

### IMPROVEMENTS

- Add helper commands for broadcasting software upgrade and recover client
messages. ([\#4](https://github.com/noble-assets/authority/pull/4))

## v1.0.0

*Nov 10, 2024*
Expand Down
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
@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(NewCmdSoftwareUpgrade())
cmd.AddCommand(NewCmdRecoverClient())

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 NewCmdSoftwareUpgrade() *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 NewCmdRecoverClient() *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
}
45 changes: 45 additions & 0 deletions client/cli/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2024 NASD Inc.
//
// Use of this source code is governed by a BSL-style
// license that can be found in the LICENSE file or at
// https://mariadb.com/bsl11.

package cli

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]

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]

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
Loading

0 comments on commit 63853fe

Please sign in to comment.