chore: set tools to main (#23687)

This commit is contained in:
Alex | Interchain Labs 2025-02-12 17:13:41 -05:00 committed by GitHub
parent 224e68e1e5
commit 98f2048979
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
84 changed files with 4815 additions and 3932 deletions

View File

@ -31,6 +31,11 @@ require (
pgregory.net/rapid v1.1.0
)
require (
github.com/cosmos/go-bip39 v1.0.0
github.com/tendermint/go-amino v0.16.0
)
require (
cloud.google.com/go v0.112.1 // indirect
cloud.google.com/go/compute/metadata v0.5.2 // indirect
@ -66,7 +71,6 @@ require (
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/cometbft/cometbft-db v0.14.1 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/go-bip39 v1.0.0 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/iavl v1.2.2 // indirect
github.com/cosmos/ics23/go v0.11.0 // indirect
@ -169,7 +173,6 @@ require (
github.com/spf13/viper v1.19.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect

View File

@ -31,9 +31,17 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased]
## [v0.2.0-rc.2](https://github.com/cosmos/cosmos-sdk/releases/tag/tools/confix/v0.2.0-rc.2) - 2025-01-16
* [#23238](https://github.com/cosmos/cosmos-sdk/pull/23238) Loosen `app.toml` validation for consistency between baseapp chains and v2 chains.
## [v0.2.0-rc.1](https://github.com/cosmos/cosmos-sdk/releases/tag/tools/confix/v0.2.0-rc.1) - 2024-12-18
* [#21052](https://github.com/cosmos/cosmos-sdk/pull/21052) Add a migration to v2 config.
## [v0.1.2](https://github.com/cosmos/cosmos-sdk/releases/tag/tools/confix/v0.1.2) - 2024-08-13
* (confix) [#21202](https://github.com/cosmos/cosmos-sdk/pull/21202) Allow customization of migration `PlanBuilder`.
* [#21202](https://github.com/cosmos/cosmos-sdk/pull/21202) Allow customization of migration `PlanBuilder`.
## [v0.1.1](https://github.com/cosmos/cosmos-sdk/releases/tag/tools/confix/v0.1.1) - 2023-12-11

View File

@ -4,9 +4,11 @@ all: confix condiff test
confix:
go build -mod=readonly ./cmd/confix
@echo "confix binary has been successfully built in tools/confix/confix"
condiff:
go build -mod=readonly ./cmd/condiff
@echo "condiff binary has been successfully built in tools/confix/condiff"
test:
go test -mod=readonly -race ./...

View File

@ -23,7 +23,7 @@ import "cosmossdk.io/tools/confix/cmd"
Find the following line:
```go
initRootCmd(rootCmd, encodingConfig)
initRootCmd(rootCmd, moduleManager)
```
After that line, add the following:
@ -39,6 +39,11 @@ An implementation example can be found in `simapp`.
The command will be available as `simd config`.
:::tip
Using confix directly in the application can have less features than using it standalone.
This is because confix is versioned with the SDK, while `latest` is the standalone version.
:::
### Using Confix Standalone
To use Confix standalone, without having to add it in your application, install it with the following command:
@ -93,14 +98,16 @@ confix set ~/.simapp/config/client.toml chain-id "foo-1" # sets the value chain-
### Migrate
Migrate a configuration file to a new version, e.g.:
Migrate a configuration file to a new version, config type defaults to `app.toml`, if you want to change it to `client.toml`, please indicate it by adding the optional parameter, e.g.:
```shell
simd config migrate v0.47 # migrates defaultHome/config/app.toml to the latest v0.47 config
simd config migrate v0.50 # migrates defaultHome/config/app.toml to the latest v0.50 config
simd config migrate v0.50 --client # migrates defaultHome/config/client.toml to the latest v0.50 config
```
```shell
confix migrate v0.47 ~/.simapp/config/app.toml # migrate ~/.simapp/config/app.toml to the latest v0.47 config
confix migrate v0.50 ~/.simapp/config/app.toml # migrate ~/.simapp/config/app.toml to the latest v0.50 config
confix migrate v0.50 ~/.simapp/config/client.toml --client # migrate ~/.simapp/config/client.toml to the latest v0.50 config
```
### Diff
@ -109,10 +116,12 @@ Get the diff between a given configuration file and the default configuration fi
```shell
simd config diff v0.47 # gets the diff between defaultHome/config/app.toml and the latest v0.47 config
simd config diff v0.47 --client # gets the diff between defaultHome/config/client.toml and the latest v0.47 config
```
```shell
confix diff v0.47 ~/.simapp/config/app.toml # gets the diff between ~/.simapp/config/app.toml and the latest v0.47 config
confix diff v0.47 ~/.simapp/config/client.toml --client # gets the diff between ~/.simapp/config/client.toml and the latest v0.47 config
```
### View
@ -129,9 +138,19 @@ confix view ~/.simapp/config/client.toml # views the current app client conf
### Maintainer
At each SDK modification of the default configuration, add the default SDK config under `data/v0.XX-app.toml`.
At each SDK modification of the default configuration, add the default SDK config under `data/vXX-app.toml`.
This allows users to use the tool standalone.
### Compatibility
The recommended standalone version is `latest`, which is using the latest development version of the Confix.
| SDK Version | Confix Version |
| ----------- | -------------- |
| v0.50 | v0.1.x |
| v0.52 | v0.2.x |
| v2 | v0.2.x |
## Credits
This project is based on the [CometBFT RFC 019](https://github.com/cometbft/cometbft/blob/5013bc3f4a6d64dcc2bf02ccc002ebc9881c62e4/docs/rfc/rfc-019-config-version.md) and their own implementation of [confix](https://github.com/cometbft/cometbft/blob/v0.36.x/scripts/confix/confix.go).
This project is based on the [CometBFT RFC 019](https://github.com/cometbft/cometbft/blob/5013bc3f4a6d64dcc2bf02ccc002ebc9881c62e4/docs/rfc/rfc-019-config-version.md) and their never released own implementation of [confix](https://github.com/cometbft/cometbft/blob/v0.36.x/scripts/confix/confix.go).

View File

@ -4,6 +4,10 @@ import (
"github.com/spf13/cobra"
)
const (
tomlSuffix = ".toml"
)
// ConfigCommand contains all the confix commands
// These command can be used to interactively update an application config value.
func ConfigCommand() *cobra.Command {

View File

@ -1,9 +1,12 @@
package cmd
import (
"errors"
"fmt"
"maps"
"path/filepath"
"slices"
"strings"
"github.com/spf13/cobra"
@ -12,22 +15,31 @@ import (
"github.com/cosmos/cosmos-sdk/client"
)
// DiffCommand creates a new command for comparing configuration files
func DiffCommand() *cobra.Command {
return &cobra.Command{
Use: "diff [target-version] <app-toml-path>",
Short: "Outputs all config values that are different from the app.toml defaults.",
cmd := &cobra.Command{
Use: "diff [target-version] <config-path>",
Short: "Outputs all config values that are different from the default.",
Long: "This command compares the specified configuration file (app.toml or client.toml) with the defaults and outputs any differences.",
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var filename string
var configPath string
clientCtx := client.GetClientContextFromCmd(cmd)
switch {
case len(args) > 1:
filename = args[1]
configPath = args[1]
case clientCtx.HomeDir != "":
filename = fmt.Sprintf("%s/config/app.toml", clientCtx.HomeDir)
configPath = filepath.Join(clientCtx.HomeDir, "config", "app.toml")
default:
return fmt.Errorf("must provide a path to the app.toml file")
return errors.New("must provide a path to the app.toml or client.toml")
}
configType := confix.AppConfigType
if ok, _ := cmd.Flags().GetBool(confix.ClientConfigType); ok {
configPath = strings.ReplaceAll(configPath, "app.toml", "client.toml") // for the case we are using the home dir of client ctx
configType = confix.ClientConfigType
} else if strings.HasSuffix(configPath, "client.toml") {
return errors.New("app.toml file expected, got client.toml, use --client flag to diff client.toml")
}
targetVersion := args[0]
@ -35,14 +47,14 @@ func DiffCommand() *cobra.Command {
return fmt.Errorf("unknown version %q, supported versions are: %q", targetVersion, slices.Collect(maps.Keys(confix.Migrations)))
}
targetVersionFile, err := confix.LoadLocalConfig(targetVersion)
targetVersionFile, err := confix.LoadLocalConfig(targetVersion, configType)
if err != nil {
panic(fmt.Errorf("failed to load internal config: %w", err))
return fmt.Errorf("failed to load internal config: %w", err)
}
rawFile, err := confix.LoadConfig(filename)
rawFile, err := confix.LoadConfig(configPath)
if err != nil {
return fmt.Errorf("failed to load config: %v", err)
return fmt.Errorf("failed to load config: %w", err)
}
diff := confix.DiffValues(rawFile, targetVersionFile)
@ -58,4 +70,8 @@ func DiffCommand() *cobra.Command {
return nil
},
}
cmd.Flags().Bool(confix.ClientConfigType, false, "diff client.toml instead of app.toml")
return cmd
}

View File

@ -2,9 +2,12 @@ package cmd
import (
"context"
"errors"
"fmt"
"maps"
"path/filepath"
"slices"
"strings"
"github.com/spf13/cobra"
@ -21,22 +24,38 @@ var (
func MigrateCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "migrate [target-version] <app-toml-path> (options)",
Short: "Migrate Cosmos SDK app configuration file to the specified version",
Long: `Migrate the contents of the Cosmos SDK app configuration (app.toml) to the specified version.
Use: "migrate [target-version] <config-path>",
Short: "Migrate Cosmos SDK configuration file to the specified version",
Long: `Migrate the contents of the Cosmos SDK configuration (app.toml or client.toml) to the specified version. Configuration type is app by default.
The output is written in-place unless --stdout is provided.
In case of any error in updating the file, no output is written.`,
Args: cobra.MinimumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
var filename string
var configPath string
clientCtx := client.GetClientContextFromCmd(cmd)
configType := confix.AppConfigType
isClient, _ := cmd.Flags().GetBool(confix.ClientConfigType)
if isClient {
configType = confix.ClientConfigType
}
switch {
case len(args) > 1:
filename = args[1]
configPath = args[1]
case clientCtx.HomeDir != "":
filename = fmt.Sprintf("%s/config/app.toml", clientCtx.HomeDir)
suffix := "app.toml"
if isClient {
suffix = "client.toml"
}
configPath = filepath.Join(clientCtx.HomeDir, "config", suffix)
default:
return fmt.Errorf("must provide a path to the app.toml file")
return errors.New("must provide a path to the app.toml or client.toml")
}
if strings.HasSuffix(configPath, "client.toml") && !isClient {
return errors.New("app.toml file expected, got client.toml, use --client flag to migrate client.toml")
}
targetVersion := args[0]
@ -45,9 +64,9 @@ In case of any error in updating the file, no output is written.`,
return fmt.Errorf("unknown version %q, supported versions are: %q", targetVersion, slices.Collect(maps.Keys(confix.Migrations)))
}
rawFile, err := confix.LoadConfig(filename)
rawFile, err := confix.LoadConfig(configPath)
if err != nil {
return fmt.Errorf("failed to load config: %v", err)
return fmt.Errorf("failed to load config: %w", err)
}
ctx := context.Background()
@ -55,12 +74,15 @@ In case of any error in updating the file, no output is written.`,
ctx = confix.WithLogWriter(ctx, cmd.ErrOrStderr())
}
outputPath := filename
outputPath := configPath
if FlagStdOut {
outputPath = ""
}
if err := confix.Upgrade(ctx, plan(rawFile, targetVersion), filename, outputPath, FlagSkipValidate); err != nil {
// get transformation steps and formatDoc in which plan need to be applied
steps, formatDoc := plan(rawFile, targetVersion, configType)
if err := confix.Upgrade(ctx, steps, formatDoc, configPath, outputPath, FlagSkipValidate); err != nil {
return fmt.Errorf("failed to migrate config: %w", err)
}
@ -71,6 +93,7 @@ In case of any error in updating the file, no output is written.`,
cmd.Flags().BoolVar(&FlagStdOut, "stdout", false, "print the updated config to stdout")
cmd.Flags().BoolVar(&FlagVerbose, "verbose", false, "log changes to stderr")
cmd.Flags().BoolVar(&FlagSkipValidate, "skip-validate", false, "skip configuration validation (allows to migrate unknown configurations)")
cmd.Flags().Bool(confix.ClientConfigType, false, "migrate client.toml instead of app.toml")
return cmd
}

View File

@ -1,7 +1,7 @@
package cmd_test
import (
"fmt"
"path/filepath"
"strings"
"testing"
@ -9,30 +9,31 @@ import (
"cosmossdk.io/tools/confix/cmd"
"github.com/cosmos/cosmos-sdk/client"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
)
func TestMigradeCmd(t *testing.T) {
func TestMigrateCmd(t *testing.T) {
clientCtx, cleanup := initClientContext(t)
defer cleanup()
_, err := clitestutil.ExecTestCLICmd(client.Context{}, cmd.MigrateCommand(), []string{"v0.0"})
assert.ErrorContains(t, err, "must provide a path to the app.toml file")
_, err = clitestutil.ExecTestCLICmd(clientCtx, cmd.MigrateCommand(), []string{"v0.0"})
_, err := clitestutil.ExecTestCLICmd(clientCtx, cmd.MigrateCommand(), []string{"v0.0"})
assert.ErrorContains(t, err, "unknown version")
// clientCtx does not create app.toml, so this should fail
_, err = clitestutil.ExecTestCLICmd(clientCtx, cmd.MigrateCommand(), []string{"v0.45"})
assert.ErrorContains(t, err, "no such file or directory")
// try to migrate from client.toml it should fail without --skip-validate
_, err = clitestutil.ExecTestCLICmd(clientCtx, cmd.MigrateCommand(), []string{"v0.46", fmt.Sprintf("%s/config/client.toml", clientCtx.HomeDir)})
// try to migrate from unsupported.toml it should fail without --skip-validate
_, err = clitestutil.ExecTestCLICmd(clientCtx, cmd.MigrateCommand(), []string{"v0.46", filepath.Join(clientCtx.HomeDir, "config", "unsupported.toml")})
assert.ErrorContains(t, err, "failed to migrate config")
// try to migrate from client.toml - it should work and give us a big diff
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd.MigrateCommand(), []string{"v0.46", fmt.Sprintf("%s/config/client.toml", clientCtx.HomeDir), "--skip-validate", "--verbose"})
// try to migrate from unsupported.toml - it should work and give us a big diff
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd.MigrateCommand(), []string{"v0.46", filepath.Join(clientCtx.HomeDir, "config", "unsupported.toml"), "--skip-validate", "--verbose"})
assert.NilError(t, err)
assert.Assert(t, strings.Contains(out.String(), "add app-db-backend key"))
// this should work
out, err = clitestutil.ExecTestCLICmd(clientCtx, cmd.MigrateCommand(), []string{"v0.52", filepath.Join(clientCtx.HomeDir, "config", "client.toml"), "--client", "--verbose"})
assert.NilError(t, err)
assert.Assert(t, strings.Contains(out.String(), "add keyring-default-keyname key"))
}

View File

@ -4,6 +4,7 @@ import (
"context"
"errors"
"fmt"
"path/filepath"
"strings"
"github.com/creachadair/tomledit"
@ -19,7 +20,7 @@ import (
// SetCommand returns a CLI command to interactively update an application config value.
func SetCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "set [config] [key] [value]",
Use: "set <config> <key> <value>",
Short: "Set an application config value",
Long: "Set an application config value. The [config] argument must be the path of the file when using the `confix` tool standalone, otherwise it must be the name of the config file without the .toml extension.",
Args: cobra.ExactArgs(3),
@ -30,7 +31,7 @@ func SetCommand() *cobra.Command {
clientCtx := client.GetClientContextFromCmd(cmd)
if clientCtx.HomeDir != "" {
filename = fmt.Sprintf("%s/config/%s.toml", clientCtx.HomeDir, filename)
filename = filepath.Join(clientCtx.HomeDir, "config", filename+tomlSuffix)
}
plan := transform.Plan{
@ -72,7 +73,12 @@ func SetCommand() *cobra.Command {
ctx = confix.WithLogWriter(ctx, cmd.ErrOrStderr())
}
return confix.Upgrade(ctx, plan, filename, outputPath, FlagSkipValidate)
doc, err := confix.LoadConfig(filename)
if err != nil {
return err
}
return confix.Upgrade(ctx, plan, doc, filename, outputPath, FlagSkipValidate)
},
}
@ -86,7 +92,7 @@ func SetCommand() *cobra.Command {
// GetCommand returns a CLI command to interactively get an application config value.
func GetCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "get [config] [key]",
Use: "get <config> <key>",
Short: "Get an application config value",
Long: "Get an application config value. The [config] argument must be the path of the file when using the `confix` tool standalone, otherwise it must be the name of the config file without the .toml extension.",
Args: cobra.ExactArgs(2),
@ -97,7 +103,7 @@ func GetCommand() *cobra.Command {
clientCtx := client.GetClientContextFromCmd(cmd)
if clientCtx.HomeDir != "" {
filename = fmt.Sprintf("%s/config/%s.toml", clientCtx.HomeDir, filename)
filename = filepath.Join(clientCtx.HomeDir, "config", filename+tomlSuffix)
}
doc, err := confix.LoadConfig(filename)

View File

@ -3,6 +3,7 @@ package cmd_test
import (
"fmt"
"os"
"path/filepath"
"strings"
"testing"
@ -17,6 +18,7 @@ import (
// initClientContext initiates client Context for tests
func initClientContext(t *testing.T) (client.Context, func()) {
t.Helper()
home := t.TempDir()
chainID := "test-chain"
clientCtx := client.Context{}.
@ -26,6 +28,8 @@ func initClientContext(t *testing.T) (client.Context, func()) {
clientCtx, err := config.ReadFromClientConfig(clientCtx)
assert.NilError(t, err)
assert.Equal(t, clientCtx.ChainID, chainID)
_ = os.Link(filepath.Join(home, "config", "client.toml"), filepath.Join(home, "config", "unsupported.toml"))
return clientCtx, func() { _ = os.RemoveAll(home) }
}

View File

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"github.com/pelletier/go-toml/v2"
"github.com/spf13/cobra"
@ -12,10 +13,10 @@ import (
)
func ViewCommand() *cobra.Command {
flagOutputFomat := "output-format"
flagOutputFormat := "output-format"
cmd := &cobra.Command{
Use: "view [config]",
Use: "view <config>",
Short: "View the config file",
Long: "View the config file. The [config] argument must be the path of the file when using the `confix` tool standalone, otherwise it must be the name of the config file without the .toml extension.",
Args: cobra.ExactArgs(1),
@ -24,7 +25,7 @@ func ViewCommand() *cobra.Command {
clientCtx := client.GetClientContextFromCmd(cmd)
if clientCtx.HomeDir != "" {
filename = fmt.Sprintf("%s/config/%s.toml", clientCtx.HomeDir, filename)
filename = filepath.Join(clientCtx.HomeDir, "config", filename+tomlSuffix)
}
file, err := os.ReadFile(filename)
@ -32,7 +33,7 @@ func ViewCommand() *cobra.Command {
return err
}
if format, _ := cmd.Flags().GetString(flagOutputFomat); format == "toml" {
if format, _ := cmd.Flags().GetString(flagOutputFormat); format == "toml" {
cmd.Println(string(file))
return nil
}
@ -49,7 +50,7 @@ func ViewCommand() *cobra.Command {
}
// output flag
cmd.Flags().String(flagOutputFomat, "toml", "Output format (json|toml)")
cmd.Flags().String(flagOutputFormat, "toml", "Output format (json|toml)")
return cmd
}

View File

@ -44,7 +44,7 @@ halt-time = 0
# It has no bearing on application state pruning which is determined by the
# "pruning-*" configurations.
#
# Note: Tendermint block pruning is dependant on this parameter in conjunction
# Note: Tendermint block pruning is dependent on this parameter in conjunction
# with the unbonding (safety threshold) period, state pruning and state sync
# snapshot parameters to determine the correct minimum value of
# ResponseCommit.RetainHeight.

View File

@ -43,7 +43,7 @@ halt-time = 0
# It has no bearing on application state pruning which is determined by the
# "pruning-*" configurations.
#
# Note: Tendermint block pruning is dependant on this parameter in conjunction
# Note: Tendermint block pruning is dependent on this parameter in conjunction
# with the unbonding (safety threshold) period, state pruning and state sync
# snapshot parameters to determine the correct minimum value of
# ResponseCommit.RetainHeight.
@ -172,7 +172,7 @@ enable-fee-suggestion = false
# GasToSuggest defines gas limit when calculating the fee
gas-to-suggest = 200000
# DenomToSuggest defines the defult denom for fee suggestion.
# DenomToSuggest defines the default denom for fee suggestion.
# Price must be in minimum-gas-prices.
denom-to-suggest = "uatom"

View File

@ -43,7 +43,7 @@ halt-time = 0
# It has no bearing on application state pruning which is determined by the
# "pruning-*" configurations.
#
# Note: Tendermint block pruning is dependant on this parameter in conjunction
# Note: Tendermint block pruning is dependent on this parameter in conjunction
# with the unbonding (safety threshold) period, state pruning and state sync
# snapshot parameters to determine the correct minimum value of
# ResponseCommit.RetainHeight.
@ -170,7 +170,7 @@ enable-fee-suggestion = false
# GasToSuggest defines gas limit when calculating the fee
gas-to-suggest = 200000
# DenomToSuggest defines the defult denom for fee suggestion.
# DenomToSuggest defines the default denom for fee suggestion.
# Price must be in minimum-gas-prices.
denom-to-suggest = "uatom"

View File

@ -0,0 +1,17 @@
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
###############################################################################
### Client Configuration ###
###############################################################################
# The network chain ID
chain-id = "demo"
# The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory)
keyring-backend = "os"
# CLI output format (text|json)
output = "text"
# <host>:<port> to Tendermint RPC interface for this chain
node = "tcp://localhost:26657"
# Transaction broadcasting mode (sync|async|block)
broadcast-mode = "sync"

View File

@ -47,7 +47,7 @@ halt-time = 0
# It has no bearing on application state pruning which is determined by the
# "pruning-*" configurations.
#
# Note: CometBFT block pruning is dependant on this parameter in conjunction
# Note: CometBFT block pruning is dependent on this parameter in conjunction
# with the unbonding (safety threshold) period, state pruning and state sync
# snapshot parameters to determine the correct minimum value of
# ResponseCommit.RetainHeight.

View File

@ -0,0 +1,17 @@
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
###############################################################################
### Client Configuration ###
###############################################################################
# The network chain ID
chain-id = "demo"
# The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory)
keyring-backend = "os"
# CLI output format (text|json)
output = "text"
# <host>:<port> to Tendermint RPC interface for this chain
node = "tcp://localhost:26657"
# Transaction broadcasting mode (sync|async|block)
broadcast-mode = "sync"

View File

@ -0,0 +1,228 @@
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
###############################################################################
### Base Configuration ###
###############################################################################
# The minimum gas prices a validator is willing to accept for processing a
# transaction. A transaction's fees must meet the minimum of any denomination
# specified in this config (e.g. 0.25token1,0.0001token2).
minimum-gas-prices = "0stake"
# The maximum gas a query coming over rest/grpc may consume.
# If this is set to zero, the query can consume an unbounded amount of gas.
query-gas-limit = "0"
# default: the last 362880 states are kept, pruning at 10 block intervals
# nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node)
# everything: 2 latest states will be kept; pruning at 10 block intervals.
# custom: allow pruning options to be manually specified through 'pruning-keep-recent', and 'pruning-interval'
pruning = "default"
# These are applied if and only if the pruning strategy is custom.
pruning-keep-recent = "0"
pruning-interval = "0"
# HaltHeight contains a non-zero block height at which a node will gracefully
# halt and shutdown that can be used to assist upgrades and testing.
#
# Note: Commitment of state will be attempted on the corresponding block.
halt-height = 0
# HaltTime contains a non-zero minimum block time (in Unix seconds) at which
# a node will gracefully halt and shutdown that can be used to assist upgrades
# and testing.
#
# Note: Commitment of state will be attempted on the corresponding block.
halt-time = 0
# MinRetainBlocks defines the minimum block height offset from the current
# block being committed, such that all blocks past this offset are pruned
# from CometBFT. It is used as part of the process of determining the
# ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates
# that no blocks should be pruned.
#
# This configuration value is only responsible for pruning CometBFT blocks.
# It has no bearing on application state pruning which is determined by the
# "pruning-*" configurations.
#
# Note: CometBFT block pruning is dependent on this parameter in conjunction
# with the unbonding (safety threshold) period, state pruning and state sync
# snapshot parameters to determine the correct minimum value of
# ResponseCommit.RetainHeight.
min-retain-blocks = 0
# InterBlockCache enables inter-block caching.
inter-block-cache = true
# IndexEvents defines the set of events in the form {eventType}.{attributeKey},
# which informs CometBFT what to index. If empty, all events will be indexed.
#
# Example:
# ["message.sender", "message.recipient"]
index-events = []
# IavlCacheSize set the size of the iavl tree cache (in number of nodes).
iavl-cache-size = 781250
# IAVLDisableFastNode enables or disables the fast node feature of IAVL.
# Default is false.
iavl-disable-fastnode = false
# AppDBBackend defines the database backend type to use for the application and snapshots DBs.
# An empty string indicates that a fallback will be used.
# The fallback is the db_backend value set in CometBFT's config.toml.
app-db-backend = ""
###############################################################################
### Telemetry Configuration ###
###############################################################################
[telemetry]
# Prefixed with keys to separate services.
service-name = ""
# Enabled enables the application telemetry functionality. When enabled,
# an in-memory sink is also enabled by default. Operators may also enabled
# other sinks such as Prometheus.
enabled = false
# Enable prefixing gauge values with hostname.
enable-hostname = false
# Enable adding hostname to labels.
enable-hostname-label = false
# Enable adding service to labels.
enable-service-label = false
# PrometheusRetentionTime, when positive, enables a Prometheus metrics sink.
prometheus-retention-time = 0
# GlobalLabels defines a global set of name/value label tuples applied to all
# metrics emitted using the wrapper functions defined in telemetry package.
#
# Example:
# [["chain_id", "cosmoshub-1"]]
global-labels = []
# MetricsSink defines the type of metrics sink to use.
metrics-sink = "mem"
# StatsdAddr defines the address of a statsd server to send metrics to.
# Only utilized if MetricsSink is set to "statsd" or "dogstatsd".
statsd-addr = ""
# DatadogHostname defines the hostname to use when emitting metrics to
# Datadog. Only utilized if MetricsSink is set to "dogstatsd".
datadog-hostname = ""
###############################################################################
### API Configuration ###
###############################################################################
[api]
# Enable defines if the API server should be enabled.
enable = false
# Swagger defines if swagger documentation should automatically be registered.
swagger = false
# Address defines the API server to listen on.
address = "tcp://localhost:1317"
# MaxOpenConnections defines the number of maximum open connections.
max-open-connections = 1000
# RPCReadTimeout defines the CometBFT RPC read timeout (in seconds).
rpc-read-timeout = 10
# RPCWriteTimeout defines the CometBFT RPC write timeout (in seconds).
rpc-write-timeout = 0
# RPCMaxBodyBytes defines the CometBFT maximum request body (in bytes).
rpc-max-body-bytes = 1000000
# EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk).
enabled-unsafe-cors = false
###############################################################################
### gRPC Configuration ###
###############################################################################
[grpc]
# Enable defines if the gRPC server should be enabled.
enable = true
# Address defines the gRPC server address to bind to.
address = "localhost:9090"
# MaxRecvMsgSize defines the max message size in bytes the server can receive.
# The default value is 10MB.
max-recv-msg-size = "10485760"
# MaxSendMsgSize defines the max message size in bytes the server can send.
# The default value is math.MaxInt32.
max-send-msg-size = "2147483647"
# SkipCheckHeader defines if the gRPC server should bypass check header.
skip-check-header = false
###############################################################################
### State Sync Configuration ###
###############################################################################
# State sync snapshots allow other nodes to rapidly join the network without replaying historical
# blocks, instead downloading and applying a snapshot of the application state at a given height.
[state-sync]
# snapshot-interval specifies the block interval at which local state sync snapshots are
# taken (0 to disable).
snapshot-interval = 0
# snapshot-keep-recent specifies the number of recent snapshots to keep and serve (0 to keep all).
snapshot-keep-recent = 2
###############################################################################
### State Streaming ###
###############################################################################
# Streaming allows nodes to stream state to external systems.
[streaming]
# streaming.abci specifies the configuration for the ABCI Listener streaming service.
[streaming.abci]
# List of kv store keys to stream out via gRPC.
# The store key names MUST match the module's StoreKey name.
#
# Example:
# ["acc", "bank", "gov", "staking", "mint"[,...]]
# ["*"] to expose all keys.
keys = []
# The plugin name used for streaming via gRPC.
# Streaming is only enabled if this is set.
# Supported plugins: abci
plugin = ""
# stop-node-on-err specifies whether to stop the node on message delivery error.
stop-node-on-err = true
###############################################################################
### Mempool ###
###############################################################################
[mempool]
# Setting max-txs to 0 will allow for a unbounded amount of transactions in the mempool.
# Setting max_txs to negative 1 (-1) will disable transactions from being inserted into the mempool (no-op mempool).
# Setting max_txs to a positive number (> 0) will limit the number of transactions in the mempool, by the specified amount.
#
# Note, this configuration only applies to SDK built-in app-side mempool
# implementations.
max-txs = -1

View File

@ -0,0 +1,27 @@
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
###############################################################################
### Client Configuration ###
###############################################################################
# The network chain ID
chain-id = "demo"
# The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory)
keyring-backend = "test"
# Default key name, if set, defines the default key to use for signing transaction when the --from flag is not specified
keyring-default-keyname = ""
# CLI output format (text|json)
output = "text"
# <host>:<port> to CometBFT RPC interface for this chain
node = "tcp://localhost:26657"
# Transaction broadcasting mode (sync|async)
broadcast-mode = "sync"
# gRPC server endpoint to which the client will connect.
# It can be overwritten by the --grpc-addr flag in each command.
grpc-address = ""
# Allow the gRPC client to connect over insecure channels.
# It can be overwritten by the --grpc-insecure flag in each command.
grpc-insecure = false

View File

@ -0,0 +1,178 @@
[comet]
# min-retain-blocks defines the minimum block height offset from the current block being committed, such that all blocks past this offset are pruned from CometBFT. A value of 0 indicates that no blocks should be pruned.
min-retain-blocks = 0
# halt-height contains a non-zero block height at which a node will gracefully halt and shutdown that can be used to assist upgrades and testing.
halt-height = 0
# halt-time contains a non-zero minimum block time (in Unix seconds) at which a node will gracefully halt and shutdown that can be used to assist upgrades and testing.
halt-time = 0
# address defines the CometBFT RPC server address to bind to.
address = 'tcp://127.0.0.1:26658'
# transport defines the CometBFT RPC server transport protocol: socket, grpc
transport = 'socket'
# trace enables the CometBFT RPC server to output trace information about its internal operations.
trace = false
# standalone starts the application without the CometBFT node. The node should be started separately.
standalone = false
# index-abci-events defines the set of events in the form {eventType}.{attributeKey}, which informs CometBFT what to index. If empty, all events will be indexed.
index-abci-events = []
# disable-index-abci-events disables the ABCI event indexing done by CometBFT. Useful when relying on the SDK indexer for event indexing, but still want events to be included in FinalizeBlockResponse.
disable-index-abci-events = false
# disable-abci-events disables all ABCI events. Useful when relying on the SDK indexer for event indexing.
disable-abci-events = false
# mempool defines the configuration for the SDK built-in app-side mempool implementations.
[comet.mempool]
# max-txs defines the maximum number of transactions that can be in the mempool. A value of 0 indicates an unbounded mempool, a negative value disables the app-side mempool.
max-txs = -1
# indexer defines the configuration for the SDK built-in indexer implementation.
[comet.indexer]
# Buffer size of the channels used for buffering data sent to indexer go routines.
channel_buffer_size = 1024
# Target is a map of named indexer targets to their configuration.
[comet.indexer.target]
[grpc]
# Enable defines if the gRPC server should be enabled.
enable = true
# Address defines the gRPC server address to bind to.
address = 'localhost:9090'
# MaxRecvMsgSize defines the max message size in bytes the server can receive.
# The default value is 10MB.
max-recv-msg-size = 10485760
# MaxSendMsgSize defines the max message size in bytes the server can send.
# The default value is math.MaxInt32.
max-send-msg-size = 2147483647
[grpc-gateway]
# Enable defines if the gRPC-Gateway should be enabled.
enable = true
# Address defines the address the gRPC-Gateway server binds to.
address = 'localhost:1317'
[rest]
# Enable defines if the REST server should be enabled.
enable = true
# Address defines the REST server address to bind to.
address = 'localhost:8080'
[server]
# minimum-gas-prices defines the price which a validator is willing to accept for processing a transaction. A transaction's fees must meet the minimum of any denomination specified in this config (e.g. 0.25token1;0.0001token2).
minimum-gas-prices = '0stake'
[store]
# The type of database for application and snapshots databases.
app-db-backend = 'goleveldb'
[store.options]
# State commitment database type. Currently we support: "iavl" and "iavl-v2"
sc-type = 'iavl'
# Pruning options for state commitment
[store.options.sc-pruning-option]
# Number of recent heights to keep on disk.
keep-recent = 2
# Height interval at which pruned heights are removed from disk.
interval = 100
[store.options.iavl-config]
# CacheSize set the size of the iavl tree cache.
cache-size = 500000
# If true, the tree will work like no fast storage and always not upgrade fast storage.
skip-fast-storage-upgrade = true
[store.options.iavl-v2-config]
# CheckpointInterval set the interval of the checkpoint.
checkpoint-interval = 0
# CheckpointMemory set the memory of the checkpoint.
checkpoint-memory = 0
# StateStorage set the state storage.
state-storage = false
# HeightFilter set the height filter.
height-filter = 0
# EvictionDepth set the eviction depth.
eviction-depth = 0
# PruneRatio set the prune ratio.
prune-ratio = 0.0
# MinimumKeepVersions set the minimum keep versions.
minimum-keep-versions = 0
[swagger]
# Enable enables/disables the Swagger UI server
enable = true
# Address defines the server address to bind to
address = 'localhost:8090'
[telemetry]
# Enable enables the application telemetry functionality. When enabled, an in-memory sink is also enabled by default. Operators may also enabled other sinks such as Prometheus.
enable = true
# Address defines the metrics server address to bind to.
address = 'localhost:7180'
# Prefixed with keys to separate services.
service-name = ''
# Enable prefixing gauge values with hostname.
enable-hostname = false
# Enable adding hostname to labels.
enable-hostname-label = false
# Enable adding service to labels.
enable-service-label = false
# PrometheusRetentionTime, when positive, enables a Prometheus metrics sink. It defines the retention duration in seconds.
prometheus-retention-time = 600
# GlobalLabels defines a global set of name/value label tuples applied to all metrics emitted using the wrapper functions defined in telemetry package.
# Example:
# [["chain_id", "cosmoshub-1"]]
global-labels = []
# MetricsSink defines the type of metrics backend to use. Default is in memory
metrics-sink = ''
# StatsdAddr defines the address of a statsd server to send metrics to. Only utilized if MetricsSink is set to "statsd" or "dogstatsd".
stats-addr = ''
# DatadogHostname defines the hostname to use when emitting metrics to Datadog. Only utilized if MetricsSink is set to "dogstatsd".
data-dog-hostname = ''

View File

@ -0,0 +1,27 @@
# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
###############################################################################
### Client Configuration ###
###############################################################################
# The network chain ID
chain-id = ""
# The keyring's backend, where the keys are stored (os|file|kwallet|pass|test|memory)
keyring-backend = "test"
# Default key name, if set, defines the default key to use for signing transaction when the --from flag is not specified
keyring-default-keyname = ""
# CLI output format (text|json)
output = "text"
# <host>:<port> to CometBFT RPC interface for this chain
node = "tcp://localhost:26657"
# Transaction broadcasting mode (sync|async)
broadcast-mode = "sync"
# gRPC server endpoint to which the client will connect.
# It can be overwritten by the --grpc-addr flag in each command.
grpc-address = ""
# Allow the gRPC client to connect over insecure channels.
# It can be overwritten by the --grpc-insecure flag in each command.
grpc-insecure = false

View File

@ -77,7 +77,7 @@ func DiffKeys(lhs, rhs *tomledit.Document) []Diff {
return diff
}
// DiffKeys diffs the keyspaces with different values of the TOML documents in files lhs and rhs.
// DiffValues diffs the keyspaces with different values of the TOML documents in files lhs and rhs.
func DiffValues(lhs, rhs *tomledit.Document) []Diff {
diff := diffDocs(allKVs(lhs.Global), allKVs(rhs.Global), true)

View File

@ -4,6 +4,8 @@ import (
"embed"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/creachadair/tomledit"
)
@ -11,11 +13,16 @@ import (
//go:embed data
var data embed.FS
// LoadConfig loads and parses the TOML document from confix data
func LoadLocalConfig(name string) (*tomledit.Document, error) {
f, err := data.Open(fmt.Sprintf("data/%s-app.toml", name))
// LoadLocalConfig loads and parses the TOML document from confix data
func LoadLocalConfig(name, configType string) (*tomledit.Document, error) {
fileName, err := getFileName(name, configType)
if err != nil {
panic(fmt.Errorf("failed to read file: %w. This file should have been included in confix", err))
return nil, err
}
f, err := data.Open(filepath.Join("data", fileName))
if err != nil {
return nil, fmt.Errorf("failed to read file: %w. This file should have been included in confix", err)
}
defer f.Close()
@ -26,9 +33,21 @@ func LoadLocalConfig(name string) (*tomledit.Document, error) {
func LoadConfig(path string) (*tomledit.Document, error) {
f, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("failed to open %q: %v", path, err)
return nil, fmt.Errorf("failed to open %q: %w", path, err)
}
defer f.Close()
return tomledit.Parse(f)
}
// getFileName constructs the filename based on the type of configuration (app or client)
func getFileName(name, configType string) (string, error) {
switch strings.ToLower(configType) {
case "app":
return fmt.Sprintf("%s-app.toml", name), nil
case "client":
return fmt.Sprintf("%s-client.toml", name), nil
default:
return "", fmt.Errorf("unsupported config type: %q", configType)
}
}

View File

@ -3,155 +3,163 @@ module cosmossdk.io/tools/confix
go 1.23
require (
github.com/cosmos/cosmos-sdk v0.50.6
github.com/creachadair/atomicfile v0.3.1
github.com/creachadair/tomledit v0.0.24
github.com/pelletier/go-toml/v2 v2.2.2
github.com/cosmos/cosmos-sdk v0.50.11
github.com/creachadair/atomicfile v0.3.7
github.com/creachadair/tomledit v0.0.27
github.com/pelletier/go-toml/v2 v2.2.3
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
gotest.tools/v3 v3.5.1
)
require (
cosmossdk.io/api v0.7.5 // indirect
go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
)
require (
cosmossdk.io/api v0.7.6 // indirect
cosmossdk.io/collections v0.4.0 // indirect
cosmossdk.io/core v0.11.0 // indirect
cosmossdk.io/depinject v1.0.0-alpha.4 // indirect
cosmossdk.io/core v0.11.1 // indirect
cosmossdk.io/depinject v1.1.0 // indirect
cosmossdk.io/errors v1.0.1 // indirect
cosmossdk.io/log v1.4.1 // indirect
cosmossdk.io/math v1.3.0 // indirect
cosmossdk.io/store v1.1.0 // indirect
cosmossdk.io/x/tx v0.13.4 // indirect
filippo.io/edwards25519 v1.0.0 // indirect
cosmossdk.io/log v1.5.0 // indirect
cosmossdk.io/math v1.5.0 // indirect
cosmossdk.io/store v1.1.1 // indirect
cosmossdk.io/x/tx v1.0.0-alpha.3 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.1 // indirect
github.com/DataDog/datadog-go v3.2.0+incompatible // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/DataDog/zstd v1.5.6 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.4 // indirect
github.com/bgentry/speakeasy v0.2.0 // indirect
github.com/bytedance/sonic v1.12.8 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v1.1.1 // indirect
github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0 // indirect
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect
github.com/cockroachdb/pebble v1.1.2 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/cometbft/cometbft v0.38.12 // indirect
github.com/cometbft/cometbft-db v0.11.0 // indirect
github.com/cometbft/cometbft v0.38.17 // indirect
github.com/cometbft/cometbft-db v0.14.1 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-db v1.0.2 // indirect
github.com/cosmos/cosmos-db v1.1.1 // indirect
github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect
github.com/cosmos/go-bip39 v1.0.0 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/gogoproto v1.7.0 // indirect
github.com/cosmos/iavl v1.1.2 // indirect
github.com/cosmos/iavl v1.2.2 // indirect
github.com/cosmos/ics23/go v0.11.0 // indirect
github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.6.0 // indirect
github.com/emicklei/dot v1.6.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/emicklei/dot v1.6.2 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/go-kit/kit v0.12.0 // indirect
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/getsentry/sentry-go v0.30.0 // indirect
github.com/go-kit/kit v0.13.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.2.0 // indirect
github.com/golang/glog v1.2.4 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v1.12.1 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/orderedcode v0.0.1 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-metrics v0.5.3 // indirect
github.com/hashicorp/go-plugin v1.5.2 // indirect
github.com/hashicorp/go-metrics v0.5.4 // indirect
github.com/hashicorp/go-plugin v1.6.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hdevalence/ed25519consensus v0.1.0 // indirect
github.com/huandu/skiplist v1.2.0 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/huandu/skiplist v1.2.1 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/improbable-eng/grpc-web v0.15.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/linxGnu/grocksdb v1.8.14 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/linxGnu/grocksdb v1.9.7 // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/minio/highwayhash v1.0.3 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67 // indirect
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.20.1 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/sasha-s/go-deadlock v0.3.5 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/stretchr/testify v1.10.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v0.14.3 // indirect
go.etcd.io/bbolt v1.3.10 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 // indirect
golang.org/x/net v0.28.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/term v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/tools v0.22.0 // indirect
github.com/zondax/ledger-go v0.14.3 // indirect; indirectÎ
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.34.2 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/grpc v1.70.0 // indirect
google.golang.org/protobuf v1.36.4 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
nhooyr.io/websocket v1.8.6 // indirect

View File

@ -1,26 +1,26 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
cosmossdk.io/api v0.7.5 h1:eMPTReoNmGUm8DeiQL9DyM8sYDjEhWzL1+nLbI9DqtQ=
cosmossdk.io/api v0.7.5/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38=
cosmossdk.io/api v0.7.6 h1:PC20PcXy1xYKH2KU4RMurVoFjjKkCgYRbVAD4PdqUuY=
cosmossdk.io/api v0.7.6/go.mod h1:IcxpYS5fMemZGqyYtErK7OqvdM0C8kdW3dq8Q/XIG38=
cosmossdk.io/collections v0.4.0 h1:PFmwj2W8szgpD5nOd8GWH6AbYNi1f2J6akWXJ7P5t9s=
cosmossdk.io/collections v0.4.0/go.mod h1:oa5lUING2dP+gdDquow+QjlF45eL1t4TJDypgGd+tv0=
cosmossdk.io/core v0.11.0 h1:vtIafqUi+1ZNAE/oxLOQQ7Oek2n4S48SWLG8h/+wdbo=
cosmossdk.io/core v0.11.0/go.mod h1:LaTtayWBSoacF5xNzoF8tmLhehqlA9z1SWiPuNC6X1w=
cosmossdk.io/depinject v1.0.0-alpha.4 h1:PLNp8ZYAMPTUKyG9IK2hsbciDWqna2z1Wsl98okJopc=
cosmossdk.io/depinject v1.0.0-alpha.4/go.mod h1:HeDk7IkR5ckZ3lMGs/o91AVUc7E596vMaOmslGFM3yU=
cosmossdk.io/core v0.11.1 h1:h9WfBey7NAiFfIcUhDVNS503I2P2HdZLebJlUIs8LPA=
cosmossdk.io/core v0.11.1/go.mod h1:OJzxcdC+RPrgGF8NJZR2uoQr56tc7gfBKhiKeDO7hH0=
cosmossdk.io/depinject v1.1.0 h1:wLan7LG35VM7Yo6ov0jId3RHWCGRhe8E8bsuARorl5E=
cosmossdk.io/depinject v1.1.0/go.mod h1:kkI5H9jCGHeKeYWXTqYdruogYrEeWvBQCw1Pj4/eCFI=
cosmossdk.io/errors v1.0.1 h1:bzu+Kcr0kS/1DuPBtUFdWjzLqyUuCiyHjyJB6srBV/0=
cosmossdk.io/errors v1.0.1/go.mod h1:MeelVSZThMi4bEakzhhhE/CKqVv3nOJDA25bIqRDu/U=
cosmossdk.io/log v1.4.1 h1:wKdjfDRbDyZRuWa8M+9nuvpVYxrEOwbD/CA8hvhU8QM=
cosmossdk.io/log v1.4.1/go.mod h1:k08v0Pyq+gCP6phvdI6RCGhLf/r425UT6Rk/m+o74rU=
cosmossdk.io/math v1.3.0 h1:RC+jryuKeytIiictDslBP9i1fhkVm6ZDmZEoNP316zE=
cosmossdk.io/math v1.3.0/go.mod h1:vnRTxewy+M7BtXBNFybkuhSH4WfedVAAnERHgVFhp3k=
cosmossdk.io/store v1.1.0 h1:LnKwgYMc9BInn9PhpTFEQVbL9UK475G2H911CGGnWHk=
cosmossdk.io/store v1.1.0/go.mod h1:oZfW/4Fc/zYqu3JmQcQdUJ3fqu5vnYTn3LZFFy8P8ng=
cosmossdk.io/x/tx v0.13.4 h1:Eg0PbJgeO0gM8p5wx6xa0fKR7hIV6+8lC56UrsvSo0Y=
cosmossdk.io/x/tx v0.13.4/go.mod h1:BkFqrnGGgW50Y6cwTy+JvgAhiffbGEKW6KF9ufcDpvk=
cosmossdk.io/log v1.5.0 h1:dVdzPJW9kMrnAYyMf1duqacoidB9uZIl+7c6z0mnq0g=
cosmossdk.io/log v1.5.0/go.mod h1:Tr46PUJjiUthlwQ+hxYtUtPn4D/oCZXAkYevBeh5+FI=
cosmossdk.io/math v1.5.0 h1:sbOASxee9Zxdjd6OkzogvBZ25/hP929vdcYcBJQbkLc=
cosmossdk.io/math v1.5.0/go.mod h1:AAwwBmUhqtk2nlku174JwSll+/DepUXW3rWIXN5q+Nw=
cosmossdk.io/store v1.1.1 h1:NA3PioJtWDVU7cHHeyvdva5J/ggyLDkyH0hGHl2804Y=
cosmossdk.io/store v1.1.1/go.mod h1:8DwVTz83/2PSI366FERGbWSH7hL6sB7HbYp8bqksNwM=
cosmossdk.io/x/tx v1.0.0-alpha.3 h1:+55/JFH5QRqnFhOI2heH3DKsaNL0RpXcJOQNzUvHiaQ=
cosmossdk.io/x/tx v1.0.0-alpha.3/go.mod h1:h4pQ/j6Gfu8goB1R3Jbl4qY4RjYVNAsoylcleTXdSRg=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
filippo.io/edwards25519 v1.0.0 h1:0wAIcmJUqRdI8IJ/3eGi5/HwXZWPujYXXlkrQogz0Ek=
filippo.io/edwards25519 v1.0.0/go.mod h1:N1IkdkCkiLB6tki+MYJoSx2JTY9NUlxZE7eHn5EwJns=
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 h1:/vQbFIOMbk2FiG/kXiLl8BRyzTWDw7gX/Hz7Dd5eDMs=
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4/go.mod h1:hN7oaIRCjzsZ2dE+yG5k+rsdt3qcwykqK6HVGcKwsw4=
github.com/99designs/keyring v1.2.1 h1:tYLp1ULvO7i3fI5vE21ReQuj99QFSs7lGm0xWyJo87o=
@ -31,21 +31,19 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dXCilEuNEeAn20fdD4=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ=
github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ=
github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/DataDog/zstd v1.5.6 h1:LbEglqepa/ipmmQJUDnSsfvA8e8IStVcGaFWDuxvGOY=
github.com/DataDog/zstd v1.5.6/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw=
github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw=
github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk=
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/adlio/schema v1.3.3 h1:oBJn8I02PyTB466pZO1UZEn1TV5XLlifBSyMrmHl/1I=
github.com/adlio/schema v1.3.3/go.mod h1:1EsRssiv9/Ce2CMzq5DoL7RiMshhuigQxrR4DMV9fHg=
github.com/adlio/schema v1.3.6 h1:k1/zc2jNfeiZBA5aFTRy37jlBIuCkXCm0XmvpzCKI9I=
github.com/adlio/schema v1.3.6/go.mod h1:qkxwLgPBd1FgLRHYVCmQT/rrBr3JH38J9LjmVzWNudg=
github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -56,8 +54,8 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
@ -69,18 +67,21 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 h1:41iFGWnSlI2gVpmOtVTJZNodLdLQLn/KsJqFvXwnd/s=
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bgentry/speakeasy v0.2.0 h1:tgObeVOf8WAvtuAX6DhJ4xks4CFNwPDZiqzGqIHE51E=
github.com/bgentry/speakeasy v0.2.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bits-and-blooms/bitset v1.8.0 h1:FD+XqgOZDUxxZ8hzoBFuV9+cGWY9CslN6d5MS5JVb4c=
github.com/bits-and-blooms/bitset v1.8.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
github.com/btcsuite/btcd/btcec/v2 v2.3.4/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04=
github.com/btcsuite/btcd/btcutil v1.1.6 h1:zFL2+c3Lb9gEgqKNzowKUPQNb8jV7v5Oaodi/AYFd6c=
github.com/btcsuite/btcd/btcutil v1.1.6/go.mod h1:9dFymx8HpuLqBnsPELrImQeTQfKBQqzqGbbV3jK55aE=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U=
github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc=
github.com/bufbuild/protocompile v0.6.0 h1:Uu7WiSQ6Yj9DbkdnOe7U4mNKp58y9WDMKDn28/ZlunY=
github.com/bufbuild/protocompile v0.6.0/go.mod h1:YNP35qEYoYGme7QMtz5SBCoN4kL4g12jTtjuzRNdjpE=
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0=
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
@ -88,8 +89,6 @@ github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInq
github.com/cenkalti/backoff/v4 v4.1.3 h1:cFAlzYUlVYDysBEH2T5hyJZMh3+5+WCBvSnK6Q8UtC4=
github.com/cenkalti/backoff/v4 v4.1.3/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
@ -102,6 +101,9 @@ github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6D
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
@ -111,42 +113,42 @@ github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd/v2 v2.0.2 h1:weh8u7Cneje73dDh+2tEVLUvyBc89iwepWCD8b8034E=
github.com/cockroachdb/apd/v2 v2.0.2/go.mod h1:DDxRlzC2lo3/vSlmSoS7JkqbbrARPuFOGr0B9pvN3Gw=
github.com/cockroachdb/apd/v3 v3.2.1 h1:U+8j7t0axsIgvQUqthuNm82HIrYXodOV2iWLWtEaIwg=
github.com/cockroachdb/apd/v3 v3.2.1/go.mod h1:klXJcjp+FffLTHlhIG69tezTDvdP065naDsHzKhYSqc=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4=
github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU=
github.com/cockroachdb/errors v1.11.3 h1:5bA+k2Y6r+oz/6Z/RFlNeVCesGARKuC6YymtcDrbC/I=
github.com/cockroachdb/errors v1.11.3/go.mod h1:m4UIW4CDjx+R5cybPsNrRbreomiFqt8o1h1wUVazSd8=
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce h1:giXvy4KSc/6g/esnpM7Geqxka4WSqI1SZc7sMJFd3y4=
github.com/cockroachdb/fifo v0.0.0-20240606204812-0bbfbd93a7ce/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE=
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs=
github.com/cockroachdb/pebble v1.1.1 h1:XnKU22oiCLy2Xn8vp1re67cXg4SAasg/WDt1NtcRFaw=
github.com/cockroachdb/pebble v1.1.1/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU=
github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0 h1:pU88SPhIFid6/k0egdR5V6eALQYq2qbSmukrkgIh/0A=
github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0/go.mod h1:9/y3cnZ5GKakj/H4y9r9GTjCvAFta7KLgSHPJJYc52M=
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 h1:ASDL+UJcILMqgNeV5jiqR4j+sTuvQNHdf2chuKj1M5k=
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506/go.mod h1:Mw7HqKr2kdtu6aYGn3tPmAftiP3QPX63LdK/zcariIo=
github.com/cockroachdb/pebble v1.1.2 h1:CUh2IPtR4swHlEj48Rhfzw6l/d0qA31fItcIszQVIsA=
github.com/cockroachdb/pebble v1.1.2/go.mod h1:4exszw1r40423ZsmkG/09AFEG83I0uDgfujJdbL6kYU=
github.com/cockroachdb/redact v1.1.5 h1:u1PMllDkdFfPWaNGMyLD1+so+aq3uUItthCFqzwPJ30=
github.com/cockroachdb/redact v1.1.5/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo=
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/cometbft/cometbft v0.38.12 h1:OWsLZN2KcSSFe8bet9xCn07VwhBnavPea3VyPnNq1bg=
github.com/cometbft/cometbft v0.38.12/go.mod h1:GPHp3/pehPqgX1930HmK1BpBLZPxB75v/dZg8Viwy+o=
github.com/cometbft/cometbft-db v0.11.0 h1:M3Lscmpogx5NTbb1EGyGDaFRdsoLWrUWimFEyf7jej8=
github.com/cometbft/cometbft-db v0.11.0/go.mod h1:GDPJAC/iFHNjmZZPN8V8C1yr/eyityhi2W1hz2MGKSc=
github.com/cometbft/cometbft v0.38.17 h1:FkrQNbAjiFqXydeAO81FUzriL4Bz0abYxN/eOHrQGOk=
github.com/cometbft/cometbft v0.38.17/go.mod h1:5l0SkgeLRXi6bBfQuevXjKqML1jjfJJlvI1Ulp02/o4=
github.com/cometbft/cometbft-db v0.14.1 h1:SxoamPghqICBAIcGpleHbmoPqy+crij/++eZz3DlerQ=
github.com/cometbft/cometbft-db v0.14.1/go.mod h1:KHP1YghilyGV/xjD5DP3+2hyigWx0WTp9X+0Gnx0RxQ=
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg=
github.com/containerd/continuity v0.3.0/go.mod h1:wJEAIwKOm/pBZuBd0JmeTvnLquTB1Ag8espWhkykbPM=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cosmos/btcutil v1.0.5 h1:t+ZFcX77LpKtDBhjucvnOH8C2l2ioGsBNEQ3jef8xFk=
github.com/cosmos/btcutil v1.0.5/go.mod h1:IyB7iuqZMJlthe2tkIFL33xPyzbFYP0XVdS8P5lUPis=
github.com/cosmos/cosmos-db v1.0.2 h1:hwMjozuY1OlJs/uh6vddqnk9j7VamLv+0DBlbEXbAKs=
github.com/cosmos/cosmos-db v1.0.2/go.mod h1:Z8IXcFJ9PqKK6BIsVOB3QXtkKoqUOp1vRvPT39kOXEA=
github.com/cosmos/cosmos-db v1.1.1 h1:FezFSU37AlBC8S98NlSagL76oqBRWq/prTPvFcEJNCM=
github.com/cosmos/cosmos-db v1.1.1/go.mod h1:AghjcIPqdhSLP/2Z0yha5xPH3nLnskz81pBx3tcVSAw=
github.com/cosmos/cosmos-proto v1.0.0-beta.5 h1:eNcayDLpip+zVLRLYafhzLvQlSmyab+RC5W7ZfmxJLA=
github.com/cosmos/cosmos-proto v1.0.0-beta.5/go.mod h1:hQGLpiIUloJBMdQMMWb/4wRApmI9hjHH05nefC0Ojec=
github.com/cosmos/cosmos-sdk v0.50.6 h1:efR3MsvMHX5sxS3be+hOobGk87IzlZbSpsI2x/Vw3hk=
github.com/cosmos/cosmos-sdk v0.50.6/go.mod h1:lVkRY6cdMJ0fG3gp8y4hFrsKZqF4z7y0M2UXFb9Yt40=
github.com/cosmos/cosmos-sdk v0.50.11 h1:LxR1aAc8kixdrs3itO+3a44sFoc+vjxVAOyPFx22yjk=
github.com/cosmos/cosmos-sdk v0.50.11/go.mod h1:gt14Meok2IDCjbDtjwkbUcgVNEpUBDN/4hg9cCUtLgw=
github.com/cosmos/go-bip39 v1.0.0 h1:pcomnQdrdH22njcAatO0yWojsUnCO3y2tNoV1cb6hHY=
github.com/cosmos/go-bip39 v1.0.0/go.mod h1:RNJv0H/pOIVgxw6KS7QeX2a0Uo0aKUlfhZ4xuwvCdJw=
github.com/cosmos/gogogateway v1.2.0 h1:Ae/OivNhp8DqBi/sh2A8a1D0y638GpL3tkmLQAiKxTE=
@ -154,19 +156,20 @@ github.com/cosmos/gogogateway v1.2.0/go.mod h1:iQpLkGWxYcnCdz5iAdLcRBSw3h7NXeOkZ
github.com/cosmos/gogoproto v1.4.2/go.mod h1:cLxOsn1ljAHSV527CHOtaIP91kK6cCrZETRBrkzItWU=
github.com/cosmos/gogoproto v1.7.0 h1:79USr0oyXAbxg3rspGh/m4SWNyoz/GLaAh0QlCe2fro=
github.com/cosmos/gogoproto v1.7.0/go.mod h1:yWChEv5IUEYURQasfyBW5ffkMHR/90hiHgbNgrtp4j0=
github.com/cosmos/iavl v1.1.2 h1:zL9FK7C4L/P4IF1Dm5fIwz0WXCnn7Bp1M2FxH0ayM7Y=
github.com/cosmos/iavl v1.1.2/go.mod h1:jLeUvm6bGT1YutCaL2fIar/8vGUE8cPZvh/gXEWDaDM=
github.com/cosmos/iavl v1.2.2 h1:qHhKW3I70w+04g5KdsdVSHRbFLgt3yY3qTMd4Xa4rC8=
github.com/cosmos/iavl v1.2.2/go.mod h1:GiM43q0pB+uG53mLxLDzimxM9l/5N9UuSY3/D0huuVw=
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/ledger-cosmos-go v0.13.3 h1:7ehuBGuyIytsXbd4MP43mLeoN2LTOEnk5nvue4rK+yM=
github.com/cosmos/ledger-cosmos-go v0.13.3/go.mod h1:HENcEP+VtahZFw38HZ3+LS3Iv5XV6svsnkk9vdJtLr8=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creachadair/atomicfile v0.3.1 h1:yQORkHjSYySh/tv5th1dkKcn02NEW5JleB84sjt+W4Q=
github.com/creachadair/atomicfile v0.3.1/go.mod h1:mwfrkRxFKwpNAflYZzytbSwxvbK6fdGRRlp0KEQc0qU=
github.com/creachadair/tomledit v0.0.24 h1:5Xjr25R2esu1rKCbQEmjZYlrhFkDspoAbAKb6QKQDhQ=
github.com/creachadair/tomledit v0.0.24/go.mod h1:9qHbShRWQzSCcn617cMzg4eab1vbLCOjOshAWSzWr8U=
github.com/creachadair/atomicfile v0.3.7 h1:wdg8+Isz07NDMi2yZQAoI1EKB9SxuDhvo5MUii/ZqlM=
github.com/creachadair/atomicfile v0.3.7/go.mod h1:lUrZrE/XjMA7rJY/n8dF7/sSpy6KjtPaxPbrDambthA=
github.com/creachadair/mds v0.22.1 h1:Wink9jeYR7brBbOkOTVZVrd6vyb5W4ZBRhlZd96TSgU=
github.com/creachadair/mds v0.22.1/go.mod h1:ArfS0vPHoLV/SzuIzoqTEZfoYmac7n9Cj8XPANHocvw=
github.com/creachadair/tomledit v0.0.27 h1:6xOpEnkKmcpT/gmKhabN0JXrqNX065lyje1/mXTSSIE=
github.com/creachadair/tomledit v0.0.27/go.mod h1:v1EWpgCisD3ct1kO8Gq4o4pdgX5JXD0rBI2PJ4UnPoA=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
@ -177,13 +180,12 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/decred/dcrd/crypto/blake256 v1.0.1 h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f h1:U5y3Y5UE0w7amNe7Z5G/twsBW0KEalRQXZzf8ufSh9I=
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f/go.mod h1:xH/i4TFMt8koVQZ6WFms69WAsDWr2XsYL3Hkl7jkoLE=
github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o=
github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk=
github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de/go.mod h1:KPxhHT9ZxKefz+PCeOGsrHpl1qZ7i70dGTu2u+Ahh6E=
github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs=
github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
@ -204,8 +206,8 @@ github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5m
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/emicklei/dot v1.6.1 h1:ujpDlBkkwgWUY+qPId5IwapRW/xEoligRSYjioR6DFI=
github.com/emicklei/dot v1.6.1/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
github.com/emicklei/dot v1.6.2 h1:08GN+DD79cy/tzN6uLCT84+2Wk9u+wvqP+Hkx/dIR8A=
github.com/emicklei/dot v1.6.2/go.mod h1:DeV7GvQtIw4h2u73RKBkkFdvVAz0D9fzeJrgPW6gy/s=
github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -216,9 +218,8 @@ github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
@ -230,10 +231,10 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/getsentry/sentry-go v0.27.0 h1:Pv98CIbtB3LkMWmXi4Joa5OOcwbmnX88sF5qbK3r3Ps=
github.com/getsentry/sentry-go v0.27.0/go.mod h1:lc76E2QywIyW8WuBnwl8Lc4bkmQH4+w1gwTf25trprY=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/getsentry/sentry-go v0.30.0 h1:lWUwDnY7sKHaVIoZ9wYqRHJ5iEmoc0pqcRqFkosKzBo=
github.com/getsentry/sentry-go v0.30.0/go.mod h1:WU9B9/1/sHDqeV8T+3VwwbjeR5MSXs/6aqG3mqZrezA=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
@ -246,8 +247,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o=
github.com/go-kit/kit v0.12.0 h1:e4o3o3IsBfAKQh5Qbbiqyfu97Ku7jrO/JbohvztANh4=
github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs=
github.com/go-kit/kit v0.13.0 h1:OoneCcHKHQ03LfBpoQCUfCluwd2Vt3ohz+kvbJneZAU=
github.com/go-kit/kit v0.13.0/go.mod h1:phqEHMMUbyrCFCTgH48JueqrM3md2HcAZ8N3XE4FKDg=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
@ -256,6 +257,10 @@ github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU=
@ -291,10 +296,13 @@ github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXP
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.2.0 h1:uCdmnmatrKCgMBlM4rMuJZWOkPDqdbZPnrMXDY4gI68=
github.com/golang/glog v1.2.0/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc=
github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs=
@ -317,18 +325,21 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw=
github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -344,14 +355,16 @@ github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLe
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/handlers v1.5.2 h1:cLTUSsNkgcwhgRqvCNmdbRWG0A3N4F+M2nWKdScwyEE=
github.com/gorilla/handlers v1.5.2/go.mod h1:dX+xVpaxdSw+q0Qek8SSsl3dfMk3jNddUkMzo0GtH0w=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
@ -371,17 +384,17 @@ github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyN
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-metrics v0.5.3 h1:M5uADWMOGCTUNU1YuC4hfknOeHNaX54LDm4oYSucoNE=
github.com/hashicorp/go-metrics v0.5.3/go.mod h1:KEjodfebIOuBYSAe/bHTm+HChmKSxAOXPBieMLYozDE=
github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY=
github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-plugin v1.5.2 h1:aWv8eimFqWlsEiMrYZdPYl+FdHaBJSN4AWwGWfT1G2Y=
github.com/hashicorp/go-plugin v1.5.2/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4=
github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog=
github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
@ -403,15 +416,15 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
github.com/hdevalence/ed25519consensus v0.1.0 h1:jtBwzzcHuTmFrQN6xQZn6CQEO/V9f7HsjsjeEZ6auqU=
github.com/hdevalence/ed25519consensus v0.1.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/hdevalence/ed25519consensus v0.2.0 h1:37ICyZqdyj0lAZ8P4D1d1id3HqbbG1N3iBb1Tb4rdcU=
github.com/hdevalence/ed25519consensus v0.2.0/go.mod h1:w3BHWjwJbFU29IRHL1Iqkw3sus+7FctEyM4RqDxYNzo=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huandu/go-assert v1.1.5 h1:fjemmA7sSfYHJD7CUqs9qTwwfdNAx7/j2/ZlHXzNB3c=
github.com/huandu/go-assert v1.1.5/go.mod h1:yOLvuqZwmcHIC5rIzrBhT7D3Q9c3GFnd0JrPVhn/06U=
github.com/huandu/skiplist v1.2.0 h1:gox56QD77HzSC0w+Ws3MH3iie755GBJU1OER3h5VsYw=
github.com/huandu/skiplist v1.2.0/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w=
github.com/huandu/skiplist v1.2.1 h1:dTi93MgjwErA/8idWTzIw4Y1kZsMWx35fmI2c8Rij7w=
github.com/huandu/skiplist v1.2.1/go.mod h1:7v3iFjLcSAzO4fN5B8dvebvo/qsfumiLiDXMrPiHF9w=
github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg=
github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI=
github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho=
@ -434,6 +447,7 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
@ -445,9 +459,12 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.11.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
@ -463,16 +480,15 @@ github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM=
github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4=
github.com/linxGnu/grocksdb v1.8.14 h1:HTgyYalNwBSG/1qCQUIott44wU5b2Y9Kr3z7SK5OfGQ=
github.com/linxGnu/grocksdb v1.8.14/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
github.com/linxGnu/grocksdb v1.9.7 h1:Bp2r1Yti/IXxEobZZnDooXAui/Q+5gVqgQMenLWyDUw=
github.com/linxGnu/grocksdb v1.9.7/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA=
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/manifoldco/promptui v0.9.0 h1:3V4HzJk1TtXW1MTZMP7mdlwbBpIinw3HztaIlYthEiA=
github.com/manifoldco/promptui v0.9.0/go.mod h1:ka04sppxSGFAtxX0qhlYQjISsg9mR4GWtQEhdbn6Pgg=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
@ -491,14 +507,11 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/minio/highwayhash v1.0.2 h1:Aak5U0nElisjDCfPSG79Tgzkn2gl66NxOMspRrKnA/g=
github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
github.com/minio/highwayhash v1.0.3 h1:kbnuUMoHYyVl7szWjSxJnxw11k2U709jqFPPmIUyD6Q=
github.com/minio/highwayhash v1.0.3/go.mod h1:GGYsuwP/fPD6Y9hMiXuapVvlIUEhFhMTh0rxU3ik1LQ=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
@ -529,8 +542,9 @@ github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a h1:dlRvE5fWabOchtH7znfiFCcOvmIYgOeAS5ifBXBlh9Q=
github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
@ -557,8 +571,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc2 h1:2zx/Stx4Wc5pIPDvIxHXvXtQFW/7XWJGmnM7r3wg034=
github.com/opencontainers/image-spec v1.1.0-rc2/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
github.com/opencontainers/runc v1.1.3 h1:vIXrkId+0/J2Ymu2m7VjGvbSlAId9XNRPhn2p4b+d8w=
github.com/opencontainers/runc v1.1.3/go.mod h1:1J5XiS+vdZ3wCyZybsuxXZWGrgSr8fFJHLXuG2PsnNg=
github.com/opencontainers/runc v1.1.12 h1:BOIssBaW1La0/qbNZHXOOa71dZfZEQOzW7dqQf3phss=
github.com/opencontainers/runc v1.1.12/go.mod h1:S+lQwSfncpBha7XTy/5lBwWgm5+y5Ma/O44Ekby9FK8=
github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis=
github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
@ -574,13 +588,11 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67 h1:jik8PHtAIsPlCRJjJzl4udgEf7hawInF9texMeO2jrU=
github.com/petermattis/goid v0.0.0-20231207134359-e60b3f734c67/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 h1:Dx7Ovyv/SFnMFw3fD4oEoeorXc6saIiQ23LrGLth0Gw=
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
@ -601,8 +613,9 @@ github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5Fsn
github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.20.1 h1:IMJXHOD6eARkQpxo8KkhgEVFlBNm+nkrFUyGlIu7Na8=
github.com/prometheus/client_golang v1.20.1/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -617,14 +630,16 @@ github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt2
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.15.0/go.mod h1:U+gB1OBLb1lF3O42bTCL+FK18tX9Oar16Clt/msog/s=
github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc=
github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.62.0 h1:xasJaQlnWAeyHdUBeGjXmutelfJHWMRr+Fg4QszZ2Io=
github.com/prometheus/common v0.62.0/go.mod h1:vyBcEuLSvWos9B1+CyL7JZ2up+uFzXhkqml0W5zIY1I=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.3.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
@ -634,15 +649,14 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
@ -651,41 +665,33 @@ github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgY
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/sasha-s/go-deadlock v0.3.1 h1:sqv7fDNShgjcaxkO0JNcOAlr8B9+cV5Ey/OB71efZx0=
github.com/sasha-s/go-deadlock v0.3.1/go.mod h1:F73l+cr82YSh10GxyRI6qZiCgK64VaZjwesgfQ1/iLM=
github.com/sasha-s/go-deadlock v0.3.5 h1:tNCOEEDG6tBqrNDOX35j/7hL5FcFViG6awUGROb2NsU=
github.com/sasha-s/go-deadlock v0.3.5/go.mod h1:bugP6EGbdGYObIlx7pUZtWqlvo8k9H6vCBBsiChJQ5U=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
@ -706,9 +712,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d h1:vfofYNRScrDdvS342BElfbETmL1Aiz3i2t0zfRj16Hs=
@ -719,16 +725,16 @@ github.com/tidwall/btree v1.7.0 h1:L1fkJH/AuEh5zBnnBbmTwQ5Lt+bRJ5A8EWecslvo9iI=
github.com/tidwall/btree v1.7.0/go.mod h1:twD9XRA5jj9VUQGELzDO4HPQTNJsoWWfYEL+EUQ2cKY=
github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0=
github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
@ -737,12 +743,24 @@ github.com/zondax/hid v0.9.2/go.mod h1:l5wttcP0jwtdLjqjMMWFVEE7d1zO0jvSPA9OPZxWp
github.com/zondax/ledger-go v0.14.3 h1:wEpJt2CEcBJ428md/5MgSLsXLBos98sBOyxNmCjfUCw=
github.com/zondax/ledger-go v0.14.3/go.mod h1:IKKaoxupuB43g4NxeQmbLXv7T9AlQyie1UpHb342ycI=
go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0=
go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ=
go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5 h1:qxen9oVGzDdIRP6ejyAJc760RwW4SnVDiTYTzwnXuxo=
go.etcd.io/bbolt v1.4.0-alpha.0.0.20240404170359-43604f3112c5/go.mod h1:eW0HG9/oHQhvRCvb1/pIXW4cOvtDqeQK+XSi3TnwaXY=
go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg=
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
@ -758,22 +776,23 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9E
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.18.1/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670 h1:18EFjUmQOcUvxNYSkA6jO9VAiXCnxFY6NyDX0bHDmkU=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw=
golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20200331195152-e8c3332aa8e5/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0 h1:985EYyeCOxTpcgOTJpflJUwOeEz0CQOdPt73OzpE9F8=
golang.org/x/exp v0.0.0-20240404231335-c0f41cb1a7a0/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@ -788,8 +807,8 @@ golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -812,13 +831,14 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE=
golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg=
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -829,9 +849,10 @@ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -840,14 +861,11 @@ golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5h
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -869,9 +887,11 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -887,20 +907,21 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU=
golang.org/x/term v0.23.0/go.mod h1:DgV24QBUrK6jhZXl+20l6UWznPlwAHm1Q1mGHtydmSk=
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -924,8 +945,8 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA=
golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c=
golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o=
golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -948,10 +969,10 @@ google.golang.org/genproto v0.0.0-20210126160654-44e461bb6506/go.mod h1:FWY/as6D
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo=
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ=
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda h1:LI5DOvAxUPMv/50agcLLoo+AdWc1irS9Rzz4vPuD1V4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a h1:OAiGFfOiA0v9MRYsSidp3ubZaBnteRUyn3xB2ZQ5G/E=
google.golang.org/genproto/googleapis/api v0.0.0-20241202173237-19429a94021a/go.mod h1:jehYqy3+AhJU9ve55aNOaSml7wUXjF9x6z2LcCfpAhY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 h1:yrTuav+chrF0zF/joFGICKTzYv7mh/gr9AgEXrVU8ao=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM=
@ -966,11 +987,12 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ=
google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM=
google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
google.golang.org/grpc v1.70.0 h1:pWFv03aZoHzlRKHWicjsZytKAiYCtNS0dHbXnIdq7jQ=
google.golang.org/grpc v1.70.0/go.mod h1:ofIJqVKDXx/JiXrwr2IG4/zwdH9txy3IlF40RmcJSQw=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -985,8 +1007,8 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -1026,6 +1048,7 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k=
nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=

63
tools/confix/match.go Normal file
View File

@ -0,0 +1,63 @@
package confix
import (
"sort"
"github.com/creachadair/tomledit"
"github.com/creachadair/tomledit/transform"
)
// MatchKeys diffs the keyspaces of the TOML documents in files lhs and rhs.
// Comments, order, and values are ignored for comparison purposes.
// It will return in the format of map[oldKey]newKey
func MatchKeys(lhs, rhs *tomledit.Document) map[string]string {
matches := matchDocs(map[string]string{}, allKVs(lhs.Global), allKVs(rhs.Global))
lsec, rsec := lhs.Sections, rhs.Sections
transform.SortSectionsByName(lsec)
transform.SortSectionsByName(rsec)
i, j := 0, 0
for i < len(lsec) && j < len(rsec) {
switch {
case lsec[i].Name.Before(rsec[j].Name):
i++
case rsec[j].Name.Before(lsec[i].Name):
j++
default:
matches = matchDocs(matches, allKVs(lsec[i]), allKVs(rsec[j]))
i++
j++
}
}
return matches
}
// matchDocs get all the keys matching in lhs and rhs.
// value of keys are ignored
func matchDocs(matchesMap map[string]string, lhs, rhs []KV) map[string]string {
sort.Slice(lhs, func(i, j int) bool {
return lhs[i].Key < lhs[j].Key
})
sort.Slice(rhs, func(i, j int) bool {
return rhs[i].Key < rhs[j].Key
})
i, j := 0, 0
for i < len(lhs) && j < len(rhs) {
switch {
case lhs[i].Key < rhs[j].Key:
i++
case lhs[i].Key > rhs[j].Key:
j++
default:
// key exists in both lhs and rhs
matchesMap[lhs[i].Key] = rhs[j].Key
i++
j++
}
}
return matchesMap
}

View File

@ -11,43 +11,69 @@ import (
)
const (
AppConfig = "app.toml"
ClientConfig = "client.toml"
CMTConfig = "config.toml"
AppConfig = "app.toml"
AppConfigType = "app"
ClientConfig = "client.toml"
ClientConfigType = "client"
CMTConfig = "config.toml"
)
// MigrationMap defines a mapping from a version to a transformation plan.
type MigrationMap map[string]func(from *tomledit.Document, to string) transform.Plan
type MigrationMap map[string]func(from *tomledit.Document, to, planType string) (transform.Plan, *tomledit.Document)
// loadDestConfigFile is the function signature to load the destination version
// configuration toml file.
type loadDestConfigFile func(to string) (*tomledit.Document, error)
type loadDestConfigFile func(to, planType string) (*tomledit.Document, error)
var Migrations = MigrationMap{
"v0.45": NoPlan, // Confix supports only the current supported SDK version. So we do not support v0.44 -> v0.45.
"v0.46": defaultPlanBuilder,
"v0.47": defaultPlanBuilder,
"v0.50": defaultPlanBuilder,
// "v0.xx.x": PlanBuilder, // add specific migration in case of configuration changes in minor versions
"v0.52": defaultPlanBuilder,
"v2": V2PlanBuilder,
// "v0.xx.x": defaultPlanBuilder, // add specific migration in case of configuration changes in minor versions
}
func defaultPlanBuilder(from *tomledit.Document, to string) transform.Plan {
return PlanBuilder(from, to, LoadLocalConfig)
type v2KeyChangesMap map[string][]string
// list all the keys which are need to be modified in v2
var v2KeyChanges = v2KeyChangesMap{
"minimum-gas-prices": []string{"server.minimum-gas-prices"},
"min-retain-blocks": []string{"comet.min-retain-blocks"},
"index-events": []string{"comet.index-abci-events"},
"halt-height": []string{"comet.halt-height"},
"halt-time": []string{"comet.halt-time"},
"app-db-backend": []string{"store.app-db-backend"},
"pruning-keep-recent": []string{
"store.options.sc-pruning-option.keep-recent",
},
"pruning-interval": []string{
"store.options.sc-pruning-option.interval",
},
"iavl-cache-size": []string{"store.options.iavl-config.cache-size"},
"iavl-disable-fastnode": []string{"store.options.iavl-config.skip-fast-storage-upgrade"},
"telemetry.enabled": []string{"telemetry.enable"},
"mempool.max-txs": []string{"comet.mempool.max-txs"},
// Add other key mappings as needed
}
func defaultPlanBuilder(from *tomledit.Document, to, planType string) (transform.Plan, *tomledit.Document) {
return PlanBuilder(from, to, planType, LoadLocalConfig)
}
// PlanBuilder is a function that returns a transformation plan for a given diff between two files.
func PlanBuilder(from *tomledit.Document, to string, loadFn loadDestConfigFile) transform.Plan {
func PlanBuilder(from *tomledit.Document, to, planType string, loadFn loadDestConfigFile) (transform.Plan, *tomledit.Document) {
plan := transform.Plan{}
deletedSections := map[string]bool{}
target, err := loadFn(to)
target, err := loadFn(to, planType)
if err != nil {
panic(fmt.Errorf("failed to parse file: %w. This file should have been valid", err))
}
diffs := DiffKeys(from, target)
for _, diff := range diffs {
diff := diff
kv := diff.KV
var step transform.Step
@ -119,11 +145,105 @@ func PlanBuilder(from *tomledit.Document, to string, loadFn loadDestConfigFile)
plan = append(plan, step)
}
return plan
return plan, from
}
// NoPlan returns a no-op plan.
func NoPlan(_ *tomledit.Document, to string) transform.Plan {
func NoPlan(from *tomledit.Document, to, planType string) (transform.Plan, *tomledit.Document) {
fmt.Printf("no migration needed to %s\n", to)
return transform.Plan{}
return transform.Plan{}, from
}
// V2PlanBuilder is a function that returns a transformation plan to convert to v2 config
func V2PlanBuilder(from *tomledit.Document, to, planType string) (transform.Plan, *tomledit.Document) {
target, err := LoadLocalConfig(to, planType)
if err != nil {
panic(fmt.Errorf("failed to parse file: %w. This file should have been valid", err))
}
plan := transform.Plan{}
plan = updateMatchedKeysPlan(from, target, plan)
plan = applyKeyChangesPlan(from, plan)
return plan, target
}
// updateMatchedKeysPlan updates all matched keys with old key values
func updateMatchedKeysPlan(from, target *tomledit.Document, plan transform.Plan) transform.Plan {
matches := MatchKeys(from, target)
for oldKey, newKey := range matches {
oldEntry := getEntry(from, oldKey)
if oldEntry == nil {
continue
}
// check if the key "app-db-backend" exists and if its value is empty in the existing config
// If the value is empty, update the key value with the default value
// of v2 i.e., goleveldb to prevent network failures.
if isAppDBBackend(newKey, oldEntry) {
continue // lets keep app-db-backend with v2 default value
}
// update newKey value with old entry value
step := createUpdateStep(oldKey, newKey, oldEntry)
plan = append(plan, step)
}
return plan
}
// applyKeyChangesPlan checks if key changes are needed with the "to" version and applies them
func applyKeyChangesPlan(from *tomledit.Document, plan transform.Plan) transform.Plan {
changes := v2KeyChanges
for oldKey, newKeys := range changes {
oldEntry := getEntry(from, oldKey)
if oldEntry == nil {
continue
}
for _, newKey := range newKeys {
// check if the key "app-db-backend" exists and if its value is empty in the existing config
// If the value is empty, update the key value with the default value
// of v2 i.e., goleveldb to prevent network failures.
if isAppDBBackend(newKey, oldEntry) {
continue // lets keep app-db-backend with v2 default value
}
// update newKey value with old entry value
step := createUpdateStep(oldKey, newKey, oldEntry)
plan = append(plan, step)
}
}
return plan
}
// getEntry retrieves the first entry for the given key from the document
func getEntry(doc *tomledit.Document, key string) *parser.KeyValue {
splitKeys := strings.Split(key, ".")
entry := doc.First(splitKeys...)
if entry == nil || entry.KeyValue == nil {
return nil
}
return entry.KeyValue
}
// isAppDBBackend checks if the key is "store.app-db-backend" and the value is empty
func isAppDBBackend(key string, entry *parser.KeyValue) bool {
return key == "store.app-db-backend" && entry.Value.String() == `""`
}
// createUpdateStep creates a transformation step to update a key with a new key value
func createUpdateStep(oldKey, newKey string, oldEntry *parser.KeyValue) transform.Step {
return transform.Step{
Desc: fmt.Sprintf("updating %s key with %s key", oldKey, newKey),
T: transform.Func(func(_ context.Context, doc *tomledit.Document) error {
splitNewKeys := strings.Split(newKey, ".")
newEntry := doc.First(splitNewKeys...)
if newEntry == nil || newEntry.KeyValue == nil {
return nil
}
newEntry.KeyValue.Value = oldEntry.Value
return nil
}),
}
}

View File

@ -14,7 +14,6 @@ import (
"github.com/spf13/viper"
clientcfg "github.com/cosmos/cosmos-sdk/client/config"
srvcfg "github.com/cosmos/cosmos-sdk/server/config"
)
// Upgrade reads the configuration file at configPath and applies any
@ -28,34 +27,30 @@ import (
// Upgrade is a convenience wrapper for calls to LoadConfig, ApplyFixes, and
// CheckValid. If the caller requires more control over the behavior of the
// Upgrade, call those functions directly.
func Upgrade(ctx context.Context, plan transform.Plan, configPath, outputPath string, skipValidate bool) error {
func Upgrade(ctx context.Context, plan transform.Plan, doc *tomledit.Document, configPath, outputPath string, skipValidate bool) error {
if configPath == "" {
return errors.New("empty input configuration path")
}
doc, err := LoadConfig(configPath)
if err != nil {
return fmt.Errorf("loading config: %v", err)
}
// transforms doc and reports whether it succeeded.
if err := plan.Apply(ctx, doc); err != nil {
return fmt.Errorf("updating %q: %v", configPath, err)
return fmt.Errorf("updating %q: %w", configPath, err)
}
var buf bytes.Buffer
if err := tomledit.Format(&buf, doc); err != nil {
return fmt.Errorf("formatting config: %v", err)
return fmt.Errorf("formatting config: %w", err)
}
// allow to skip validation
if !skipValidate {
// verify that file is valid after applying fixes
if err := CheckValid(configPath, buf.Bytes()); err != nil {
return fmt.Errorf("updated config is invalid: %v", err)
return fmt.Errorf("updated config is invalid: %w", err)
}
}
var err error
if outputPath == "" {
_, err = os.Stdout.Write(buf.Bytes())
} else {
@ -77,14 +72,8 @@ func CheckValid(fileName string, data []byte) error {
switch {
case strings.HasSuffix(fileName, AppConfig):
var cfg srvcfg.Config
if err := v.Unmarshal(&cfg); err != nil {
return fmt.Errorf("failed to unmarshal as server config: %w", err)
}
if err := cfg.ValidateBasic(); err != nil {
return fmt.Errorf("server config invalid: %w", err)
}
// no validation of server config as v1 and v2 configs are both valid.
// any app.toml is simply considered as a server config.
case strings.HasSuffix(fileName, ClientConfig):
var cfg clientcfg.ClientConfig
if err := v.Unmarshal(&cfg); err != nil {

View File

@ -10,6 +10,7 @@ import (
)
func mustReadConfig(t *testing.T, path string) []byte {
t.Helper()
f, err := os.ReadFile(path)
if err != nil {
t.Fatalf("failed to open file: %v", err)
@ -34,9 +35,6 @@ func TestCheckValid(t *testing.T) {
err = confix.CheckValid("client.toml", []byte{})
assert.Error(t, err, "client config invalid: chain-id is empty")
err = confix.CheckValid("app.toml", []byte{})
assert.ErrorContains(t, err, "server config invalid")
err = confix.CheckValid("app.toml", mustReadConfig(t, "data/v0.45-app.toml"))
assert.NilError(t, err)

View File

@ -36,28 +36,72 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased]
## Features
## v1.7.1 - 2025-01-12
* [#12457](https://github.com/cosmos/cosmos-sdk/issues/12457) Add `cosmovisor pre-upgrade` command to manually add an upgrade to cosmovisor.
* [#15361](https://github.com/cosmos/cosmos-sdk/pull/15361) Add `cosmovisor config` command to display the configuration used by cosmovisor.
### Bug Fixes
## Client Breaking Changes
* [#23652](https://github.com/cosmos/cosmos-sdk/pull/23652) Fix issue with wrong directory placement when using `prepare-upgrade` for non archive.
* [#23653](https://github.com/cosmos/cosmos-sdk/pull/23653) Remove duplicate binary downloads during auto-download process
* [#14881](https://github.com/cosmos/cosmos-sdk/pull/14881) Cosmovisor supports only upgrade plan with a checksum. This is enforced by the `x/upgrade` module for better security.
## v1.7.0 - 2024-11-18
### Features
* [#21790](https://github.com/cosmos/cosmos-sdk/pull/21790) Add `add-batch-upgrade` command.
* [#21972](https://github.com/cosmos/cosmos-sdk/pull/21972) Add `prepare-upgrade` command
* [#21932](https://github.com/cosmos/cosmos-sdk/pull/21932) Add `cosmovisor show-upgrade-info` command to display the upgrade-info.json into stdout.
### Improvements
* [#21891](https://github.com/cosmos/cosmos-sdk/pull/21891) create `current` symlink as relative
* [#21462](https://github.com/cosmos/cosmos-sdk/pull/21462) Pass `stdin` to binary.
### Bug Fixes
* [#22528](https://github.com/cosmos/cosmos-sdk/pull/22528) Fix premature upgrades on restarting cosmovisor.
## v1.6.0 - 2024-08-12
## Improvements
* Bump `cosmossdk.io/x/upgrade` to v0.1.4 (including go-getter vulnerability fix)
* [#19995](https://github.com/cosmos/cosmos-sdk/pull/19995):
* `init command` writes the configuration to the config file only at the default path `DAEMON_HOME/cosmovisor/config.toml`.
* Provide `--cosmovisor-config` flag with value as args to provide the path to the configuration file in the `run` command. `run --cosmovisor-config <path> (other cmds with flags) ...`.
* Add `--cosmovisor-config` flag to provide `config.toml` path to the configuration file in root command used by `add-upgrade` and `config` subcommands.
* `config command` now displays the configuration from the config file if it is provided. If the config file is not provided, it will display the configuration from the environment variables.
## Bug Fixes
* [#20062](https://github.com/cosmos/cosmos-sdk/pull/20062) Fixed cosmovisor add-upgrade permissions
* [#20585](https://github.com/cosmos/cosmos-sdk/pull/20585) Always parse stdout and stderr
* [#20585](https://github.com/cosmos/cosmos-sdk/pull/20585) Pass right home to command `status`
* [#20585](https://github.com/cosmos/cosmos-sdk/pull/20585) Fix upgrades applied automatically (check two casing of sync_info)
## v1.5.0 - 2023-07-17
## Features
* [#16413](https://github.com/cosmos/cosmos-sdk/issues/16413) Add `cosmovisor add-upgrade` command to manually add an upgrade to cosmovisor.
* [#16573](https://github.com/cosmos/cosmos-sdk/pull/16573) Extend `cosmovisor` configuration with new log format options.
* [#16550](https://github.com/cosmos/cosmos-sdk/pull/16550) Add COSMOVISOR_CUSTOM_PREUPGRADE to cosmovisor to execute custom pre-upgrade scripts (separate from daemon `pre-upgrade` command).
* [#16963](https://github.com/cosmos/cosmos-sdk/pull/16963) Add DAEMON_SHUTDOWN_GRACE to send interrupt and wait before sending kill.
* [#15361](https://github.com/cosmos/cosmos-sdk/pull/15361) Add `cosmovisor config` command to display the configuration used by cosmovisor.
## Improvements
* [#16919](https://github.com/cosmos/cosmos-sdk/pull/16919) Add COSMOVISOR_DISABLE_RECASE to cosmovisor to disable automatic case change for plan name.
* [#14881](https://github.com/cosmos/cosmos-sdk/pull/14881) Refactor Cosmovisor to use `x/upgrade` validation logic.
* [#14881](https://github.com/cosmos/cosmos-sdk/pull/14881) Refactor Cosmovisor to depend only on the `x/upgrade` module.
* [#15362](https://github.com/cosmos/cosmos-sdk/pull/15362) Allow disabling Cosmovisor logs
* [#15362](https://github.com/cosmos/cosmos-sdk/pull/15362) Allow disabling Cosmovisor logs.
## v1.4.0 2022-10-23
## v1.4.0 - 2022-10-23
### API Breaking Changes
* [#13603](https://github.com/cosmos-sdk/pull/13603) Rename cosmovisor package to `cosmossdk.io/tools/cosmovisor`.
* [#13603](https://github.com/cosmos/cosmos-sdk/pull/13603) Rename cosmovisor package to `cosmossdk.io/tools/cosmovisor`.
## v1.3.0 2022-09-11
## v1.3.0 - 2022-09-11
### Improvements
@ -68,7 +112,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [#13221](https://github.com/cosmos/cosmos-sdk/pull/13221) Fix `go install`.
## v1.2.0 2022-07-26
## v1.2.0 - 2022-07-26
### Features
@ -85,7 +129,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [\#12188](https://github.com/cosmos/cosmos-sdk/pull/12188) Remove the possibility to set a time with only a number. `DAEMON_POLL_INTERVAL` env variable now only supports a duration (e.g. `100ms`, `30s`, `20m`).
## v1.1.0 2022-02-10
## v1.1.0 - 2022-02-10
### Features
@ -100,7 +144,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [\#10458](https://github.com/cosmos/cosmos-sdk/pull/10458) Fix version when using 'go install github.com/cosmos/cosmos-sdk/cosmovisor/cmd/cosmovisor@v1.0.0' to install cosmovisor.
## v1.0.0 2021-09-30
## v1.0.0 - 2021-09-30
### Features
@ -119,7 +163,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* [\#10128](https://github.com/cosmos/cosmos-sdk/pull/10128) Change default value of `DAEMON_RESTART_AFTER_UPGRADE` to `true`.
## v0.1 2021-08-06
## v0.1.0 - 2021-08-06
This is the first release and we started this changelog on 2021-07-01. See the [README](https://github.com/cosmos/cosmos-sdk/blob/release/cosmovisor/v0.1.x/cosmovisor/CHANGELOG.md) file for the full list of features.

View File

@ -4,6 +4,7 @@ all: cosmovisor test
cosmovisor:
go build -mod=readonly ./cmd/cosmovisor
@echo "cosmovisor binary has been successfully built in tools/cosmovisor/cosmovisor"
test:
go test -mod=readonly -race ./...

View File

@ -7,21 +7,22 @@ sidebar_position: 1
`cosmovisor` is a process manager for Cosmos SDK application binaries that automates application binary switch at chain upgrades.
It polls the `upgrade-info.json` file that is created by the x/upgrade module at upgrade height, and then can automatically download the new binary, stop the current binary, switch from the old binary to the new one, and finally restart the node with the new binary.
* [Cosmovisor](#cosmovisor)
* [Design](#design)
* [Contributing](#contributing)
* [Setup](#setup)
* [Design](#design)
* [Contributing](#contributing)
* [Setup](#setup)
* [Installation](#installation)
* [Command Line Arguments And Environment Variables](#command-line-arguments-and-environment-variables)
* [Folder Layout](#folder-layout)
* [Usage](#usage)
* [Usage](#usage)
* [Initialization](#initialization)
* [Detecting Upgrades](#detecting-upgrades)
* [Adding Upgrade Binary](#adding-upgrade-binary)
* [Auto-Download](#auto-download)
* [Example: SimApp Upgrade](#example-simapp-upgrade)
* [Preparing for an Upgrade](#preparing-for-an-upgrade)
* [Example: SimApp Upgrade](#example-simapp-upgrade)
* [Chain Setup](#chain-setup)
* [Prepare Cosmovisor and Start the Chain](#prepare-cosmovisor-and-start-the-chain)
* [Update App](#update-app)
* [Prepare Cosmovisor and Start the Chain](#prepare-cosmovisor-and-start-the-chain)
* [Update App](#update-app)
## Design
@ -35,7 +36,7 @@ Cosmovisor is designed to be used as a wrapper for a `Cosmos SDK` app:
*Note: If new versions of the application are not set up to run in-place store migrations, migrations will need to be run manually before restarting `cosmovisor` with the new binary. For this reason, we recommend applications adopt in-place store migrations.*
:::tip
Only the lastest version of cosmovisor is actively developed/maintained.
Only the latest version of cosmovisor is actively developed/maintained.
:::
:::warning
@ -52,7 +53,7 @@ Release branches have the following format `release/cosmovisor/vA.B.x`, where A
### Installation
You can download Cosmovisor from the [GitHub releases](https://github.com/cosmos/cosmos-sdk/releases/tag/cosmovisor%2Fv1.3.0).
You can download Cosmovisor from the [GitHub releases](https://github.com/cosmos/cosmos-sdk/releases/tag/cosmovisor%2Fv1.5.0).
To install the latest version of `cosmovisor`, run the following command:
@ -60,10 +61,10 @@ To install the latest version of `cosmovisor`, run the following command:
go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest
```
To install a previous version, you can specify the version. IMPORTANT: Chains that use Cosmos SDK v0.44.3 or earlier (eg v0.44.2) and want to use auto-download feature MUST use `cosmovisor v0.1.0`
To install a specific version, you can specify the version:
```shell
go install github.com/cosmos/cosmos-sdk/cosmovisor/cmd/cosmovisor@v0.1.0
go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@v1.5.0
```
Run `cosmovisor version` to check the cosmovisor version.
@ -71,7 +72,8 @@ Run `cosmovisor version` to check the cosmovisor version.
Alternatively, for building from source, simply run `make cosmovisor`. The binary will be located in `tools/cosmovisor`.
:::warning
Building from source using `make cosmovisor` won't display the correct `cosmovisor` version.
Installing cosmovisor using `go install` will display the correct `cosmovisor` version.
Building from source (`make cosmovisor`) or installing `cosmovisor` by other means won't display the correct version.
:::
### Command Line Arguments And Environment Variables
@ -82,26 +84,28 @@ The first argument passed to `cosmovisor` is the action for `cosmovisor` to take
* `run` - Run the configured binary using the rest of the provided arguments.
* `version` - Output the `cosmovisor` version and also run the binary with the `version` argument.
* `config` - Display the current `cosmovisor` configuration, that means displaying the environment variables value that `cosmovisor` is using.
* `add-upgrade` - Add an upgrade manually to `cosmovisor`.
* `add-upgrade` - Add an upgrade manually to `cosmovisor`. This command allow you to easily add the binary corresponding to an upgrade in cosmovisor.
All arguments passed to `cosmovisor run` will be passed to the application binary (as a subprocess). `cosmovisor` will return `/dev/stdout` and `/dev/stderr` of the subprocess as its own. For this reason, `cosmovisor run` cannot accept any command-line arguments other than those available to the application binary.
:::warning
Use of `cosmovisor` without one of the action arguments is deprecated. For backwards compatibility, if the first argument is not an action argument, `run` is assumed. However, this fallback might be removed in future versions, so it is recommended that you always provide `run`.
:::
`cosmovisor` reads its configuration from environment variables:
`cosmovisor` reads its configuration from environment variables, or its configuration file (use `--cosmovisor-config <path>`):
* `DAEMON_HOME` is the location where the `cosmovisor/` directory is kept that contains the genesis binary, the upgrade binaries, and any additional auxiliary files associated with each binary (e.g. `$HOME/.gaiad`, `$HOME/.regend`, `$HOME/.simd`, etc.).
* `DAEMON_NAME` is the name of the binary itself (e.g. `gaiad`, `regend`, `simd`, etc.).
* `DAEMON_ALLOW_DOWNLOAD_BINARIES` (*optional*), if set to `true`, will enable auto-downloading of new binaries (for security reasons, this is intended for full nodes rather than validators). By default, `cosmovisor` will not auto-download new binaries.
* `DAEMON_DOWNLOAD_MUST_HAVE_CHECKSUM` (*optional*, default = `false`), if `true` cosmovisor will require that a checksum is provided in the upgrade plan for the binary to be downloaded. If `false`, cosmovisor will not require a checksum to be provided, but still check the checksum if one is provided.
* `DAEMON_RESTART_AFTER_UPGRADE` (*optional*, default = `true`), if `true`, restarts the subprocess with the same command-line arguments and flags (but with the new binary) after a successful upgrade. Otherwise (`false`), `cosmovisor` stops running after an upgrade and requires the system administrator to manually restart it. Note restart is only after the upgrade and does not auto-restart the subprocess after an error occurs.
* `DAEMON_RESTART_DELAY` (*optional*, default none), allow a node operator to define a delay between the node halt (for upgrade) and backup by the specified time. The value must be a duration (e.g. `1s`).
* `DAEMON_SHUTDOWN_GRACE` (*optional*, default none), if set, send interrupt to binary and wait the specified time to allow for cleanup/cache flush to disk before sending the kill signal. The value must be a duration (e.g. `1s`).
* `DAEMON_POLL_INTERVAL` (*optional*, default 300 milliseconds), is the interval length for polling the upgrade plan file. The value must be a duration (e.g. `1s`).
* `DAEMON_DATA_BACKUP_DIR` option to set a custom backup directory. If not set, `DAEMON_HOME` is used.
* `UNSAFE_SKIP_BACKUP` (defaults to `false`), if set to `true`, upgrades directly without performing a backup. Otherwise (`false`, default) backs up the data before trying the upgrade. The default value of false is useful and recommended in case of failures and when a backup needed to rollback. We recommend using the default backup option `UNSAFE_SKIP_BACKUP=false`.
* `DAEMON_PREUPGRADE_MAX_RETRIES` (defaults to `0`). The maximum number of times to call [`pre-upgrade`](https://docs.cosmos.network/main/building-apps/app-upgrade#pre-upgrade-handling) in the application after exit status of `31`. After the maximum number of retries, Cosmovisor fails the upgrade.
* `DAEMON_PREUPGRADE_MAX_RETRIES` (defaults to `0`). The maximum number of times to call [`pre-upgrade`](https://docs.cosmos.network/main/build/building-apps/app-upgrade#pre-upgrade-handling) in the application after exit status of `31`. After the maximum number of retries, Cosmovisor fails the upgrade.
* `COSMOVISOR_DISABLE_LOGS` (defaults to `false`). If set to true, this will disable Cosmovisor logs (but not the underlying process) completely. This may be useful, for example, when a Cosmovisor subcommand you are executing returns a valid JSON you are then parsing, as logs added by Cosmovisor make this output not a valid JSON.
* `COSMOVISOR_COLOR_LOGS` (defaults to `true`). If set to true, this will colorise Cosmovisor logs (but not the underlying process).
* `COSMOVISOR_TIMEFORMAT_LOGS` (defaults to `kitchen`). If set to a value (`layout|ansic|unixdate|rubydate|rfc822|rfc822z|rfc850|rfc1123|rfc1123z|rfc3339|rfc3339nano|kitchen`), this will add timestamp prefix to Cosmovisor logs (but not the underlying process).
* `COSMOVISOR_CUSTOM_PREUPGRADE` (defaults to ``). If set, this will run $DAEMON_HOME/cosmovisor/$COSMOVISOR_CUSTOM_PREUPGRADE prior to upgrade with the arguments [ upgrade.Name, upgrade.Height ]. Executes a custom script (separate and prior to the chain daemon pre-upgrade command)
* `COSMOVISOR_DISABLE_RECASE` (defaults to `false`). If set to true, the upgrade directory will expected to match the upgrade plan name without any case changes
### Folder Layout
@ -114,15 +118,16 @@ Use of `cosmovisor` without one of the action arguments is deprecated. For backw
│   └── bin
│   └── $DAEMON_NAME
└── upgrades
└── <name>
├── bin
│   └── $DAEMON_NAME
└── upgrade-info.json
│ └── <name>
│ ├── bin
│ │   └── $DAEMON_NAME
│ └── upgrade-info.json
└── preupgrade.sh (optional)
```
The `cosmovisor/` directory incudes a subdirectory for each version of the application (i.e. `genesis` or `upgrades/<name>`). Within each subdirectory is the application binary (i.e. `bin/$DAEMON_NAME`) and any additional auxiliary files associated with each binary. `current` is a symbolic link to the currently active directory (i.e. `genesis` or `upgrades/<name>`). The `name` variable in `upgrades/<name>` is the lowercased URI-encoded name of the upgrade as specified in the upgrade module plan. Note that the upgrade name path are normalized to be lowercased: for instance, `MyUpgrade` is normalized to `myupgrade`, and its path is `upgrades/myupgrade`.
The `cosmovisor/` directory includes a subdirectory for each version of the application (i.e. `genesis` or `upgrades/<name>`). Within each subdirectory is the application binary (i.e. `bin/$DAEMON_NAME`) and any additional auxiliary files associated with each binary. `current` is a symbolic link to the currently active directory (i.e. `genesis` or `upgrades/<name>`). The `name` variable in `upgrades/<name>` is the lowercased URI-encoded name of the upgrade as specified in the upgrade module plan. Note that the upgrade name path are normalized to be lowercased: for instance, `MyUpgrade` is normalized to `myupgrade`, and its path is `upgrades/myupgrade`.
Please note that `$DAEMON_HOME/cosmovisor` only stores the *application binaries*. The `cosmovisor` binary itself can be stored in any typical location (e.g. `/usr/local/bin`). The application will continue to store its data in the default data directory (e.g. `$HOME/.simapp`) or the data directory specified with the `--home` flag. `$DAEMON_HOME` is independent of the data directory and can be set to any location. If you set `$DAEMON_HOME` to the same directory as the data directory, you will end up with a configuation like the following:
Please note that `$DAEMON_HOME/cosmovisor` only stores the *application binaries*. The `cosmovisor` binary itself can be stored in any typical location (e.g. `/usr/local/bin`). The application will continue to store its data in the default data directory (e.g. `$HOME/.simapp`) or the data directory specified with the `--home` flag. `$DAEMON_HOME` is dependent of the data directory and must be set to the same directory as the data directory, you will end up with a configuration like the following:
```text
.simapp
@ -179,6 +184,18 @@ When the upgrade mechanism is triggered, `cosmovisor` will:
1. if `DAEMON_ALLOW_DOWNLOAD_BINARIES` is enabled, start by auto-downloading a new binary into `cosmovisor/<name>/bin` (where `<name>` is the `upgrade-info.json:name` attribute);
2. update the `current` symbolic link to point to the new directory and save `data/upgrade-info.json` to `cosmovisor/current/upgrade-info.json`.
### Adding Upgrade Binary
`cosmovisor` has an `add-upgrade` command that allows to easily link a binary to an upgrade. It creates a new folder in `cosmovisor/upgrades/<name>` and copies the provided executable file to `cosmovisor/upgrades/<name>/bin/<DAEMON_NAME>`.
Using the `--upgrade-height` flag allows to specify at which height the binary should be switched, without going via a gorvernance proposal.
This enables support for an emergency coordinated upgrades where the binary must be switched at a specific height, but there is no time to go through a governance proposal.
:::warning
`--upgrade-height` creates an `upgrade-info.json` file. This means if a chain upgrade via governance proposal is executed before the specified height with `--upgrade-height`, the governance proposal will overwrite the `upgrade-info.json` plan created by `add-upgrade --upgrade-height <height>`.
Take this into consideration when using `--upgrade-height`.
:::
### Auto-Download
Generally, `cosmovisor` requires that the system administrator place all relevant binaries on disk before the upgrade happens. However, for people who don't need such control and want an automated setup (maybe they are syncing a non-validating fullnode and want to do little maintenance), there is another option.
@ -246,31 +263,63 @@ The result will look something like the following: `29139e1381b8177aec909fab9a75
You can also use `sha512sum` if you would prefer to use longer hashes, or `md5sum` if you would prefer to use broken hashes. Whichever you choose, make sure to set the hash algorithm properly in the checksum argument to the URL.
### Preparing for an Upgrade
To prepare for an upgrade, use the `prepare-upgrade` command:
```shell
cosmovisor prepare-upgrade
```
This command performs the following actions:
1. Retrieves upgrade information directly from the blockchain about the next scheduled upgrade.
2. Downloads the new binary specified in the upgrade plan.
3. Verifies the binary's checksum (if required by configuration).
4. Places the new binary in the appropriate directory for Cosmovisor to use during the upgrade.
The `prepare-upgrade` command provides detailed logging throughout the process, including:
* The name and height of the upcoming upgrade
* The URL from which the new binary is being downloaded
* Confirmation of successful download and verification
* The path where the new binary has been placed
Example output:
```bash
INFO Preparing for upgrade name=v1.0.0 height=1000000
INFO Downloading upgrade binary url=https://example.com/binary/v1.0.0?checksum=sha256:339911508de5e20b573ce902c500ee670589073485216bee8b045e853f24bce8
INFO Upgrade preparation complete name=v1.0.0 height=1000000
```
*Note: The current way of downloading manually and placing the binary at the right place would still work.*
## Example: SimApp Upgrade
The following instructions provide a demonstration of `cosmovisor` using the simulation application (`simapp`) shipped with the Cosmos SDK's source code. The following commands are to be run from within the `cosmos-sdk` repository.
### Chain Setup
Let's create a new chain using the `v0.44` version of simapp (the Cosmos SDK demo app):
Let's create a new chain using the `v0.47.4` version of simapp (the Cosmos SDK demo app):
```shell
git checkout v0.44.6
git checkout v0.47.4
make build
```
Clean `~/.simapp` (never do this in a production environment):
```shell
./build/simd unsafe-reset-all
./build/simd tendermint unsafe-reset-all
```
Set up app config:
```shell
./build/simd config set client chain-id test
./build/simd config set client keyring-backend test
./build/simd config set client broadcast-mode sync
./build/simd config chain-id test
./build/simd config keyring-backend test
./build/simd config broadcast-mode sync
```
Initialize the node and overwrite any previous genesis file (never do this in a production environment):
@ -279,16 +328,10 @@ Initialize the node and overwrite any previous genesis file (never do this in a
./build/simd init test --chain-id test --overwrite
```
Set the minimum gas price to `0stake` in `~/.simapp/config/app.toml`:
```shell
minimum-gas-prices = "0stake"
```
For the sake of this demonstration, amend `voting_period` in `genesis.json` to a reduced time of 20 seconds (`20s`):
```shell
cat <<< $(jq '.app_state.gov.voting_params.voting_period = "20s"' $HOME/.simapp/config/genesis.json) > $HOME/.simapp/config/genesis.json
cat <<< $(jq '.app_state.gov.params.voting_period = "20s"' $HOME/.simapp/config/genesis.json) > $HOME/.simapp/config/genesis.json
```
Create a validator, and setup genesis transaction:
@ -315,24 +358,29 @@ Set the optional environment variable to trigger an automatic app restart:
export DAEMON_RESTART_AFTER_UPGRADE=true
```
Create the folder for the genesis binary and copy the `simd` binary:
Initialize cosmovisor with the current binary:
```shell
mkdir -p $DAEMON_HOME/cosmovisor/genesis/bin
cp ./build/simd $DAEMON_HOME/cosmovisor/genesis/bin
cosmovisor init ./build/simd
```
Now you can run cosmovisor with simapp v0.44:
Now you can run cosmovisor with simapp v0.47.4:
```shell
cosmovisor run start
```
#### Update App
### Update App
Update app to the latest version (e.g. v0.45).
Update app to the latest version (e.g. v0.50.0).
Next, we can add a migration - which is defined using `x/upgrade` [upgrade plan](https://github.com/cosmos/cosmos-sdk/blob/main/docs/core/upgrade.md) (you may refer to a past version if you are using an older Cosmos SDK release). In a migration we can do any deterministic state change.
:::note
Migration plans are defined using the `x/upgrade` module and described in [In-Place Store Migrations](https://github.com/cosmos/cosmos-sdk/blob/main/docs/learn/advanced/15-upgrade.md). Migrations can perform any deterministic state change.
The migration plan to upgrade the simapp from v0.47 to v0.50 is defined in `simapp/upgrade.go`.
:::
Build the new version `simd` binary:
@ -349,32 +397,13 @@ The migration name must match the one defined in the migration plan.
:::
```shell
mkdir -p $DAEMON_HOME/cosmovisor/upgrades/test1/bin
cp ./build/simd $DAEMON_HOME/cosmovisor/upgrades/test1/bin
cosmovisor add-upgrade v047-to-v050 ./build/simd
```
Open a new terminal window and submit an upgrade proposal along with a deposit and a vote (these commands must be run within 20 seconds of each other):
**<= v0.45**:
```shell
./build/simd tx gov submit-proposal software-upgrade test1 --title upgrade --description upgrade --upgrade-height 200 --from validator --yes
./build/simd tx gov deposit 1 10000000stake --from validator --yes
./build/simd tx gov vote 1 yes --from validator --yes
```
**v0.46, v0.47**:
```shell
./build/simd tx gov submit-legacy-proposal software-upgrade test1 --title upgrade --description upgrade --upgrade-height 200 --from validator --yes
./build/simd tx gov deposit 1 10000000stake --from validator --yes
./build/simd tx gov vote 1 yes --from validator --yes
```
**>= v0.50+**:
```shell
./build/simd tx upgrade software-upgrade test1 --title upgrade --summary upgrade --upgrade-height 200 --from validator --yes
./build/simd tx upgrade software-upgrade v047-to-v050 --title upgrade --summary upgrade --upgrade-height 200 --upgrade-info "{}" --no-validate --from validator --yes
./build/simd tx gov deposit 1 10000000stake --from validator --yes
./build/simd tx gov vote 1 yes --from validator --yes
```

View File

@ -1,7 +1,9 @@
# Cosmovisor v1.4.0 Release Notes
# Cosmovisor v1.5.0 Release Notes
* Rename cosmovisor package to `cosmossdk.io/tools/cosmovisor`.
See the [CHANGELOG](https://github.com/cosmos/cosmos-sdk/blob/tools/cosmovisor/v1.5.0/tools/cosmovisor/CHANGELOG.md) for details on the changes in v1.5.0.
## Changelog
## Installation instructions
For more details, please see the [CHANGELOG](https://github.com/cosmos/cosmos-sdk/blob/tools/cosmovisor/v1.4.0/tools/cosmovisor/CHANGELOG.md).
```go
go install cosmossdk.io/tools/cosmovisor/cmd/cosmovisor@latest
```

View File

@ -2,7 +2,9 @@ package cosmovisor
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/url"
"os"
"path/filepath"
@ -10,24 +12,33 @@ import (
"strings"
"time"
"github.com/pelletier/go-toml/v2"
"github.com/spf13/viper"
"cosmossdk.io/log"
cverrors "cosmossdk.io/tools/cosmovisor/errors"
"cosmossdk.io/x/upgrade/plan"
upgradetypes "cosmossdk.io/x/upgrade/types"
)
// environment variable names
const (
EnvHome = "DAEMON_HOME"
EnvName = "DAEMON_NAME"
EnvDownloadBin = "DAEMON_ALLOW_DOWNLOAD_BINARIES"
EnvRestartUpgrade = "DAEMON_RESTART_AFTER_UPGRADE"
EnvRestartDelay = "DAEMON_RESTART_DELAY"
EnvSkipBackup = "UNSAFE_SKIP_BACKUP"
EnvDataBackupPath = "DAEMON_DATA_BACKUP_DIR"
EnvInterval = "DAEMON_POLL_INTERVAL"
EnvPreupgradeMaxRetries = "DAEMON_PREUPGRADE_MAX_RETRIES"
EnvDisableLogs = "COSMOVISOR_DISABLE_LOGS"
EnvHome = "DAEMON_HOME"
EnvName = "DAEMON_NAME"
EnvDownloadBin = "DAEMON_ALLOW_DOWNLOAD_BINARIES"
EnvDownloadMustHaveChecksum = "DAEMON_DOWNLOAD_MUST_HAVE_CHECKSUM"
EnvRestartUpgrade = "DAEMON_RESTART_AFTER_UPGRADE"
EnvRestartDelay = "DAEMON_RESTART_DELAY"
EnvShutdownGrace = "DAEMON_SHUTDOWN_GRACE"
EnvSkipBackup = "UNSAFE_SKIP_BACKUP"
EnvDataBackupPath = "DAEMON_DATA_BACKUP_DIR"
EnvInterval = "DAEMON_POLL_INTERVAL"
EnvPreupgradeMaxRetries = "DAEMON_PREUPGRADE_MAX_RETRIES"
EnvGRPCAddress = "DAEMON_GRPC_ADDRESS"
EnvDisableLogs = "COSMOVISOR_DISABLE_LOGS"
EnvColorLogs = "COSMOVISOR_COLOR_LOGS"
EnvTimeFormatLogs = "COSMOVISOR_TIMEFORMAT_LOGS"
EnvCustomPreupgrade = "COSMOVISOR_CUSTOM_PREUPGRADE"
EnvDisableRecase = "COSMOVISOR_DISABLE_RECASE"
)
const (
@ -35,23 +46,30 @@ const (
genesisDir = "genesis"
upgradesDir = "upgrades"
currentLink = "current"
)
// must be the same as x/upgrade/types.UpgradeInfoFilename
const defaultFilename = "upgrade-info.json"
cfgFileName = "config"
cfgExtension = "toml"
)
// Config is the information passed in to control the daemon
type Config struct {
Home string
Name string
AllowDownloadBinaries bool
RestartAfterUpgrade bool
RestartDelay time.Duration
PollInterval time.Duration
UnsafeSkipBackup bool
DataBackupPath string
PreupgradeMaxRetries int
DisableLogs bool
Home string `toml:"daemon_home" mapstructure:"daemon_home"`
Name string `toml:"daemon_name" mapstructure:"daemon_name"`
AllowDownloadBinaries bool `toml:"daemon_allow_download_binaries" mapstructure:"daemon_allow_download_binaries" default:"false"`
DownloadMustHaveChecksum bool `toml:"daemon_download_must_have_checksum" mapstructure:"daemon_download_must_have_checksum" default:"false"`
RestartAfterUpgrade bool `toml:"daemon_restart_after_upgrade" mapstructure:"daemon_restart_after_upgrade" default:"true"`
RestartDelay time.Duration `toml:"daemon_restart_delay" mapstructure:"daemon_restart_delay"`
ShutdownGrace time.Duration `toml:"daemon_shutdown_grace" mapstructure:"daemon_shutdown_grace"`
PollInterval time.Duration `toml:"daemon_poll_interval" mapstructure:"daemon_poll_interval" default:"300ms"`
UnsafeSkipBackup bool `toml:"unsafe_skip_backup" mapstructure:"unsafe_skip_backup" default:"false"`
DataBackupPath string `toml:"daemon_data_backup_dir" mapstructure:"daemon_data_backup_dir"`
PreUpgradeMaxRetries int `toml:"daemon_preupgrade_max_retries" mapstructure:"daemon_preupgrade_max_retries" default:"0"`
GRPCAddress string `toml:"daemon_grpc_address" mapstructure:"daemon_grpc_address"`
DisableLogs bool `toml:"cosmovisor_disable_logs" mapstructure:"cosmovisor_disable_logs" default:"false"`
ColorLogs bool `toml:"cosmovisor_color_logs" mapstructure:"cosmovisor_color_logs" default:"true"`
TimeFormatLogs string `toml:"cosmovisor_timeformat_logs" mapstructure:"cosmovisor_timeformat_logs" default:"kitchen"`
CustomPreUpgrade string `toml:"cosmovisor_custom_preupgrade" mapstructure:"cosmovisor_custom_preupgrade" default:""`
DisableRecase bool `toml:"cosmovisor_disable_recase" mapstructure:"cosmovisor_disable_recase" default:"false"`
// currently running upgrade
currentUpgrade upgradetypes.Plan
@ -62,6 +80,11 @@ func (cfg *Config) Root() string {
return filepath.Join(cfg.Home, rootName)
}
// DefaultCfgPath returns the default path to the configuration file.
func (cfg *Config) DefaultCfgPath() string {
return filepath.Join(cfg.Root(), cfgFileName+"."+cfgExtension)
}
// GenesisBin is the path to the genesis binary - must be in place to start manager
func (cfg *Config) GenesisBin() string {
return filepath.Join(cfg.Root(), genesisDir, "bin", cfg.Name)
@ -85,19 +108,29 @@ func (cfg *Config) BaseUpgradeDir() string {
// UpgradeInfoFilePath is the expected upgrade-info filename created by `x/upgrade/keeper`.
func (cfg *Config) UpgradeInfoFilePath() string {
return filepath.Join(cfg.Home, "data", defaultFilename)
return filepath.Join(cfg.Home, "data", upgradetypes.UpgradeInfoFilename)
}
// UpgradeInfoBatchFilePath is the same as UpgradeInfoFilePath but with a batch suffix.
func (cfg *Config) UpgradeInfoBatchFilePath() string {
return cfg.UpgradeInfoFilePath() + ".batch"
}
// SymLinkToGenesis creates a symbolic link from "./current" to the genesis directory.
func (cfg *Config) SymLinkToGenesis() (string, error) {
genesis := filepath.Join(cfg.Root(), genesisDir)
link := filepath.Join(cfg.Root(), currentLink)
if err := os.Symlink(genesis, link); err != nil {
// workdir is set to cosmovisor directory so relative
// symlinks are getting resolved correctly
if err := os.Symlink(genesisDir, currentLink); err != nil {
return "", err
}
res, err := filepath.EvalSymlinks(cfg.GenesisBin())
if err != nil {
return "", err
}
// and return the genesis binary
return cfg.GenesisBin(), nil
return res, nil
}
// WaitRestartDelay will block and wait until the RestartDelay has elapsed.
@ -111,38 +144,82 @@ func (cfg *Config) WaitRestartDelay() {
// This will resolve the symlink to the underlying directory to make it easier to debug
func (cfg *Config) CurrentBin() (string, error) {
cur := filepath.Join(cfg.Root(), currentLink)
// if nothing here, fallback to genesis
info, err := os.Lstat(cur)
if err != nil {
// Create symlink to the genesis
return cfg.SymLinkToGenesis()
}
// if it is there, ensure it is a symlink
if info.Mode()&os.ModeSymlink == 0 {
info, err := os.Lstat(cur)
if err != nil || (info.Mode()&os.ModeSymlink == 0) {
// Create symlink to the genesis
return cfg.SymLinkToGenesis()
}
// resolve it
dest, err := os.Readlink(cur)
res, err := filepath.EvalSymlinks(cur)
if err != nil {
// Create symlink to the genesis
return cfg.SymLinkToGenesis()
}
// and return the binary
binpath := filepath.Join(dest, "bin", cfg.Name)
binpath := filepath.Join(res, "bin", cfg.Name)
return binpath, nil
}
// GetConfigFromFile will read the configuration from the config file at the given path.
// If the file path is not provided, it will read the configuration from the ENV variables.
// If a file path is provided and ENV variables are set, they will override the values in the file.
func GetConfigFromFile(filePath string) (*Config, error) {
if filePath == "" {
return GetConfigFromEnv(false)
}
// ensure the file exist
if _, err := os.Stat(filePath); err != nil {
return nil, fmt.Errorf("config not found: at %s : %w", filePath, err)
}
v := viper.New()
// read the configuration from the file
v.SetConfigFile(filePath)
// load the env variables
// if the env variable is set, it will override the value provided by the config
v.AutomaticEnv()
if err := v.ReadInConfig(); err != nil {
return nil, fmt.Errorf("failed to read config file: %w", err)
}
cfg := &Config{}
if err := v.Unmarshal(cfg); err != nil {
return nil, fmt.Errorf("failed to unmarshal configuration: %w", err)
}
var (
err error
errs []error
)
if cfg.TimeFormatLogs, err = getTimeFormatOption(cfg.TimeFormatLogs); err != nil {
errs = append(errs, err)
}
errs = append(errs, cfg.validate()...)
if len(errs) > 0 {
return nil, errors.Join(errs...)
}
return cfg, nil
}
// GetConfigFromEnv will read the environmental variables into a config
// and then validate it is reasonable
func GetConfigFromEnv() (*Config, error) {
func GetConfigFromEnv(skipValidate bool) (*Config, error) {
var errs []error
cfg := &Config{
Home: os.Getenv(EnvHome),
Name: os.Getenv(EnvName),
DataBackupPath: os.Getenv(EnvDataBackupPath),
Home: os.Getenv(EnvHome),
Name: os.Getenv(EnvName),
DataBackupPath: os.Getenv(EnvDataBackupPath),
CustomPreUpgrade: os.Getenv(EnvCustomPreupgrade),
}
if cfg.DataBackupPath == "" {
@ -150,16 +227,28 @@ func GetConfigFromEnv() (*Config, error) {
}
var err error
if cfg.AllowDownloadBinaries, err = booleanOption(EnvDownloadBin, false); err != nil {
if cfg.AllowDownloadBinaries, err = BooleanOption(EnvDownloadBin, false); err != nil {
errs = append(errs, err)
}
if cfg.RestartAfterUpgrade, err = booleanOption(EnvRestartUpgrade, true); err != nil {
if cfg.DownloadMustHaveChecksum, err = BooleanOption(EnvDownloadMustHaveChecksum, false); err != nil {
errs = append(errs, err)
}
if cfg.UnsafeSkipBackup, err = booleanOption(EnvSkipBackup, false); err != nil {
if cfg.RestartAfterUpgrade, err = BooleanOption(EnvRestartUpgrade, true); err != nil {
errs = append(errs, err)
}
if cfg.DisableLogs, err = booleanOption(EnvDisableLogs, false); err != nil {
if cfg.UnsafeSkipBackup, err = BooleanOption(EnvSkipBackup, false); err != nil {
errs = append(errs, err)
}
if cfg.DisableLogs, err = BooleanOption(EnvDisableLogs, false); err != nil {
errs = append(errs, err)
}
if cfg.ColorLogs, err = BooleanOption(EnvColorLogs, true); err != nil {
errs = append(errs, err)
}
if cfg.TimeFormatLogs, err = TimeFormatOptionFromEnv(EnvTimeFormatLogs, time.Kitchen); err != nil {
errs = append(errs, err)
}
if cfg.DisableRecase, err = BooleanOption(EnvDisableRecase, false); err != nil {
errs = append(errs, err)
}
@ -186,19 +275,51 @@ func GetConfigFromEnv() (*Config, error) {
}
}
envPreupgradeMaxRetriesVal := os.Getenv(EnvPreupgradeMaxRetries)
if cfg.PreupgradeMaxRetries, err = strconv.Atoi(envPreupgradeMaxRetriesVal); err != nil && envPreupgradeMaxRetriesVal != "" {
cfg.ShutdownGrace = 0 // default value but makes it explicit
shutdownGrace := os.Getenv(EnvShutdownGrace)
if shutdownGrace != "" {
val, err := parseEnvDuration(shutdownGrace)
if err != nil {
errs = append(errs, fmt.Errorf("invalid: %s: %w", EnvShutdownGrace, err))
} else {
cfg.ShutdownGrace = val
}
}
envPreUpgradeMaxRetriesVal := os.Getenv(EnvPreupgradeMaxRetries)
if cfg.PreUpgradeMaxRetries, err = strconv.Atoi(envPreUpgradeMaxRetriesVal); err != nil && envPreUpgradeMaxRetriesVal != "" {
errs = append(errs, fmt.Errorf("%s could not be parsed to int: %w", EnvPreupgradeMaxRetries, err))
}
errs = append(errs, cfg.validate()...)
if len(errs) > 0 {
return nil, cverrors.FlattenErrors(errs...)
cfg.GRPCAddress = os.Getenv(EnvGRPCAddress)
if cfg.GRPCAddress == "" {
cfg.GRPCAddress = "localhost:9090"
}
if !skipValidate {
errs = append(errs, cfg.validate()...)
if len(errs) > 0 {
return nil, errors.Join(errs...)
}
}
return cfg, nil
}
func (cfg *Config) Logger(dst io.Writer) log.Logger {
var logger log.Logger
if cfg.DisableLogs {
logger = log.NewNopLogger()
} else {
logger = log.NewLogger(dst,
log.ColorOption(cfg.ColorLogs),
log.TimeFormatOption(cfg.TimeFormatLogs)).With(log.ModuleKey, "cosmovisor")
}
return logger
}
func parseEnvDuration(input string) (time.Duration, error) {
duration, err := time.ParseDuration(input)
if err != nil {
@ -206,26 +327,12 @@ func parseEnvDuration(input string) (time.Duration, error) {
}
if duration <= 0 {
return 0, fmt.Errorf("must be greater than 0")
return 0, errors.New("must be greater than 0")
}
return duration, nil
}
// LogConfigOrError logs either the config details or the error.
func LogConfigOrError(logger log.Logger, cfg *Config, err error) {
if cfg == nil && err == nil {
return
}
logger.Info("configuration:")
switch {
case err != nil:
cverrors.LogErrors(logger, "configuration errors found", err)
case cfg != nil:
logger.Info(cfg.DetailString())
}
}
// validate returns an error if this config is invalid.
// it enforces Home/cosmovisor is a valid directory and exists,
// and that Name is set
@ -285,24 +392,23 @@ func (cfg *Config) SetCurrentUpgrade(u upgradetypes.Plan) (rerr error) {
}
// set a symbolic link
link := filepath.Join(cfg.Root(), currentLink)
safeName := url.PathEscape(u.Name)
upgrade := filepath.Join(cfg.Root(), upgradesDir, safeName)
upgrade := filepath.Join(upgradesDir, safeName)
// remove link if it exists
if _, err := os.Stat(link); err == nil {
if err := os.Remove(link); err != nil {
if _, err := os.Stat(currentLink); err == nil {
if err := os.Remove(currentLink); err != nil {
return fmt.Errorf("failed to remove existing link: %w", err)
}
}
// point to the new directory
if err := os.Symlink(upgrade, link); err != nil {
if err := os.Symlink(upgrade, currentLink); err != nil {
return fmt.Errorf("creating current symlink: %w", err)
}
cfg.currentUpgrade = u
f, err := os.Create(filepath.Join(upgrade, upgradetypes.UpgradeInfoFilename))
f, err := os.Create(filepath.Join(cfg.Root(), upgrade, upgradetypes.UpgradeInfoFilename))
if err != nil {
return err
}
@ -321,6 +427,7 @@ func (cfg *Config) SetCurrentUpgrade(u upgradetypes.Plan) (rerr error) {
return err
}
// UpgradeInfo returns the current upgrade info
func (cfg *Config) UpgradeInfo() (upgradetypes.Plan, error) {
if cfg.currentUpgrade.Name != "" {
return cfg.currentUpgrade, nil
@ -347,8 +454,8 @@ returnError:
return cfg.currentUpgrade, fmt.Errorf("failed to read %q: %w", filename, err)
}
// checks and validates env option
func booleanOption(name string, defaultVal bool) (bool, error) {
// BooleanOption checks and validate env option
func BooleanOption(name string, defaultVal bool) (bool, error) {
p := strings.ToLower(os.Getenv(name))
switch p {
case "":
@ -361,19 +468,99 @@ func booleanOption(name string, defaultVal bool) (bool, error) {
return false, fmt.Errorf("env variable %q must have a boolean value (\"true\" or \"false\"), got %q", name, p)
}
// TimeFormatOptionFromEnv checks and validates the time format option
func TimeFormatOptionFromEnv(env, defaultVal string) (string, error) {
val, set := os.LookupEnv(env)
if !set {
return defaultVal, nil
}
return getTimeFormatOption(val)
}
func getTimeFormatOption(val string) (string, error) {
switch val {
case "layout":
return time.Layout, nil
case "ansic":
return time.ANSIC, nil
case "unixdate":
return time.UnixDate, nil
case "rubydate":
return time.RubyDate, nil
case "rfc822":
return time.RFC822, nil
case "rfc822z":
return time.RFC822Z, nil
case "rfc850":
return time.RFC850, nil
case "rfc1123":
return time.RFC1123, nil
case "rfc1123z":
return time.RFC1123Z, nil
case "rfc3339":
return time.RFC3339, nil
case "rfc3339nano":
return time.RFC3339Nano, nil
case "kitchen":
return time.Kitchen, nil
case "":
return "", nil
}
return "", fmt.Errorf("env variable %q must have a timeformat value (\"layout|ansic|unixdate|rubydate|rfc822|rfc822z|rfc850|rfc1123|rfc1123z|rfc3339|rfc3339nano|kitchen\"), got %q", EnvTimeFormatLogs, val)
}
// ValueToTimeFormatOption converts the time format option to the env value
func ValueToTimeFormatOption(format string) string {
switch format {
case time.Layout:
return "layout"
case time.ANSIC:
return "ansic"
case time.UnixDate:
return "unixdate"
case time.RubyDate:
return "rubydate"
case time.RFC822:
return "rfc822"
case time.RFC822Z:
return "rfc822z"
case time.RFC850:
return "rfc850"
case time.RFC1123:
return "rfc1123"
case time.RFC1123Z:
return "rfc1123z"
case time.RFC3339:
return "rfc3339"
case time.RFC3339Nano:
return "rfc3339nano"
case time.Kitchen:
return "kitchen"
default:
return ""
}
}
// DetailString returns a multi-line string with details about this config.
func (cfg Config) DetailString() string {
configEntries := []struct{ name, value string }{
{EnvHome, cfg.Home},
{EnvName, cfg.Name},
{EnvDownloadBin, fmt.Sprintf("%t", cfg.AllowDownloadBinaries)},
{EnvDownloadMustHaveChecksum, fmt.Sprintf("%t", cfg.DownloadMustHaveChecksum)},
{EnvRestartUpgrade, fmt.Sprintf("%t", cfg.RestartAfterUpgrade)},
{EnvRestartDelay, cfg.RestartDelay.String()},
{EnvShutdownGrace, cfg.ShutdownGrace.String()},
{EnvInterval, cfg.PollInterval.String()},
{EnvSkipBackup, fmt.Sprintf("%t", cfg.UnsafeSkipBackup)},
{EnvDataBackupPath, cfg.DataBackupPath},
{EnvPreupgradeMaxRetries, fmt.Sprintf("%d", cfg.PreupgradeMaxRetries)},
{EnvPreupgradeMaxRetries, fmt.Sprintf("%d", cfg.PreUpgradeMaxRetries)},
{EnvDisableLogs, fmt.Sprintf("%t", cfg.DisableLogs)},
{EnvColorLogs, fmt.Sprintf("%t", cfg.ColorLogs)},
{EnvTimeFormatLogs, cfg.TimeFormatLogs},
{EnvCustomPreupgrade, cfg.CustomPreUpgrade},
{EnvDisableRecase, fmt.Sprintf("%t", cfg.DisableRecase)},
}
derivedEntries := []struct{ name, value string }{
@ -402,3 +589,48 @@ func (cfg Config) DetailString() string {
}
return sb.String()
}
// Export exports the configuration to a file at the cosmovisor root directory.
func (cfg Config) Export() (string, error) {
// always use the default path
path := filepath.Clean(cfg.DefaultCfgPath())
// check if config file already exists ask user if they want to overwrite it
if _, err := os.Stat(path); err == nil {
// ask user if they want to overwrite the file
if !askForConfirmation(fmt.Sprintf("file %s already exists, do you want to overwrite it?", path)) {
cfg.Logger(os.Stdout).Info("file already exists, not overriding")
return path, nil
}
}
// create the file
file, err := os.Create(filepath.Clean(path))
if err != nil {
return "", fmt.Errorf("failed to create configuration file: %w", err)
}
// convert the time value to its format option
cfg.TimeFormatLogs = ValueToTimeFormatOption(cfg.TimeFormatLogs)
defer file.Close()
// write the configuration to the file
err = toml.NewEncoder(file).Encode(cfg)
if err != nil {
return "", fmt.Errorf("failed to encode configuration: %w", err)
}
return path, nil
}
func askForConfirmation(str string) bool {
var response string
fmt.Printf("%s [y/n]: ", str)
_, err := fmt.Scanln(&response)
if err != nil {
return false
}
return strings.ToLower(response) == "y"
}

View File

@ -1,21 +1,17 @@
package cosmovisor
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"testing"
"time"
"github.com/rs/zerolog"
"github.com/pelletier/go-toml/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"cosmossdk.io/log"
"cosmossdk.io/tools/cosmovisor/errors"
"cosmossdk.io/x/upgrade/plan"
)
@ -29,31 +25,48 @@ func TestArgsTestSuite(t *testing.T) {
// cosmovisorEnv are the string values of environment variables used to configure Cosmovisor.
type cosmovisorEnv struct {
Home string
Name string
DownloadBin string
RestartUpgrade string
RestartDelay string
SkipBackup string
DataBackupPath string
Interval string
PreupgradeMaxRetries string
DisableLogs string
Home string
Name string
DownloadBin string
DownloadMustHaveChecksum string
RestartUpgrade string
RestartDelay string
SkipBackup string
DataBackupPath string
Interval string
PreupgradeMaxRetries string
DisableLogs string
ColorLogs string
TimeFormatLogs string
CustomPreupgrade string
DisableRecase string
ShutdownGrace string
}
type envMap struct {
val string
allowEmpty bool
}
// ToMap creates a map of the cosmovisorEnv where the keys are the env var names.
func (c cosmovisorEnv) ToMap() map[string]string {
return map[string]string{
EnvHome: c.Home,
EnvName: c.Name,
EnvDownloadBin: c.DownloadBin,
EnvRestartUpgrade: c.RestartUpgrade,
EnvRestartDelay: c.RestartDelay,
EnvSkipBackup: c.SkipBackup,
EnvDataBackupPath: c.DataBackupPath,
EnvInterval: c.Interval,
EnvPreupgradeMaxRetries: c.PreupgradeMaxRetries,
EnvDisableLogs: c.DisableLogs,
func (c cosmovisorEnv) ToMap() map[string]envMap {
return map[string]envMap{
EnvHome: {val: c.Home, allowEmpty: false},
EnvName: {val: c.Name, allowEmpty: false},
EnvDownloadBin: {val: c.DownloadBin, allowEmpty: false},
EnvDownloadMustHaveChecksum: {val: c.DownloadMustHaveChecksum, allowEmpty: false},
EnvRestartUpgrade: {val: c.RestartUpgrade, allowEmpty: false},
EnvRestartDelay: {val: c.RestartDelay, allowEmpty: false},
EnvShutdownGrace: {val: c.ShutdownGrace, allowEmpty: false},
EnvSkipBackup: {val: c.SkipBackup, allowEmpty: false},
EnvDataBackupPath: {val: c.DataBackupPath, allowEmpty: false},
EnvInterval: {val: c.Interval, allowEmpty: false},
EnvPreupgradeMaxRetries: {val: c.PreupgradeMaxRetries, allowEmpty: false},
EnvDisableLogs: {val: c.DisableLogs, allowEmpty: false},
EnvColorLogs: {val: c.ColorLogs, allowEmpty: false},
EnvTimeFormatLogs: {val: c.TimeFormatLogs, allowEmpty: true},
EnvCustomPreupgrade: {val: c.CustomPreupgrade, allowEmpty: true},
EnvDisableRecase: {val: c.DisableRecase, allowEmpty: true},
}
}
@ -66,10 +79,14 @@ func (c *cosmovisorEnv) Set(envVar, envVal string) {
c.Name = envVal
case EnvDownloadBin:
c.DownloadBin = envVal
case EnvDownloadMustHaveChecksum:
c.DownloadMustHaveChecksum = envVal
case EnvRestartUpgrade:
c.RestartUpgrade = envVal
case EnvRestartDelay:
c.RestartDelay = envVal
case EnvShutdownGrace:
c.ShutdownGrace = envVal
case EnvSkipBackup:
c.SkipBackup = envVal
case EnvDataBackupPath:
@ -80,8 +97,16 @@ func (c *cosmovisorEnv) Set(envVar, envVal string) {
c.PreupgradeMaxRetries = envVal
case EnvDisableLogs:
c.DisableLogs = envVal
case EnvColorLogs:
c.ColorLogs = envVal
case EnvTimeFormatLogs:
c.TimeFormatLogs = envVal
case EnvCustomPreupgrade:
c.CustomPreupgrade = envVal
case EnvDisableRecase:
c.DisableRecase = envVal
default:
panic(fmt.Errorf("Unknown environment variable [%s]. Ccannot set field to [%s]. ", envVar, envVal))
panic(fmt.Errorf("Unknown environment variable [%s]. Cannot set field to [%s]. ", envVar, envVal))
}
}
@ -103,16 +128,16 @@ func (s *argsTestSuite) clearEnv() *cosmovisorEnv {
// setEnv sets environment variables to the values provided.
// If t is not nil, and there's a problem, the test will fail immediately.
// If t is nil, problems will just be logged using s.T().
func (s *argsTestSuite) setEnv(t *testing.T, env *cosmovisorEnv) {
func (s *argsTestSuite) setEnv(t *testing.T, env *cosmovisorEnv) { //nolint:thelper // false positive
if t == nil {
s.T().Logf("Restoring environment variables.")
}
for envVar, envVal := range env.ToMap() {
var err error
var msg string
if len(envVal) != 0 {
err = os.Setenv(envVar, envVal)
msg = fmt.Sprintf("setting %s to %s", envVar, envVal)
if len(envVal.val) != 0 || envVal.allowEmpty {
err = os.Setenv(envVar, envVal.val)
msg = fmt.Sprintf("setting %s to %s", envVar, envVal.val)
} else {
err = os.Unsetenv(envVar)
msg = fmt.Sprintf("unsetting %s", envVar)
@ -284,7 +309,7 @@ func (s *argsTestSuite) TestBooleanOption() {
name := "COSMOVISOR_TEST_VAL"
check := func(def, expected, isErr bool, msg string) {
v, err := booleanOption(name, def)
v, err := BooleanOption(name, def)
if isErr {
s.Require().Error(err)
return
@ -318,24 +343,77 @@ func (s *argsTestSuite) TestBooleanOption() {
check(false, true, false, "should handle true value case not sensitive")
}
func (s *argsTestSuite) TestTimeFormat() {
initialEnv := s.clearEnv()
defer s.setEnv(nil, initialEnv)
name := "COSMOVISOR_TEST_VAL"
check := func(def, expected string, isErr bool, msg string) {
v, err := TimeFormatOptionFromEnv(name, def)
if isErr {
s.Require().Error(err)
return
}
s.Require().NoError(err)
s.Require().Equal(expected, v, msg)
}
os.Unsetenv(name)
check(time.Kitchen, time.Kitchen, false, "should correctly set default value")
os.Setenv(name, "")
check(time.Kitchen, "", false, "should correctly set to a none")
os.Setenv(name, "wrong")
check(time.Kitchen, "", true, "should error on wrong value")
os.Setenv(name, "layout")
check(time.Kitchen, time.Layout, false, "should handle layout value")
os.Setenv(name, "ansic")
check(time.Kitchen, time.ANSIC, false, "should handle ansic value")
os.Setenv(name, "unixdate")
check(time.Kitchen, time.UnixDate, false, "should handle unixdate value")
os.Setenv(name, "rubydate")
check(time.Kitchen, time.RubyDate, false, "should handle rubydate value")
os.Setenv(name, "rfc822")
check(time.Kitchen, time.RFC822, false, "should handle rfc822 value")
os.Setenv(name, "rfc822z")
check(time.Kitchen, time.RFC822Z, false, "should handle rfc822z value")
os.Setenv(name, "rfc850")
check(time.Kitchen, time.RFC850, false, "should handle rfc850 value")
os.Setenv(name, "rfc1123")
check(time.Kitchen, time.RFC1123, false, "should handle rfc1123 value")
os.Setenv(name, "rfc1123z")
check(time.Kitchen, time.RFC1123Z, false, "should handle rfc1123z value")
os.Setenv(name, "rfc3339")
check(time.Kitchen, time.RFC3339, false, "should handle rfc3339 value")
os.Setenv(name, "rfc3339nano")
check(time.Kitchen, time.RFC3339Nano, false, "should handle rfc3339nano value")
os.Setenv(name, "kitchen")
check(time.Kitchen, time.Kitchen, false, "should handle kitchen value")
}
func (s *argsTestSuite) TestDetailString() {
home := "/home"
name := "test-name"
allowDownloadBinaries := true
downloadMustHaveChecksum := true
restartAfterUpgrade := true
pollInterval := 406 * time.Millisecond
unsafeSkipBackup := false
dataBackupPath := "/home"
preupgradeMaxRetries := 8
cfg := &Config{
Home: home,
Name: name,
AllowDownloadBinaries: allowDownloadBinaries,
RestartAfterUpgrade: restartAfterUpgrade,
PollInterval: pollInterval,
UnsafeSkipBackup: unsafeSkipBackup,
DataBackupPath: dataBackupPath,
PreupgradeMaxRetries: preupgradeMaxRetries,
Home: home,
Name: name,
AllowDownloadBinaries: allowDownloadBinaries,
DownloadMustHaveChecksum: downloadMustHaveChecksum,
RestartAfterUpgrade: restartAfterUpgrade,
PollInterval: pollInterval,
UnsafeSkipBackup: unsafeSkipBackup,
DataBackupPath: dataBackupPath,
PreUpgradeMaxRetries: preupgradeMaxRetries,
}
expectedPieces := []string{
@ -343,11 +421,15 @@ func (s *argsTestSuite) TestDetailString() {
fmt.Sprintf("%s: %s", EnvHome, home),
fmt.Sprintf("%s: %s", EnvName, name),
fmt.Sprintf("%s: %t", EnvDownloadBin, allowDownloadBinaries),
fmt.Sprintf("%s: %t", EnvDownloadMustHaveChecksum, downloadMustHaveChecksum),
fmt.Sprintf("%s: %t", EnvRestartUpgrade, restartAfterUpgrade),
fmt.Sprintf("%s: %s", EnvInterval, pollInterval),
fmt.Sprintf("%s: %t", EnvSkipBackup, unsafeSkipBackup),
fmt.Sprintf("%s: %s", EnvDataBackupPath, home),
fmt.Sprintf("%s: %d", EnvPreupgradeMaxRetries, preupgradeMaxRetries),
fmt.Sprintf("%s: %t", EnvDisableLogs, cfg.DisableLogs),
fmt.Sprintf("%s: %t", EnvColorLogs, cfg.ColorLogs),
fmt.Sprintf("%s: %s", EnvTimeFormatLogs, cfg.TimeFormatLogs),
"Derived Values:",
fmt.Sprintf("Root Dir: %s", home),
fmt.Sprintf("Upgrade Dir: %s", home),
@ -363,6 +445,43 @@ func (s *argsTestSuite) TestDetailString() {
}
}
var newConfig = func(
home, name string,
downloadBin bool,
downloadMustHaveChecksum bool,
restartUpgrade bool,
restartDelay int,
skipBackup bool,
dataBackupPath string,
interval, preupgradeMaxRetries int,
grpcAddress string,
disableLogs, colorLogs bool,
timeFormatLogs string,
customPreUpgrade string,
disableRecase bool,
shutdownGrace int,
) *Config {
return &Config{
Home: home,
Name: name,
AllowDownloadBinaries: downloadBin,
DownloadMustHaveChecksum: downloadMustHaveChecksum,
RestartAfterUpgrade: restartUpgrade,
RestartDelay: time.Millisecond * time.Duration(restartDelay),
PollInterval: time.Millisecond * time.Duration(interval),
UnsafeSkipBackup: skipBackup,
DataBackupPath: dataBackupPath,
GRPCAddress: grpcAddress,
PreUpgradeMaxRetries: preupgradeMaxRetries,
DisableLogs: disableLogs,
ColorLogs: colorLogs,
TimeFormatLogs: timeFormatLogs,
CustomPreUpgrade: customPreUpgrade,
DisableRecase: disableRecase,
ShutdownGrace: time.Duration(shutdownGrace),
}
}
func (s *argsTestSuite) TestGetConfigFromEnv() {
initialEnv := s.clearEnv()
defer s.setEnv(nil, initialEnv)
@ -371,21 +490,6 @@ func (s *argsTestSuite) TestGetConfigFromEnv() {
absPath, perr := filepath.Abs(relPath)
s.Require().NoError(perr)
newConfig := func(home, name string, downloadBin, restartUpgrade bool, restartDelay int, skipBackup bool, dataBackupPath string, interval, preupgradeMaxRetries int, disableLogs bool) *Config {
return &Config{
Home: home,
Name: name,
AllowDownloadBinaries: downloadBin,
RestartAfterUpgrade: restartUpgrade,
RestartDelay: time.Millisecond * time.Duration(restartDelay),
PollInterval: time.Millisecond * time.Duration(interval),
UnsafeSkipBackup: skipBackup,
DataBackupPath: dataBackupPath,
PreupgradeMaxRetries: preupgradeMaxRetries,
DisableLogs: disableLogs,
}
}
tests := []struct {
name string
envVals cosmovisorEnv
@ -395,210 +499,269 @@ func (s *argsTestSuite) TestGetConfigFromEnv() {
{
name: "all bad",
envVals: cosmovisorEnv{
Home: "",
Name: "",
DownloadBin: "bad",
RestartUpgrade: "bad",
RestartDelay: "bad",
SkipBackup: "bad",
DataBackupPath: "bad",
Interval: "bad",
PreupgradeMaxRetries: "bad",
Home: "",
Name: "",
DownloadBin: "bad",
DownloadMustHaveChecksum: "bad",
RestartUpgrade: "bad",
RestartDelay: "bad",
SkipBackup: "bad",
DataBackupPath: "bad",
Interval: "bad",
PreupgradeMaxRetries: "bad",
TimeFormatLogs: "bad",
CustomPreupgrade: "",
DisableRecase: "bad",
ShutdownGrace: "bad",
},
expectedCfg: nil,
expectedErrCount: 9,
expectedErrCount: 13,
},
{
name: "all good",
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "true", "10s"},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", true, 10000000000),
expectedErrCount: 0,
},
{
name: "nothing set",
envVals: cosmovisorEnv{"", "", "", "", "", "", "", "", "", "false"},
envVals: cosmovisorEnv{"", "", "", "", "", "", "", "", "", "", "false", "false", "", "", "", ""},
expectedCfg: nil,
expectedErrCount: 3,
},
// Note: Home and Name tests are done in TestValidate
// timeformat tests are done in the TestTimeFormat
{
name: "download bin bad",
envVals: cosmovisorEnv{absPath, "testname", "bad", "false", "600ms", "true", "", "303ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "bad", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "download bin not set",
envVals: cosmovisorEnv{absPath, "testname", "", "false", "600ms", "true", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", false, false, 600, true, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "", false, 0),
expectedErrCount: 0,
},
{
name: "download bin true",
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "download bin false",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "true", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", false, false, 600, true, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "download ensure checksum true",
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", true, false, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "restart upgrade bad",
envVals: cosmovisorEnv{absPath, "testname", "true", "bad", "600ms", "true", "", "303ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "bad", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "restart upgrade not set",
envVals: cosmovisorEnv{absPath, "testname", "true", "", "600ms", "true", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", true, true, 600, true, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "restart upgrade true",
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "600ms", "true", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", true, true, 600, true, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "true", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", true, true, true, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "restart upgrade true",
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "skip unsafe backups bad",
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "bad", "", "303ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "bad", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "skip unsafe backups not set",
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", true, false, 600, false, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "skip unsafe backups true",
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "true", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", true, false, 600, true, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "true", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, true, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "skip unsafe backups false",
envVals: cosmovisorEnv{absPath, "testname", "true", "false", "600ms", "false", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", true, false, 600, false, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "true", "true", "false", "600ms", "false", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", true, true, false, 600, false, absPath, 303, 1, "localhost:9090", false, true, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "poll interval bad",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "bad", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "bad", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "poll interval 0",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "0", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "0", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "poll interval not set",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "", "1", "false"},
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 300, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 300, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "poll interval 600",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "600", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "600", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "poll interval 1s",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "1s", "1", "false"},
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 1000, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "1s", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 1000, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "poll interval -3m",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "-3m", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "-3m", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "restart delay bad",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "bad", "false", "", "303ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "bad", "false", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "restart delay 0",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "0", "false", "", "303ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "0", "false", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "restart delay not set",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "", "false", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", false, false, 0, false, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "", "false", "", "303ms", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 0, false, absPath, 303, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "restart delay 600",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600", "false", "", "300ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600", "false", "", "300ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "restart delay 1s",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "1s", "false", "", "303ms", "1", "false"},
expectedCfg: newConfig(absPath, "testname", false, false, 1000, false, absPath, 303, 1, false),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "1s", "false", "", "303ms", "1", "false", "false", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 1000, false, absPath, 303, 1, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "restart delay -3m",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "-3m", "false", "", "303ms", "1", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "-3m", "false", "", "303ms", "1", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "prepupgrade max retries bad",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "bad", "false"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "bad", "false", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "prepupgrade max retries 0",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "0", "false"},
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 0, false),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "0", "false", "false", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "prepupgrade max retries not set",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "", "false"},
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 0, false),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "false", "false", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "prepupgrade max retries 5",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "5", "false"},
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 5, false),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "false", "false", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 5, "localhost:9090", false, false, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "disable logs bad",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "5", "bad"},
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "bad", "true", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "disable logs good",
envVals: cosmovisorEnv{absPath, "testname", "false", "false", "600ms", "false", "", "406ms", "", "true"},
expectedCfg: newConfig(absPath, "testname", false, false, 600, false, absPath, 406, 0, true),
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, false, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "disable logs color bad",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "5", "true", "bad", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "disable logs color good",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "kitchen", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, false, time.Kitchen, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "disable logs timestamp",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "false", "", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, false, "", "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "enable rf3339 logs timestamp",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, true, time.RFC3339, "preupgrade.sh", false, 0),
expectedErrCount: 0,
},
{
name: "invalid logs timestamp format",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "invalid", "preupgrade.sh", "", ""},
expectedCfg: nil,
expectedErrCount: 1,
},
{
name: "disable recase good",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "true", ""},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, true, time.RFC3339, "preupgrade.sh", true, 0),
expectedErrCount: 0,
},
{
name: "disable recase bad",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "bad", ""},
expectedErrCount: 1,
},
{
name: "shutdown grace good",
envVals: cosmovisorEnv{absPath, "testname", "false", "true", "false", "600ms", "false", "", "406ms", "", "true", "true", "rfc3339", "preupgrade.sh", "true", "15s"},
expectedCfg: newConfig(absPath, "testname", false, true, false, 600, false, absPath, 406, 0, "localhost:9090", true, true, time.RFC3339, "preupgrade.sh", true, 15000000000),
expectedErrCount: 0,
},
}
@ -606,13 +769,13 @@ func (s *argsTestSuite) TestGetConfigFromEnv() {
for _, tc := range tests {
s.T().Run(tc.name, func(t *testing.T) {
s.setEnv(t, &tc.envVals)
cfg, err := GetConfigFromEnv()
cfg, err := GetConfigFromEnv(false)
if tc.expectedErrCount == 0 {
assert.NoError(t, err)
} else if assert.Error(t, err) {
errCount := 1
if multi, isMulti := err.(*errors.MultiError); isMulti {
errCount = multi.Len()
if errMulti, ok := err.(interface{ Unwrap() []error }); ok {
errCount = len(errMulti.Unwrap())
}
assert.Equal(t, tc.expectedErrCount, errCount, "error count")
}
@ -621,87 +784,91 @@ func (s *argsTestSuite) TestGetConfigFromEnv() {
}
}
func (s *argsTestSuite) TestLogConfigOrError() {
cfg := &Config{
Home: "/no/place/like/it",
Name: "cosmotestvisor",
AllowDownloadBinaries: true,
RestartAfterUpgrade: true,
PollInterval: 999,
UnsafeSkipBackup: false,
DataBackupPath: "/no/place/like/it",
PreupgradeMaxRetries: 20,
}
errNormal := fmt.Errorf("this is a single error")
errs := []error{
fmt.Errorf("multi-error error 1"),
fmt.Errorf("multi-error error 2"),
fmt.Errorf("multi-error error 3"),
}
errMulti := errors.FlattenErrors(errs...)
func (s *argsTestSuite) setUpDir() string {
s.T().Helper()
makeTestLogger := func(testName string, out io.Writer) log.Logger {
output := zerolog.ConsoleWriter{Out: out, TimeFormat: time.Kitchen, NoColor: true}
logger := zerolog.New(output).With().Str("test", testName).Timestamp().Logger()
return log.NewCustomLogger(logger)
}
home := s.T().TempDir()
err := os.MkdirAll(filepath.Join(home, rootName), 0o755)
s.Require().NoError(err)
return home
}
tests := []struct {
name string
cfg *Config
err error
contains []string
notcontains []string
func (s *argsTestSuite) setupConfig(home string) string {
s.T().Helper()
cfg := newConfig(home, "test", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, "kitchen", "", true, 10000000000)
path := filepath.Join(home, rootName, "config.toml")
f, err := os.Create(path)
s.Require().NoError(err)
enc := toml.NewEncoder(f)
s.Require().NoError(enc.Encode(&cfg))
err = f.Close()
s.Require().NoError(err)
return path
}
func (s *argsTestSuite) TestConfigFromFile() {
home := s.setUpDir()
// create a config file
cfgFilePath := s.setupConfig(home)
testCases := []struct {
name string
config *Config
expectedCfg func() *Config
filePath string
expectedError string
malleate func()
}{
{
name: "normal error",
cfg: nil,
err: errNormal,
contains: []string{"configuration error", errNormal.Error()}, // TODO: Fix this.
notcontains: nil,
name: "valid config",
expectedCfg: func() *Config {
return newConfig(home, "test", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, time.Kitchen, "", true, 10000000000)
},
filePath: cfgFilePath,
expectedError: "",
malleate: func() {},
},
{
name: "multi error",
cfg: nil,
err: errMulti,
contains: []string{"configuration errors found", errs[0].Error(), errs[1].Error(), errs[2].Error()},
notcontains: nil,
name: "env variable will override config file fields",
filePath: cfgFilePath,
expectedError: "",
malleate: func() {
// set env variable different from the config file
os.Setenv(EnvName, "env-name")
},
expectedCfg: func() *Config {
return newConfig(home, "env-name", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, time.Kitchen, "", true, 10000000000)
},
},
{
name: "config",
cfg: cfg,
err: nil,
contains: []string{"Configurable Values", cfg.DetailString()},
notcontains: nil,
},
{
name: "error and config - no config details",
cfg: cfg,
err: errNormal,
contains: []string{"error"},
notcontains: []string{"Configuration is valid", EnvName, cfg.Home}, // Just some spot checks.
},
{
name: "nil nil - no output",
cfg: nil,
err: nil,
contains: nil,
notcontains: []string{" "},
name: "empty config file path will load config from ENV variables",
expectedCfg: func() *Config {
return newConfig(home, "test", true, true, true, 406, false, home, 8, 0, "localhost:9090", false, true, time.Kitchen, "", true, 10000000000)
},
filePath: "",
expectedError: "",
malleate: func() {
s.setEnv(s.T(), &cosmovisorEnv{home, "test", "true", "true", "true", "406ms", "false", home, "8ms", "0", "false", "true", "kitchen", "", "true", "10s"})
},
},
}
for _, tc := range tests {
for _, tc := range testCases {
s.T().Run(tc.name, func(t *testing.T) {
var b bytes.Buffer
logger := makeTestLogger(tc.name, &b)
LogConfigOrError(logger, tc.cfg, tc.err)
output := b.String()
for _, expected := range tc.contains {
assert.Contains(t, output, expected)
}
for _, unexpected := range tc.notcontains {
assert.NotContains(t, output, unexpected)
tc.malleate()
actualCfg, err := GetConfigFromFile(tc.filePath)
if tc.expectedError != "" {
s.Require().NoError(err)
s.Require().Contains(err.Error(), tc.expectedError)
return
}
s.Require().NoError(err)
s.Require().EqualValues(tc.expectedCfg(), actualCfg)
})
}
}
@ -714,7 +881,7 @@ func BenchmarkDetailString(b *testing.B) {
AllowDownloadBinaries: true,
UnsafeSkipBackup: true,
PollInterval: 450 * time.Second,
PreupgradeMaxRetries: 1e7,
PreUpgradeMaxRetries: 1e7,
}
b.ReportAllocs()

View File

@ -1,49 +1,41 @@
package main
import (
"encoding/json"
"fmt"
"os"
"path"
"strings"
"github.com/rs/zerolog"
"github.com/spf13/cobra"
"cosmossdk.io/log"
"cosmossdk.io/tools/cosmovisor"
upgradetypes "cosmossdk.io/x/upgrade/types"
)
func NewAddUpgradeCmd() *cobra.Command {
addUpgrade := &cobra.Command{
Use: "add-upgrade [upgrade-name] [path to executable]",
Short: "Manually add upgrade binary to Cosmovisor",
Use: "add-upgrade <upgrade-name> <path to executable>",
Short: "Add APP upgrade binary to cosmovisor",
SilenceUsage: true,
Args: cobra.ExactArgs(2),
RunE: AddUpgrade,
RunE: addUpgradeCmd,
}
addUpgrade.Flags().Bool(cosmovisor.FlagForce, false, "overwrite existing upgrade binary")
addUpgrade.Flags().Bool(cosmovisor.FlagForce, false, "overwrite existing upgrade binary / upgrade-info.json file")
addUpgrade.Flags().Int64(cosmovisor.FlagUpgradeHeight, 0, "define a height at which to upgrade the binary automatically (without governance proposal)")
return addUpgrade
}
// AddUpgrade adds upgrade info to manifest
func AddUpgrade(cmd *cobra.Command, args []string) error {
cfg, err := cosmovisor.GetConfigFromEnv()
if err != nil {
return err
// addUpgrade adds upgrade info to manifest
func addUpgrade(cfg *cosmovisor.Config, force bool, upgradeHeight int64, upgradeName, executablePath, upgradeInfoPath string) error {
logger := cfg.Logger(os.Stdout)
if !cfg.DisableRecase {
upgradeName = strings.ToLower(upgradeName)
}
logger := cmd.Context().Value(log.ContextKey).(log.Logger)
if cfg.DisableLogs {
logger = log.NewCustomLogger(zerolog.Nop())
}
upgradeName := args[0]
if len(upgradeName) == 0 {
return fmt.Errorf("upgrade name cannot be empty")
}
executablePath := args[1]
if _, err := os.Stat(executablePath); err != nil {
if os.IsNotExist(err) {
return fmt.Errorf("invalid executable path: %w", err)
@ -54,7 +46,7 @@ func AddUpgrade(cmd *cobra.Command, args []string) error {
// create upgrade dir
upgradeLocation := cfg.UpgradeDir(upgradeName)
if err := os.MkdirAll(path.Join(upgradeLocation, "bin"), 0o750); err != nil {
if err := os.MkdirAll(path.Join(upgradeLocation, "bin"), 0o755); err != nil {
return fmt.Errorf("failed to create upgrade directory: %w", err)
}
@ -64,22 +56,85 @@ func AddUpgrade(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to read binary: %w", err)
}
if _, err := os.Stat(cfg.UpgradeBin(upgradeName)); err == nil {
if force, _ := cmd.Flags().GetBool(cosmovisor.FlagForce); !force {
return fmt.Errorf("upgrade binary already exists at %s", cfg.UpgradeBin(upgradeName))
}
logger.Info(fmt.Sprintf("Overwriting %s for %s upgrade", executablePath, upgradeName))
} else if !os.IsNotExist(err) {
return fmt.Errorf("failed to check if upgrade binary exists: %w", err)
}
if err := os.WriteFile(cfg.UpgradeBin(upgradeName), executableData, 0o600); err != nil {
return fmt.Errorf("failed to write binary to location: %w", err)
if err := saveOrAbort(cfg.UpgradeBin(upgradeName), executableData, force); err != nil {
return err
}
logger.Info(fmt.Sprintf("Using %s for %s upgrade", executablePath, upgradeName))
logger.Info(fmt.Sprintf("Upgrade binary located at %s", cfg.UpgradeBin(upgradeName)))
if upgradeHeight > 0 {
plan := upgradetypes.Plan{Name: upgradeName, Height: upgradeHeight}
if err := plan.ValidateBasic(); err != nil {
panic(fmt.Errorf("something is wrong with cosmovisor: %w", err))
}
// create upgrade-info.json file
planData, err := json.Marshal(plan)
if err != nil {
return fmt.Errorf("failed to marshal upgrade plan: %w", err)
}
if err := saveOrAbort(upgradeInfoPath, planData, force); err != nil {
return err
}
logger.Info(fmt.Sprintf("%s created, %s upgrade binary will switch at height %d", upgradeInfoPath, upgradeName, upgradeHeight))
}
return nil
}
// GetConfig returns a Config using passed-in flag
func getConfigFromCmd(cmd *cobra.Command) (*cosmovisor.Config, error) {
configPath, err := cmd.Flags().GetString(cosmovisor.FlagCosmovisorConfig)
if err != nil {
return nil, fmt.Errorf("failed to get config flag: %w", err)
}
cfg, err := cosmovisor.GetConfigFromFile(configPath)
if err != nil {
return nil, err
}
return cfg, nil
}
// addUpgradeCmd parses input flags and adds upgrade info to manifest
func addUpgradeCmd(cmd *cobra.Command, args []string) error {
cfg, err := getConfigFromCmd(cmd)
if err != nil {
return err
}
upgradeName, executablePath := args[0], args[1]
force, err := cmd.Flags().GetBool(cosmovisor.FlagForce)
if err != nil {
return fmt.Errorf("failed to get force flag: %w", err)
}
upgradeHeight, err := cmd.Flags().GetInt64(cosmovisor.FlagUpgradeHeight)
if err != nil {
return fmt.Errorf("failed to get upgrade-height flag: %w", err)
}
return addUpgrade(cfg, force, upgradeHeight, upgradeName, executablePath, cfg.UpgradeInfoFilePath())
}
// saveOrAbort saves data to path or aborts if file exists and force is false
func saveOrAbort(path string, data []byte, force bool) error {
if _, err := os.Stat(path); err == nil {
if !force {
return fmt.Errorf("file already exists at %s", path)
}
} else if !os.IsNotExist(err) {
return fmt.Errorf("failed to check if file exists: %w", err)
}
//nolint:gosec // We need broader permissions to make it executable
if err := os.WriteFile(path, data, 0o755); err != nil {
return fmt.Errorf("failed to write binary to location: %w", err)
}
return nil
}

View File

@ -0,0 +1,143 @@
package main
import (
"encoding/csv"
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/spf13/cobra"
"cosmossdk.io/tools/cosmovisor"
)
func NewBatchAddUpgradeCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "add-batch-upgrade [flags]",
Short: "Add multiple upgrade binaries at specified heights to cosmovisor",
Long: `This command allows you to specify multiple upgrades at once at specific heights, copying or creating a batch upgrade file that's actively watched during 'cosmovisor run'.
You can provide upgrades in two ways:
1. Using --upgrade-file: Specify a path to a headerless CSV batch upgrade file in the format:
upgrade-name,path-to-exec,upgrade-height
2. Using --upgrade-list: Provide a comma-separated list of upgrades.
Each upgrade is defined by three colon-separated values:
a. upgrade-name: A unique identifier for the upgrade
b. path-to-exec: The file path to the upgrade's executable binary
c. upgrade-height: The block height at which the upgrade should occur
This creates a batch upgrade JSON file with the upgrade-info objects in the upgrade directory.
Note: You must provide either --upgrade-file or --upgrade-list.`,
Example: `cosmovisor add-batch-upgrade --upgrade-list upgrade_v2:/path/to/v2/binary:1000000,upgrade_v3:/path/to/v3/binary:2000000
cosmovisor add-batch-upgrade --upgrade-file /path/to/batch_upgrade.csv`,
SilenceUsage: true,
Args: cobra.NoArgs,
RunE: addBatchUpgrade,
}
cmd.Flags().String("upgrade-file", "", "Path to a batch upgrade file which is a JSON array of upgrade-info objects")
cmd.Flags().StringSlice("upgrade-list", []string{}, "List of comma-separated upgrades in the format 'name:path/to/binary:height'")
cmd.MarkFlagsMutuallyExclusive("upgrade-file", "upgrade-list")
return cmd
}
// addBatchUpgrade takes in multiple specified upgrades and creates a single
// batch upgrade file out of them
func addBatchUpgrade(cmd *cobra.Command, args []string) error {
cfg, err := getConfigFromCmd(cmd)
if err != nil {
return err
}
upgradeFile, err := cmd.Flags().GetString("upgrade-file")
if err == nil && upgradeFile != "" {
return processUpgradeFile(cfg, upgradeFile)
}
upgradeList, err := cmd.Flags().GetStringSlice("upgrade-list")
if err != nil || len(upgradeList) == 0 {
return fmt.Errorf("either --upgrade-file or --upgrade-list must be provided")
}
var splitUpgrades [][]string
for _, upgrade := range upgradeList {
splitUpgrades = append(splitUpgrades, strings.Split(upgrade, ":"))
}
return processUpgradeList(cfg, splitUpgrades)
}
// processUpgradeList takes in a list of upgrades and creates a batch upgrade file
func processUpgradeList(cfg *cosmovisor.Config, upgradeList [][]string) error {
upgradeInfoPaths := []string{}
for i, upgrade := range upgradeList {
if len(upgrade) != 3 {
return fmt.Errorf("argument at position %d (%s) is invalid", i, upgrade)
}
upgradeName := filepath.Base(upgrade[0])
upgradePath := upgrade[1]
upgradeHeight, err := strconv.ParseInt(upgrade[2], 10, 64)
if err != nil {
return fmt.Errorf("upgrade height at position %d (%s) is invalid", i, upgrade[2])
}
upgradeInfoPath := cfg.UpgradeInfoFilePath() + "." + upgradeName
upgradeInfoPaths = append(upgradeInfoPaths, upgradeInfoPath)
if err := addUpgrade(cfg, true, upgradeHeight, upgradeName, upgradePath, upgradeInfoPath); err != nil {
return err
}
}
var allData []json.RawMessage
for _, uip := range upgradeInfoPaths {
fileData, err := os.ReadFile(uip)
if err != nil {
return fmt.Errorf("error reading file %s: %w", uip, err)
}
// Verify it's valid JSON
var jsonData json.RawMessage
if err := json.Unmarshal(fileData, &jsonData); err != nil {
return fmt.Errorf("error parsing JSON from file %s: %w", uip, err)
}
// Add to our slice
allData = append(allData, jsonData)
}
// Marshal the combined data
batchData, err := json.MarshalIndent(allData, "", " ")
if err != nil {
return fmt.Errorf("error marshaling combined JSON: %w", err)
}
// Write to output file
err = os.WriteFile(cfg.UpgradeInfoBatchFilePath(), batchData, 0o600)
if err != nil {
return fmt.Errorf("error writing combined JSON to file: %w", err)
}
return nil
}
// processUpgradeFile takes in a CSV batch upgrade file, parses it and calls processUpgradeList
func processUpgradeFile(cfg *cosmovisor.Config, upgradeFile string) error {
file, err := os.Open(upgradeFile)
if err != nil {
return fmt.Errorf("error opening upgrade CSV file %s: %w", upgradeFile, err)
}
defer file.Close()
r := csv.NewReader(file)
r.FieldsPerRecord = 3
r.TrimLeadingSpace = true
records, err := r.ReadAll()
if err != nil {
return fmt.Errorf("error parsing upgrade CSV file %s: %w", upgradeFile, err)
}
if err := processUpgradeList(cfg, records); err != nil {
return err
}
return nil
}

View File

@ -7,11 +7,13 @@ import (
)
var configCmd = &cobra.Command{
Use: "config",
Short: "Display cosmovisor config (prints environment variables used by cosmovisor).",
Use: "config",
Short: "Display cosmovisor config.",
Long: `Display cosmovisor config. If a config file is provided, it will display the config from the file,
otherwise it will display the config from the environment variables.`,
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
cfg, err := cosmovisor.GetConfigFromEnv()
cfg, err := cosmovisor.GetConfigFromFile(cmd.Flag(cosmovisor.FlagCosmovisorConfig).Value.String())
if err != nil {
return err
}

View File

@ -18,7 +18,7 @@ the proposal. Cosmovisor interprets that data to perform an update: switch a cur
and restart the App.
Configuration of Cosmovisor is done through environment variables, which are
documented in: https://docs.cosmos.network/main/tooling/cosmovisor`,
documented in: https://docs.cosmos.network/main/build/tooling/cosmovisor`,
cosmovisor.EnvName, cosmovisor.EnvHome,
)
}

View File

@ -12,7 +12,7 @@ func TestGetHelpText(t *testing.T) {
expectedPieces := []string{
"Cosmovisor",
cosmovisor.EnvName, cosmovisor.EnvHome,
"https://docs.cosmos.network/main/tooling/cosmovisor",
"https://docs.cosmos.network/main/build/tooling/cosmovisor",
}
actual := GetHelpText()

View File

@ -11,18 +11,23 @@ import (
"cosmossdk.io/log"
"cosmossdk.io/tools/cosmovisor"
cverrors "cosmossdk.io/tools/cosmovisor/errors"
"cosmossdk.io/x/upgrade/plan"
)
var initCmd = &cobra.Command{
Use: "init <path to executable>",
Short: "Initialize a cosmovisor daemon home directory.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
logger := cmd.Context().Value(log.ContextKey).(log.Logger)
return InitializeCosmovisor(logger, args)
},
func NewInitCmd() *cobra.Command {
initCmd := &cobra.Command{
Use: "init <path to executable>",
Short: "Initialize a cosmovisor daemon home directory.",
Long: `Initialize a cosmovisor daemon home directory with the provided executable.
Configuration file is initialized at the default path (<-home->/cosmovisor/config.toml).`,
Args: cobra.ExactArgs(1),
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
return InitializeCosmovisor(nil, args)
},
}
return initCmd
}
// InitializeCosmovisor initializes the cosmovisor directories, current link, and initial executable.
@ -39,11 +44,22 @@ func InitializeCosmovisor(logger log.Logger, args []string) error {
case exeInfo.IsDir():
return errors.New("invalid path to executable: must not be a directory")
}
cfg, err := getConfigForInitCmd()
// skipping validation to not check if directories exist
cfg, err := cosmovisor.GetConfigFromEnv(true)
if err != nil {
return err
}
// process to minimal validation
if err := minConfigValidate(cfg); err != nil {
return err
}
if logger == nil {
logger = cfg.Logger(os.Stdout)
}
logger.Info("checking on the genesis/bin directory")
genBinExe := cfg.GenesisBin()
genBinDir, _ := filepath.Split(genBinExe)
@ -77,6 +93,12 @@ func InitializeCosmovisor(logger log.Logger, args []string) error {
return err
}
// set current working directory to $DAEMON_NAME/cosmosvisor
// to allow current symlink to be relative
if err = os.Chdir(cfg.Root()); err != nil {
return fmt.Errorf("failed to change directory to %s: %w", cfg.Root(), err)
}
logger.Info("checking on the current symlink and creating it if needed")
cur, curErr := cfg.CurrentBin()
if curErr != nil {
@ -84,31 +106,29 @@ func InitializeCosmovisor(logger log.Logger, args []string) error {
}
logger.Info(fmt.Sprintf("the current symlink points to: %q", cur))
filePath, err := cfg.Export()
if err != nil {
return fmt.Errorf("failed to export configuration: %w", err)
}
logger.Info(fmt.Sprintf("cosmovisor config.toml created at: %s", filePath))
return nil
}
// getConfigForInitCmd gets just the configuration elements needed to initialize cosmovisor.
func getConfigForInitCmd() (*cosmovisor.Config, error) {
func minConfigValidate(cfg *cosmovisor.Config) error {
var errs []error
// Note: Not using GetConfigFromEnv here because that checks that the directories already exist.
// We also don't care about the rest of the configuration stuff in here.
cfg := &cosmovisor.Config{
Home: os.Getenv(cosmovisor.EnvHome),
Name: os.Getenv(cosmovisor.EnvName),
}
if len(cfg.Name) == 0 {
errs = append(errs, fmt.Errorf("%s is not set", cosmovisor.EnvName))
}
switch {
case len(cfg.Home) == 0:
errs = append(errs, fmt.Errorf("%s is not set", cosmovisor.EnvHome))
case !filepath.IsAbs(cfg.Home):
errs = append(errs, fmt.Errorf("%s must be an absolute path", cosmovisor.EnvHome))
}
if len(errs) > 0 {
return nil, cverrors.FlattenErrors(errs...)
}
return cfg, nil
return errors.Join(errs...)
}
// copyFile copies the file at the given source to the given destination.

View File

@ -9,7 +9,8 @@ import (
"testing"
"time"
"github.com/rs/zerolog"
"github.com/pelletier/go-toml/v2"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
@ -18,6 +19,13 @@ import (
"cosmossdk.io/tools/cosmovisor"
)
const (
notset = " is not set"
cosmovisorDirName = "cosmovisor"
cfgFileWithExt = "config.toml"
)
type InitTestSuite struct {
suite.Suite
}
@ -28,15 +36,24 @@ func TestInitTestSuite(t *testing.T) {
// cosmovisorInitEnv are some string values of environment variables used to configure Cosmovisor, and used by the init command.
type cosmovisorInitEnv struct {
Home string
Name string
Home string
Name string
ColorLogs string
TimeFormatLogs string
}
type envMap struct {
val string
allowEmpty bool
}
// ToMap creates a map of the cosmovisorInitEnv where the keys are the env var names.
func (c cosmovisorInitEnv) ToMap() map[string]string {
return map[string]string{
cosmovisor.EnvHome: c.Home,
cosmovisor.EnvName: c.Name,
func (c cosmovisorInitEnv) ToMap() map[string]envMap {
return map[string]envMap{
cosmovisor.EnvHome: {val: c.Home, allowEmpty: false},
cosmovisor.EnvName: {val: c.Name, allowEmpty: false},
cosmovisor.EnvColorLogs: {val: c.ColorLogs, allowEmpty: false},
cosmovisor.EnvTimeFormatLogs: {val: c.TimeFormatLogs, allowEmpty: true},
}
}
@ -47,6 +64,10 @@ func (c *cosmovisorInitEnv) Set(envVar, envVal string) {
c.Home = envVal
case cosmovisor.EnvName:
c.Name = envVal
case cosmovisor.EnvColorLogs:
c.Name = envVal
case cosmovisor.EnvTimeFormatLogs:
c.Name = envVal
default:
panic(fmt.Errorf("Unknown environment variable [%s]. Cannot set field to [%s]. ", envVar, envVal))
}
@ -63,6 +84,7 @@ func (s *InitTestSuite) clearEnv() *cosmovisorInitEnv {
for envVar := range rv.ToMap() {
rv.Set(envVar, os.Getenv(envVar))
s.Require().NoError(os.Unsetenv(envVar))
viper.Reset()
}
return &rv
}
@ -70,16 +92,16 @@ func (s *InitTestSuite) clearEnv() *cosmovisorInitEnv {
// setEnv sets environment variables to the values provided.
// If t is not nil, and there's a problem, the test will fail immediately.
// If t is nil, problems will just be logged using s.T().
func (s *InitTestSuite) setEnv(t *testing.T, env *cosmovisorInitEnv) {
func (s *InitTestSuite) setEnv(t *testing.T, env *cosmovisorInitEnv) { //nolint:thelper // false psotive
if t == nil {
s.T().Logf("Restoring environment variables.")
}
for envVar, envVal := range env.ToMap() {
var err error
var msg string
if len(envVal) != 0 {
err = os.Setenv(envVar, envVal)
msg = fmt.Sprintf("setting %s to %s", envVar, envVal)
if len(envVal.val) != 0 || envVal.allowEmpty {
err = os.Setenv(envVar, envVal.val)
msg = fmt.Sprintf("setting %s to %s", envVar, envVal.val)
} else {
err = os.Unsetenv(envVar)
msg = fmt.Sprintf("unsetting %s", envVar)
@ -95,9 +117,29 @@ func (s *InitTestSuite) setEnv(t *testing.T, env *cosmovisorInitEnv) {
}
}
// readStdInpFromFile reads the provided data as if it were a standard input.
func (s *InitTestSuite) readStdInpFromFile(data []byte) {
// Create a temporary file and write the test input into it
tmpfile, err := os.CreateTemp("", "test")
if err != nil {
s.T().Fatal(err)
}
// write the test input into the temporary file
if _, err := tmpfile.Write(data); err != nil {
s.T().Fatal(err)
}
if _, err := tmpfile.Seek(0, 0); err != nil {
s.T().Fatal(err)
}
os.Stdin = tmpfile
}
var (
_ io.Reader = BufferedPipe{}
_ io.Writer = BufferedPipe{}
_ io.Reader = &BufferedPipe{}
_ io.Writer = &BufferedPipe{}
)
// BufferedPipe contains a connected read/write pair of files (a pipe),
@ -125,8 +167,8 @@ type BufferedPipe struct {
// NewBufferedPipe creates a new BufferedPipe with the given name.
// Files must be closed once you are done with them (e.g. with .Close()).
// Once ready, buffering must be started using .Start(). See also StartNewBufferedPipe.
func NewBufferedPipe(name string, replicateTo ...io.Writer) (BufferedPipe, error) {
p := BufferedPipe{Name: name}
func NewBufferedPipe(name string, replicateTo ...io.Writer) (*BufferedPipe, error) {
p := &BufferedPipe{Name: name}
p.Reader, p.Writer, p.Error = os.Pipe()
if p.Error != nil {
return p, p.Error
@ -142,7 +184,7 @@ func NewBufferedPipe(name string, replicateTo ...io.Writer) (BufferedPipe, error
//
// p, _ := NewBufferedPipe(name, replicateTo...)
// p.Start()
func StartNewBufferedPipe(name string, replicateTo ...io.Writer) (BufferedPipe, error) {
func StartNewBufferedPipe(name string, replicateTo ...io.Writer) (*BufferedPipe, error) {
p, err := NewBufferedPipe(name, replicateTo...)
if err != nil {
return p, err
@ -172,6 +214,7 @@ func (p *BufferedPipe) Start() {
if _, p.Error = io.Copy(&b, p.BufferReader); p.Error != nil {
b.WriteString("buffer error: " + p.Error.Error())
}
p.buffer <- b.Bytes()
}()
p.started = true
@ -196,6 +239,7 @@ func (p *BufferedPipe) Collect() []byte {
panic("buffered pipe " + p.Name + " has not been started: cannot collect")
}
_ = p.Writer.Close()
if p.buffer == nil {
return []byte{}
}
@ -205,12 +249,12 @@ func (p *BufferedPipe) Collect() []byte {
}
// Read implements the io.Reader interface on this BufferedPipe.
func (p BufferedPipe) Read(bz []byte) (n int, err error) {
func (p *BufferedPipe) Read(bz []byte) (n int, err error) {
return p.Reader.Read(bz)
}
// Write implements the io.Writer interface on this BufferedPipe.
func (p BufferedPipe) Write(bz []byte) (n int, err error) {
func (p *BufferedPipe) Write(bz []byte) (n int, err error) {
return p.Writer.Write(bz)
}
@ -231,9 +275,8 @@ func (p *BufferedPipe) panicIfStarted(msg string) {
func (s *InitTestSuite) NewCapturingLogger() (*BufferedPipe, log.Logger) {
bufferedStdOut, err := StartNewBufferedPipe("stdout", os.Stdout)
s.Require().NoError(err, "creating stdout buffered pipe")
output := zerolog.ConsoleWriter{Out: bufferedStdOut, TimeFormat: time.RFC3339Nano}
logger := log.NewCustomLogger(zerolog.New(output).With().Str("module", "cosmovisor").Timestamp().Logger())
return &bufferedStdOut, logger
logger := log.NewLogger(bufferedStdOut, log.ColorOption(false), log.TimeFormatOption(time.RFC3339Nano)).With(log.ModuleKey, cosmovisorDirName)
return bufferedStdOut, logger
}
// CreateHelloWorld creates a shell script that outputs HELLO WORLD.
@ -291,13 +334,13 @@ func (s *InitTestSuite) TestInitializeCosmovisorNegativeValidation() {
name: "no name",
env: cosmovisorInitEnv{Home: "/example", Name: ""},
args: []string{tmpExe},
inErr: []string{cosmovisor.EnvName + " is not set"},
inErr: []string{cosmovisor.EnvName + notset},
},
{
name: "no home",
env: cosmovisorInitEnv{Home: "", Name: "foo"},
args: []string{tmpExe},
inErr: []string{cosmovisor.EnvHome + " is not set"},
inErr: []string{cosmovisor.EnvHome + notset},
},
{
name: "home is relative",
@ -309,7 +352,7 @@ func (s *InitTestSuite) TestInitializeCosmovisorNegativeValidation() {
name: "no name and no home",
env: cosmovisorInitEnv{Home: "", Name: ""},
args: []string{tmpExe},
inErr: []string{cosmovisor.EnvName + " is not set", cosmovisor.EnvHome + " is not set"},
inErr: []string{cosmovisor.EnvName + notset, cosmovisor.EnvHome + notset},
},
}
@ -343,7 +386,7 @@ func (s *InitTestSuite) TestInitializeCosmovisorInvalidExisting() {
Home: filepath.Join(testDir, "home"),
Name: "pear",
}
genDir := filepath.Join(env.Home, "cosmovisor", "genesis")
genDir := filepath.Join(env.Home, cosmovisorDirName, "genesis")
genBin := filepath.Join(genDir, "bin")
require.NoError(t, os.MkdirAll(genDir, 0o755), "creating genesis directory")
require.NoError(t, copyFile(hwExe, genBin), "copying exe to genesis/bin")
@ -363,7 +406,7 @@ func (s *InitTestSuite) TestInitializeCosmovisorInvalidExisting() {
}
// Create the genesis bin executable path fully as a directory (instead of a file).
// That should get through all the other stuff, but error when EnsureBinary is called.
genBinExe := filepath.Join(env.Home, "cosmovisor", "genesis", "bin", env.Name)
genBinExe := filepath.Join(env.Home, cosmovisorDirName, "genesis", "bin", env.Name)
require.NoError(t, os.MkdirAll(genBinExe, 0o755))
expErr := fmt.Sprintf("%s is not a regular file", env.Name)
// Check the log messages just to make sure it's erroring where expecting.
@ -399,18 +442,16 @@ func (s *InitTestSuite) TestInitializeCosmovisorInvalidExisting() {
Home: filepath.Join(testDir, "home"),
Name: "orange",
}
rootDir := filepath.Join(env.Home, "cosmovisor")
rootDir := filepath.Join(env.Home, cosmovisorDirName)
require.NoError(t, os.MkdirAll(rootDir, 0o755))
curLn := filepath.Join(rootDir, "current")
genDir := filepath.Join(rootDir, "genesis")
require.NoError(t, copyFile(hwExe, curLn))
expErr := fmt.Sprintf("symlink %s %s: file exists", genDir, curLn)
s.setEnv(t, env)
buffer, logger := s.NewCapturingLogger()
logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name()))
err := InitializeCosmovisor(logger, []string{hwExe})
require.EqualError(t, err, expErr, "calling InitializeCosmovisor")
require.EqualError(t, err, "symlink genesis current: file exists", "calling InitializeCosmovisor")
bufferBz := buffer.Collect()
bufferStr := string(bufferBz)
assert.Contains(t, bufferStr, "checking on the current symlink and creating it if needed")
@ -443,23 +484,11 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
hwExe := s.CreateHelloWorld(0o755)
s.T().Run("starting with blank slate", func(t *testing.T) {
testDir := s.T().TempDir()
env := &cosmovisorInitEnv{
Home: filepath.Join(testDir, "home"),
env := s.prepareConfig(s.T(), cosmovisorInitEnv{
Name: "blank",
}
curLn := filepath.Join(env.Home, "cosmovisor", "current")
genBinDir := filepath.Join(env.Home, "cosmovisor", "genesis", "bin")
genBinExe := filepath.Join(genBinDir, env.Name)
expInLog := []string{
"checking on the genesis/bin directory",
fmt.Sprintf("creating directory (and any parents): %q", genBinDir),
"checking on the genesis/bin executable",
fmt.Sprintf("copying executable into place: %q", genBinExe),
fmt.Sprintf("making sure %q is executable", genBinExe),
"checking on the current symlink and creating it if needed",
fmt.Sprintf("the current symlink points to: %q", genBinExe),
}
})
curLn := filepath.Join(env.Home, cosmovisorDirName, "current")
s.setEnv(s.T(), env)
buffer, logger := s.NewCapturingLogger()
@ -467,12 +496,31 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
err := InitializeCosmovisor(logger, []string{hwNonExe})
require.NoError(t, err, "calling InitializeCosmovisor")
_, err = os.Stat(genBinDir)
assert.NoErrorf(t, err, "statting the genesis bin dir: %q", genBinDir)
genDir := filepath.Join(env.Home, cosmovisorDirName, "genesis", "bin")
genBinExe := filepath.Join(genDir, env.Name)
genBinDirEval, err := filepath.EvalSymlinks(genDir)
require.NoError(t, err)
genBinEvalExe := filepath.Join(genBinDirEval, env.Name)
expInLog := []string{
"checking on the genesis/bin directory",
fmt.Sprintf("creating directory (and any parents): %q", genDir),
"checking on the genesis/bin executable",
fmt.Sprintf("copying executable into place: %q", genBinExe),
fmt.Sprintf("making sure %q is executable", genBinExe),
"checking on the current symlink and creating it if needed",
fmt.Sprintf("the current symlink points to: %q", genBinEvalExe),
fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)),
}
_, err = os.Stat(genBinDirEval)
assert.NoErrorf(t, err, "statting the genesis bin dir: %q", genBinDirEval)
_, err = os.Stat(curLn)
assert.NoError(t, err, "statting the current link: %q", curLn)
exeInfo, exeErr := os.Stat(genBinExe)
if assert.NoError(t, exeErr, "statting the executable: %q", genBinExe) {
exeInfo, exeErr := os.Stat(genBinEvalExe)
if assert.NoError(t, exeErr, "statting the executable: %q", genBinEvalExe) {
assert.True(t, exeInfo.Mode().IsRegular(), "executable is regular file")
// Check if the world-executable bit is set.
exePermMask := exeInfo.Mode().Perm() & 0o001
@ -491,11 +539,19 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
Home: filepath.Join(testDir, "home"),
Name: "nocur",
}
rootDir := filepath.Join(env.Home, "cosmovisor")
rootDir := filepath.Join(env.Home, cosmovisorDirName)
genBinDir := filepath.Join(rootDir, "genesis", "bin")
genBinDirExe := filepath.Join(genBinDir, env.Name)
require.NoError(t, os.MkdirAll(genBinDir, 0o755), "making genesis bin dir")
require.NoError(t, copyFile(hwExe, genBinDirExe), "copying executable to genesis")
genBinDirEval, err := filepath.EvalSymlinks(genBinDir)
require.NoError(t, err)
genBinEvalExe := filepath.Join(genBinDirEval, env.Name)
upgradesDir := filepath.Join(rootDir, "upgrades")
for i := 1; i <= 5; i++ {
upgradeBinDir := filepath.Join(upgradesDir, fmt.Sprintf("upgrade-%02d", i), "bin")
@ -510,13 +566,14 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
"checking on the genesis/bin executable",
fmt.Sprintf("the %q file already exists", genBinDirExe),
fmt.Sprintf("making sure %q is executable", genBinDirExe),
fmt.Sprintf("the current symlink points to: %q", genBinDirExe),
fmt.Sprintf("the current symlink points to: %q", genBinEvalExe),
fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)),
}
s.setEnv(t, env)
buffer, logger := s.NewCapturingLogger()
logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name()))
err := InitializeCosmovisor(logger, []string{hwExe})
err = InitializeCosmovisor(logger, []string{hwExe})
require.NoError(t, err, "calling InitializeCosmovisor")
bufferBz := buffer.Collect()
bufferStr := string(bufferBz)
@ -531,18 +588,47 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
Home: filepath.Join(testDir, "home"),
Name: "emptygen",
}
rootDir := filepath.Join(env.Home, "cosmovisor")
rootDir := filepath.Join(env.Home, cosmovisorDirName)
genBinDir := filepath.Join(rootDir, "genesis", "bin")
genBinExe := filepath.Join(genBinDir, env.Name)
require.NoError(t, os.MkdirAll(genBinDir, 0o755), "making genesis bin dir")
s.setEnv(t, env)
buffer, logger := s.NewCapturingLogger()
logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name()))
err := InitializeCosmovisor(logger, []string{hwExe})
require.NoError(t, err, "calling InitializeCosmovisor")
genBinDirEval, err := filepath.EvalSymlinks(genBinDir)
require.NoError(t, err)
genBinEvalExe := filepath.Join(genBinDirEval, env.Name)
expInLog := []string{
"checking on the genesis/bin directory",
fmt.Sprintf("the %q directory already exists", genBinDir),
"checking on the genesis/bin executable",
fmt.Sprintf("copying executable into place: %q", genBinExe),
fmt.Sprintf("making sure %q is executable", genBinExe),
fmt.Sprintf("the current symlink points to: %q", genBinExe),
fmt.Sprintf("the current symlink points to: %q", genBinEvalExe),
fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)),
}
bufferBz := buffer.Collect()
bufferStr := string(bufferBz)
for _, exp := range expInLog {
assert.Contains(t, bufferStr, exp)
}
})
s.T().Run("ask to override (y/n) the existing config file", func(t *testing.T) {
})
s.T().Run("init command exports configs to default path", func(t *testing.T) {
testDir := s.T().TempDir()
env := &cosmovisorInitEnv{
Home: filepath.Join(testDir, "home"),
Name: "emptygen",
}
s.setEnv(t, env)
@ -552,8 +638,106 @@ func (s *InitTestSuite) TestInitializeCosmovisorValid() {
require.NoError(t, err, "calling InitializeCosmovisor")
bufferBz := buffer.Collect()
bufferStr := string(bufferBz)
for _, exp := range expInLog {
assert.Contains(t, bufferStr, exp)
}
assert.Contains(t, bufferStr, fmt.Sprintf("cosmovisor config.toml created at: %s", filepath.Join(env.Home, cosmovisorDirName, cfgFileWithExt)))
})
}
func (s *InitTestSuite) TestInitializeCosmovisorWithOverrideCfg() {
initEnv := s.clearEnv()
defer s.setEnv(nil, initEnv)
tmpExe := s.CreateHelloWorld(0o755)
testDir := s.T().TempDir()
homePath := filepath.Join(testDir, "backup")
testCases := []struct {
name string
input string
cfg *cosmovisor.Config
override bool
}{
{
name: "yes override",
input: "y\n",
cfg: &cosmovisor.Config{
Home: homePath,
Name: "old_test",
DataBackupPath: homePath,
},
override: true,
},
{
name: "no override",
input: "n\n",
cfg: &cosmovisor.Config{
Home: homePath,
Name: "old_test",
DataBackupPath: homePath,
},
override: false,
},
}
for _, tc := range testCases {
s.T().Run(tc.name, func(t *testing.T) {
// create a root cosmovisor directory
require.NoError(t, os.MkdirAll(tc.cfg.Root(), 0o755), "making root dir")
// create a config file in the default location
file, err := os.Create(tc.cfg.DefaultCfgPath())
require.NoError(t, err)
// write the config to the file
err = toml.NewEncoder(file).Encode(tc.cfg)
require.NoError(t, err)
err = file.Close()
require.NoError(t, err)
s.readStdInpFromFile([]byte(tc.input))
_, logger := s.NewCapturingLogger()
logger.Info(fmt.Sprintf("Calling InitializeCosmovisor: %s", t.Name()))
// override the daemon name in environment file
// if override is true (y), then the name should be updated in the config file
// otherwise (n), the name should not be updated in the config file
s.setEnv(t, &cosmovisorInitEnv{
Home: tc.cfg.Home,
Name: "update_name",
})
err = InitializeCosmovisor(logger, []string{tmpExe})
require.NoError(t, err, "calling InitializeCosmovisor")
cfg := &cosmovisor.Config{}
// read the config file
cfgFile, err := os.Open(tc.cfg.DefaultCfgPath())
require.NoError(t, err)
defer func() {
_ = cfgFile.Close()
}()
err = toml.NewDecoder(cfgFile).Decode(cfg)
require.NoError(t, err)
if tc.override {
// check if the name is updated
// basically, override the existing config file
assert.Equal(t, "update_name", cfg.Name)
} else {
// daemon name should not be updated
assert.Equal(t, tc.cfg.Name, cfg.Name)
}
})
}
}
func (s *InitTestSuite) prepareConfig(t *testing.T, config cosmovisorInitEnv) *cosmovisorInitEnv {
t.Helper()
config.Home = s.T().TempDir()
err := os.Chdir(config.Home)
require.NoError(t, err)
return &config
}

View File

@ -3,17 +3,10 @@ package main
import (
"context"
"os"
"cosmossdk.io/log"
cverrors "cosmossdk.io/tools/cosmovisor/errors"
)
func main() {
logger := log.NewLogger(os.Stdout).With(log.ModuleKey, "cosmovisor")
ctx := context.WithValue(context.Background(), log.ContextKey, logger)
if err := NewRootCmd().ExecuteContext(ctx); err != nil {
cverrors.LogErrors(logger, "", err)
if err := NewRootCmd().ExecuteContext(context.Background()); err != nil {
os.Exit(1)
}
}

View File

@ -0,0 +1,124 @@
package main
import (
"context"
"crypto/tls"
"fmt"
"strings"
"time"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"cosmossdk.io/tools/cosmovisor"
"cosmossdk.io/x/upgrade/plan"
upgradetypes "cosmossdk.io/x/upgrade/types"
)
func NewPrepareUpgradeCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "prepare-upgrade",
Short: "Prepare for the next upgrade",
Long: `Prepare for the next upgrade by downloading and verifying the upgrade binary.
This command will query the chain for the current upgrade plan and download the specified binary.
gRPC must be enabled on the node for this command to work.`,
RunE: prepareUpgradeHandler,
SilenceUsage: false,
Args: cobra.NoArgs,
}
return cmd
}
func prepareUpgradeHandler(cmd *cobra.Command, _ []string) error {
configPath, err := cmd.Flags().GetString(cosmovisor.FlagCosmovisorConfig)
if err != nil {
return fmt.Errorf("failed to get config flag: %w", err)
}
cfg, err := cosmovisor.GetConfigFromFile(configPath)
if err != nil {
return fmt.Errorf("failed to get config: %w", err)
}
logger := cfg.Logger(cmd.OutOrStdout())
grpcAddress := cfg.GRPCAddress
logger.Info("Using gRPC address", "address", grpcAddress)
upgradeInfo, err := queryUpgradeInfoFromChain(grpcAddress)
if err != nil {
return fmt.Errorf("failed to query upgrade info: %w", err)
}
if upgradeInfo == nil {
logger.Info("No active upgrade plan found")
return nil
}
logger.Info("Preparing for upgrade", "name", upgradeInfo.Name, "height", upgradeInfo.Height)
upgradeInfoParsed, err := plan.ParseInfo(upgradeInfo.Info, plan.ParseOptionEnforceChecksum(cfg.DownloadMustHaveChecksum))
if err != nil {
return fmt.Errorf("failed to parse upgrade info: %w", err)
}
binaryURL, err := cosmovisor.GetBinaryURL(upgradeInfoParsed.Binaries)
if err != nil {
return fmt.Errorf("binary URL not found in upgrade plan. Cannot prepare for upgrade: %w", err)
}
logger.Info("Downloading upgrade binary", "url", binaryURL)
if err := plan.DownloadUpgrade(cfg.UpgradeDir(upgradeInfo.Name), binaryURL, cfg.Name); err != nil {
return fmt.Errorf("failed to download and verify binary: %w", err)
}
logger.Info("Upgrade preparation complete", "name", upgradeInfo.Name, "height", upgradeInfo.Height)
return nil
}
func queryUpgradeInfoFromChain(grpcAddress string) (*upgradetypes.Plan, error) {
if grpcAddress == "" {
return nil, fmt.Errorf("gRPC address is empty")
}
grpcConn, err := getClient(grpcAddress)
if err != nil {
return nil, fmt.Errorf("failed to open gRPC client: %w", err)
}
defer grpcConn.Close()
queryClient := upgradetypes.NewQueryClient(grpcConn)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
res, err := queryClient.CurrentPlan(ctx, &upgradetypes.QueryCurrentPlanRequest{})
if err != nil {
return nil, fmt.Errorf("failed to query current upgrade plan: %w", err)
}
return res.Plan, nil
}
func getClient(endpoint string) (*grpc.ClientConn, error) {
var creds credentials.TransportCredentials
if strings.HasPrefix(endpoint, "https://") {
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
}
creds = credentials.NewTLS(tlsConfig)
} else {
creds = insecure.NewCredentials()
}
opts := []grpc.DialOption{
grpc.WithTransportCredentials(creds),
}
return grpc.NewClient(endpoint, opts...)
}

View File

@ -2,6 +2,8 @@ package main
import (
"github.com/spf13/cobra"
"cosmossdk.io/tools/cosmovisor"
)
func NewRootCmd() *cobra.Command {
@ -12,12 +14,16 @@ func NewRootCmd() *cobra.Command {
}
rootCmd.AddCommand(
initCmd,
NewInitCmd(),
runCmd,
configCmd,
NewVersionCmd(),
NewAddUpgradeCmd(),
NewShowUpgradeInfoCmd(),
NewBatchAddUpgradeCmd(),
NewPrepareUpgradeCmd(),
)
rootCmd.PersistentFlags().StringP(cosmovisor.FlagCosmovisorConfig, "c", "", "path to cosmovisor config file")
return rootCmd
}

View File

@ -1,51 +1,62 @@
package main
import (
"github.com/rs/zerolog"
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
"cosmossdk.io/log"
"cosmossdk.io/tools/cosmovisor"
)
var runCmd = &cobra.Command{
Use: "run",
Short: "Run an APP command.",
Use: "run",
Short: "Run an APP command.",
Long: `Run an APP command. This command is intended to be used by the cosmovisor binary.
Provide '--cosmovisor-config' file path in command args or set env variables to load configuration.
`,
SilenceUsage: true,
DisableFlagParsing: true,
RunE: func(cmd *cobra.Command, args []string) error {
return Run(cmd, args)
RunE: func(_ *cobra.Command, args []string) error {
cfgPath, args, err := parseCosmovisorConfig(args)
if err != nil {
return fmt.Errorf("failed to parse cosmovisor config: %w", err)
}
return run(cfgPath, args)
},
}
// Run runs the configured program with the given args and monitors it for upgrades.
func Run(cmd *cobra.Command, args []string, options ...RunOption) error {
cfg, err := cosmovisor.GetConfigFromEnv()
// run runs the configured program with the given args and monitors it for upgrades.
func run(cfgPath string, args []string, options ...RunOption) error {
cfg, err := cosmovisor.GetConfigFromFile(cfgPath)
if err != nil {
return err
}
logger := cmd.Context().Value(log.ContextKey).(log.Logger)
if cfg.DisableLogs {
logger = log.NewCustomLogger(zerolog.Nop())
}
runCfg := DefaultRunConfig
for _, opt := range options {
opt(&runCfg)
}
// set current working directory to $DAEMON_NAME/cosmosvisor
// to allow current symlink to be relative
if err = os.Chdir(cfg.Root()); err != nil {
return err
}
logger := cfg.Logger(runCfg.StdOut)
launcher, err := cosmovisor.NewLauncher(logger, cfg)
if err != nil {
return err
}
doUpgrade, err := launcher.Run(args, runCfg.StdOut, runCfg.StdErr)
doUpgrade, err := launcher.Run(args, runCfg.StdIn, runCfg.StdOut, runCfg.StdErr)
// if RestartAfterUpgrade, we launch after a successful upgrade (given that condition launcher.Run returns nil)
for cfg.RestartAfterUpgrade && err == nil && doUpgrade {
logger.Info("upgrade detected, relaunching", "app", cfg.Name)
doUpgrade, err = launcher.Run(args, runCfg.StdOut, runCfg.StdErr)
doUpgrade, err = launcher.Run(args, runCfg.StdIn, runCfg.StdOut, runCfg.StdErr)
}
if doUpgrade && err == nil {
@ -54,3 +65,24 @@ func Run(cmd *cobra.Command, args []string, options ...RunOption) error {
return err
}
func parseCosmovisorConfig(args []string) (string, []string, error) {
var configFilePath string
for i, arg := range args {
// Check if the argument is the config flag
if strings.EqualFold(arg, fmt.Sprintf("--%s", cosmovisor.FlagCosmovisorConfig)) ||
strings.EqualFold(arg, fmt.Sprintf("-%s", cosmovisor.FlagCosmovisorConfig)) {
// Check if there is an argument after the flag which should be the config file path
if i+1 >= len(args) {
return "", nil, fmt.Errorf("--%s requires an argument", cosmovisor.FlagCosmovisorConfig)
}
configFilePath = args[i+1]
// Remove the flag and its value from the arguments
args = append(args[:i], args[i+2:]...)
break
}
}
return configFilePath, args, nil
}

View File

@ -7,18 +7,27 @@ import (
// DefaultRunConfig defintes a default RunConfig that writes to os.Stdout and os.Stderr
var DefaultRunConfig = RunConfig{
StdIn: os.Stdin,
StdOut: os.Stdout,
StdErr: os.Stderr,
}
// RunConfig defines the configuration for running a command
type RunConfig struct {
StdIn io.Reader
StdOut io.Writer
StdErr io.Writer
}
type RunOption func(*RunConfig)
// StdInRunOption sets the StdIn reader for the Run command
func StdInRunOption(r io.Reader) RunOption {
return func(cfg *RunConfig) {
cfg.StdIn = r
}
}
// StdOutRunOption sets the StdOut writer for the Run command
func StdOutRunOption(w io.Writer) RunOption {
return func(cfg *RunConfig) {
@ -26,7 +35,7 @@ func StdOutRunOption(w io.Writer) RunOption {
}
}
// SdErrRunOption sets the StdErr writer for the Run command
// StdErrRunOption sets the StdErr writer for the Run command
func StdErrRunOption(w io.Writer) RunOption {
return func(cfg *RunConfig) {
cfg.StdErr = w

View File

@ -0,0 +1,42 @@
package main
import (
"fmt"
"os"
"github.com/spf13/cobra"
"cosmossdk.io/tools/cosmovisor"
)
func NewShowUpgradeInfoCmd() *cobra.Command {
return &cobra.Command{
Use: "show-upgrade-info",
Short: "Display current upgrade-info.json from <app> data directory",
SilenceUsage: false,
Args: cobra.NoArgs,
RunE: func(cmd *cobra.Command, args []string) error {
configPath, err := cmd.Flags().GetString(cosmovisor.FlagCosmovisorConfig)
if err != nil {
return fmt.Errorf("failed to get config flag: %w", err)
}
cfg, err := cosmovisor.GetConfigFromFile(configPath)
if err != nil {
return err
}
data, err := os.ReadFile(cfg.UpgradeInfoFilePath())
if err != nil {
if os.IsNotExist(err) {
cmd.Printf("No upgrade info found at %s\n", cfg.UpgradeInfoFilePath())
return nil
}
return fmt.Errorf("failed to read upgrade-info.json: %w", err)
}
cmd.Println(string(data))
return nil
},
}
}

View File

@ -13,10 +13,11 @@ import (
func NewVersionCmd() *cobra.Command {
versionCmd := &cobra.Command{
Use: "version",
Short: "Display cosmovisor and APP version.",
Use: "version",
Short: "Display cosmovisor and APP version.",
SilenceUsage: true,
RunE: func(cmd *cobra.Command, args []string) error {
noAppVersion, _ := cmd.Flags().GetBool(cosmovisor.FlagNoAppVersion)
noAppVersion, _ := cmd.Flags().GetBool(cosmovisor.FlagCosmovisorOnly)
if val, err := cmd.Flags().GetString(cosmovisor.FlagOutput); val == "json" && err == nil {
return printVersionJSON(cmd, args, noAppVersion)
}
@ -26,7 +27,7 @@ func NewVersionCmd() *cobra.Command {
}
versionCmd.Flags().StringP(cosmovisor.FlagOutput, "o", "text", "Output format (text|json)")
versionCmd.Flags().Bool(cosmovisor.FlagNoAppVersion, false, "Don't print APP version")
versionCmd.Flags().Bool(cosmovisor.FlagCosmovisorOnly, false, "Print cosmovisor version only")
return versionCmd
}
@ -46,7 +47,7 @@ func printVersion(cmd *cobra.Command, args []string, noAppVersion bool) error {
return nil
}
if err := Run(cmd, append([]string{"version"}, args...)); err != nil {
if err := run("", append([]string{"version"}, args...)); err != nil {
return fmt.Errorf("failed to run version command: %w", err)
}
@ -60,8 +61,8 @@ func printVersionJSON(cmd *cobra.Command, args []string, noAppVersion bool) erro
}
buf := new(strings.Builder)
if err := Run(
cmd,
if err := run(
"",
[]string{"version", "--long", "--output", "json"},
StdOutRunOption(buf),
); err != nil {

View File

@ -1,85 +0,0 @@
package errors
import (
"fmt"
"strings"
"cosmossdk.io/log"
)
// MultiError is an error combining multiple other errors.
// It will never have 0 or 1 errors. It will always have two or more.
type MultiError struct {
errs []error
}
// FlattenErrors possibly creates a MultiError.
// Nil entries are ignored.
// If all provided errors are nil (or nothing is provided), nil is returned.
// If only one non-nil error is provided, it is returned unchanged.
// If two or more non-nil errors are provided, the returned error will be of type *MultiError
// and it will contain each non-nil error.
func FlattenErrors(errs ...error) error {
rv := MultiError{}
for _, err := range errs {
if err != nil {
if merr, isMerr := err.(*MultiError); isMerr {
rv.errs = append(rv.errs, merr.errs...)
} else {
rv.errs = append(rv.errs, err)
}
}
}
switch rv.Len() {
case 0:
return nil
case 1:
return rv.errs[0]
}
return &rv
}
// GetErrors gets all the errors that make up this MultiError.
func (e MultiError) GetErrors() []error {
// Return a copy of the errs slice to prevent alteration of the original slice.
rv := make([]error, e.Len())
copy(rv, e.errs)
return rv
}
// Len gets the number of errors in this MultiError.
func (e MultiError) Len() int {
return len(e.errs)
}
// Error implements the error interface for a MultiError.
func (e *MultiError) Error() string {
var sb strings.Builder
fmt.Fprintf(&sb, "%d errors: ", len(e.errs))
for i, err := range e.errs {
if i != 0 {
sb.WriteString(", ")
}
fmt.Fprintf(&sb, "%d: %v", i+1, err)
}
return sb.String()
}
// String implements the string interface for a MultiError.
func (e MultiError) String() string {
return e.Error()
}
func LogErrors(logger log.Logger, msg string, err error) {
switch err := err.(type) {
case *MultiError:
if msg != "" {
logger.Error(msg)
}
for i, e := range err.GetErrors() {
logger.Error(fmt.Sprintf(" %d:", i+1), "error", e)
}
default:
logger.Error(msg, "error", err)
}
}

View File

@ -1,190 +0,0 @@
package errors
import (
"errors"
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
)
type MultiErrorTestSuite struct {
suite.Suite
err1 error
err2 error
err3 error
err4 error
}
func TestMultiErrorTestSuite(t *testing.T) {
suite.Run(t, new(MultiErrorTestSuite))
}
func (s *MultiErrorTestSuite) SetupTest() {
s.err1 = errors.New("expected error one")
s.err2 = errors.New("expected error two")
s.err3 = errors.New("expected error three")
s.err3 = errors.New("expected error four")
}
func (s *MultiErrorTestSuite) TestFlattenErrors() {
tests := []struct {
name string
input []error
expected error
}{
{
name: "none in nil out",
input: []error{},
expected: nil,
},
{
name: "nil in nil out",
input: []error{nil},
expected: nil,
},
{
name: "nils in nil out",
input: []error{nil, nil, nil},
expected: nil,
},
{
name: "one in same out",
input: []error{s.err1},
expected: s.err1,
},
{
name: "nils and one in that one out",
input: []error{nil, s.err2, nil},
expected: s.err2,
},
{
name: "two in multi out with both",
input: []error{s.err1, s.err2},
expected: &MultiError{errs: []error{s.err1, s.err2}},
},
{
name: "two and nils in multi out with both",
input: []error{nil, s.err1, nil, s.err2, nil},
expected: &MultiError{errs: []error{s.err1, s.err2}},
},
{
name: "lots in multi out",
input: []error{s.err1, s.err2, s.err3, s.err2, s.err1},
expected: &MultiError{errs: []error{s.err1, s.err2, s.err3, s.err2, s.err1}},
},
{
name: "multi and non in one multi out with all",
input: []error{&MultiError{errs: []error{s.err1, s.err2}}, s.err3},
expected: &MultiError{errs: []error{s.err1, s.err2, s.err3}},
},
{
name: "non and multi in one multi out with all",
input: []error{s.err1, &MultiError{errs: []error{s.err2, s.err3}}},
expected: &MultiError{errs: []error{s.err1, s.err2, s.err3}},
},
{
name: "two multi in one multi out with all",
input: []error{&MultiError{errs: []error{s.err1, s.err2}}, &MultiError{errs: []error{s.err3, s.err4}}},
expected: &MultiError{errs: []error{s.err1, s.err2, s.err3, s.err4}},
},
}
for _, tc := range tests {
s.T().Run(tc.name, func(t *testing.T) {
actual := FlattenErrors(tc.input...)
require.Equal(t, tc.expected, actual)
})
}
}
func (s *MultiErrorTestSuite) TestGetErrors() {
tests := []struct {
name string
multi MultiError
expected []error
}{
{
name: "two",
multi: MultiError{errs: []error{s.err3, s.err1}},
expected: []error{s.err3, s.err1},
},
{
name: "three",
multi: MultiError{errs: []error{s.err3, s.err1, s.err2}},
expected: []error{s.err3, s.err1, s.err2},
},
}
for _, tc := range tests {
s.T().Run(tc.name, func(t *testing.T) {
// Make sure it's getting what's expected.
actual1 := tc.multi.GetErrors()
require.NotSame(t, tc.expected, actual1)
require.Equal(t, tc.expected, actual1)
// Make sure that changing what was given back doesn't alter the original.
actual1[0] = errors.New("unexpected error")
actual2 := tc.multi.GetErrors()
require.NotEqual(t, actual1, actual2)
require.Equal(t, tc.expected, actual2)
})
}
}
func (s *MultiErrorTestSuite) TestLen() {
tests := []struct {
name string
multi MultiError
expected int
}{
{
name: "two",
multi: MultiError{errs: []error{s.err3, s.err1}},
expected: 2,
},
{
name: "three",
multi: MultiError{errs: []error{s.err3, s.err1, s.err2}},
expected: 3,
},
}
for _, tc := range tests {
s.T().Run(tc.name, func(t *testing.T) {
actual := tc.multi.Len()
require.Equal(t, tc.expected, actual)
})
}
}
func (s *MultiErrorTestSuite) TestErrorAndString() {
tests := []struct {
name string
multi MultiError
expected string
}{
{
name: "two",
multi: MultiError{errs: []error{s.err1, s.err2}},
expected: fmt.Sprintf("2 errors: 1: %s, 2: %s", s.err1, s.err2),
},
{
name: "three",
multi: MultiError{errs: []error{s.err1, s.err2, s.err3}},
expected: fmt.Sprintf("3 errors: 1: %s, 2: %s, 3: %s", s.err1, s.err2, s.err3),
},
}
for _, tc := range tests {
s.T().Run(tc.name+" Error", func(t *testing.T) {
actual := tc.multi.Error()
require.Equal(t, tc.expected, actual)
})
s.T().Run(tc.name+" String", func(t *testing.T) {
actual := tc.multi.String()
require.Equal(t, tc.expected, actual)
})
}
}

View File

@ -3,6 +3,8 @@ package cosmovisor
const (
FlagOutput = "output"
FlagSkipUpgradeHeight = "unsafe-skip-upgrades"
FlagNoAppVersion = "no-app-version"
FlagCosmovisorOnly = "cosmovisor-only"
FlagForce = "force"
FlagUpgradeHeight = "upgrade-height"
FlagCosmovisorConfig = "cosmovisor-config"
)

View File

@ -3,181 +3,203 @@ module cosmossdk.io/tools/cosmovisor
go 1.23
require (
cosmossdk.io/log v1.4.1
cosmossdk.io/x/upgrade v0.0.0-20230614103911-b3da8bb4e801
github.com/otiai10/copy v1.11.0
github.com/rs/zerolog v1.33.0
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.9.0
cosmossdk.io/log v1.5.0
cosmossdk.io/x/upgrade v0.1.4
github.com/cosmos/cosmos-sdk v0.50.11
github.com/fsnotify/fsnotify v1.8.0
github.com/otiai10/copy v1.14.1
github.com/pelletier/go-toml/v2 v2.2.3
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.10.0
google.golang.org/grpc v1.70.0
)
require (
cloud.google.com/go v0.112.0 // indirect
cloud.google.com/go/compute v1.24.0 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/iam v1.1.6 // indirect
cloud.google.com/go/storage v1.36.0 // indirect
cosmossdk.io/api v0.7.5 // indirect
cel.dev/expr v0.19.1 // indirect
cloud.google.com/go v0.118.0 // indirect
cloud.google.com/go/auth v0.14.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.7 // indirect
cloud.google.com/go/compute/metadata v0.6.0 // indirect
cloud.google.com/go/iam v1.3.1 // indirect
cloud.google.com/go/monitoring v1.22.1 // indirect
cloud.google.com/go/storage v1.50.0 // indirect
cosmossdk.io/api v0.7.6 // indirect
cosmossdk.io/collections v0.4.0 // indirect
cosmossdk.io/core v0.11.0 // indirect
cosmossdk.io/depinject v1.0.0-alpha.4 // indirect
cosmossdk.io/core v0.11.1 // indirect
cosmossdk.io/depinject v1.1.0 // indirect
cosmossdk.io/errors v1.0.1 // indirect
cosmossdk.io/math v1.3.0 // indirect
cosmossdk.io/store v1.0.0 // indirect
cosmossdk.io/x/tx v0.13.4 // indirect
filippo.io/edwards25519 v1.0.0 // indirect
cosmossdk.io/math v1.5.0 // indirect
cosmossdk.io/store v1.1.1 // indirect
cosmossdk.io/x/tx v1.0.0-alpha.3 // indirect
filippo.io/edwards25519 v1.1.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.1 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/armon/go-metrics v0.4.1 // indirect
github.com/aws/aws-sdk-go v1.44.224 // indirect
github.com/99designs/keyring v1.2.2 // indirect
github.com/DataDog/datadog-go v4.8.3+incompatible // indirect
github.com/DataDog/zstd v1.5.6 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.49.0 // indirect
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.49.0 // indirect
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/aws/aws-sdk-go v1.55.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cockroachdb/errors v1.11.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v1.1.0 // indirect
github.com/bgentry/speakeasy v0.2.0 // indirect
github.com/bytedance/sonic v1.12.8 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cncf/xds/go v0.0.0-20241223141626-cff3c89139a3 // indirect
github.com/cockroachdb/apd/v3 v3.2.1 // indirect
github.com/cockroachdb/errors v1.11.3 // indirect
github.com/cockroachdb/fifo v0.0.0-20240816210425-c5d0cb0b6fc0 // indirect
github.com/cockroachdb/logtags v0.0.0-20241215232642-bb51bb14a506 // indirect
github.com/cockroachdb/pebble v1.1.2 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/cometbft/cometbft v0.38.11 // indirect
github.com/cometbft/cometbft-db v0.9.1 // indirect
github.com/cometbft/cometbft v0.38.17 // indirect
github.com/cometbft/cometbft-db v0.14.1 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-db v1.0.2 // indirect
github.com/cosmos/cosmos-db v1.1.1 // indirect
github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect
github.com/cosmos/cosmos-sdk v0.46.0-beta2.0.20230614103911-b3da8bb4e801 // indirect
github.com/cosmos/go-bip39 v1.0.0 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/gogoproto v1.7.0 // indirect
github.com/cosmos/iavl v1.0.1 // indirect
github.com/cosmos/iavl v1.2.2 // indirect
github.com/cosmos/ics23/go v0.11.0 // indirect
github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/danieljoos/wincred v1.2.1 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/badger/v4 v4.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/emicklei/dot v1.6.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/dvsekhvalnov/jose2go v1.7.0 // indirect
github.com/emicklei/dot v1.6.2 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.3 // indirect
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/go-kit/kit v0.12.0 // indirect
github.com/getsentry/sentry-go v0.30.0 // indirect
github.com/go-kit/kit v0.13.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.2.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/glog v1.2.4 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/flatbuffers v24.3.25+incompatible // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
github.com/gorilla/handlers v1.5.2 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.3 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-getter v1.7.1 // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-getter v1.7.7 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-metrics v0.5.1 // indirect
github.com/hashicorp/go-plugin v1.5.2 // indirect
github.com/hashicorp/go-metrics v0.5.4 // indirect
github.com/hashicorp/go-plugin v1.6.2 // indirect
github.com/hashicorp/go-safetemp v1.0.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hdevalence/ed25519consensus v0.1.0 // indirect
github.com/huandu/skiplist v1.2.0 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/hdevalence/ed25519consensus v0.2.0 // indirect
github.com/huandu/skiplist v1.2.1 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/improbable-eng/grpc-web v0.15.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/linxGnu/grocksdb v1.8.12 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/linxGnu/grocksdb v1.9.7 // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/pelletier/go-toml/v2 v2.1.0 // indirect
github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc // indirect
github.com/otiai10/mint v1.6.3 // indirect
github.com/petermattis/goid v0.0.0-20240813172612-4fcff4a6cae7 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.18.0 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.47.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.62.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/cors v1.8.3 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/rs/cors v1.11.1 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/sagikazarmark/locafero v0.6.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/sasha-s/go-deadlock v0.3.5 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.18.2 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ulikunitz/xz v0.5.12 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v0.14.3 // indirect
go.etcd.io/bbolt v1.3.8 // indirect
go.etcd.io/bbolt v1.4.0-alpha.1 // indirect
go.opencensus.io v0.24.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect
go.opentelemetry.io/otel v1.22.0 // indirect
go.opentelemetry.io/otel/metric v1.22.0 // indirect
go.opentelemetry.io/otel/trace v1.22.0 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/oauth2 v0.17.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/term v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
golang.org/x/time v0.5.0 // indirect
google.golang.org/api v0.162.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
google.golang.org/grpc v1.63.2 // indirect
google.golang.org/protobuf v1.34.2 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/detectors/gcp v1.33.0 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
go.opentelemetry.io/otel v1.33.0 // indirect
go.opentelemetry.io/otel/metric v1.33.0 // indirect
go.opentelemetry.io/otel/sdk v1.33.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.33.0 // indirect
go.opentelemetry.io/otel/trace v1.33.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.13.0 // indirect
golang.org/x/crypto v0.32.0 // indirect
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
golang.org/x/net v0.34.0 // indirect
golang.org/x/oauth2 v0.25.0 // indirect
golang.org/x/sync v0.10.0 // indirect
golang.org/x/sys v0.29.0 // indirect
golang.org/x/term v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
golang.org/x/time v0.9.0 // indirect
google.golang.org/api v0.216.0 // indirect
google.golang.org/genproto v0.0.0-20250106144421-5f5ef82da422 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 // indirect
google.golang.org/protobuf v1.36.4 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.1 // indirect
nhooyr.io/websocket v1.8.6 // indirect
nhooyr.io/websocket v1.8.11 // indirect
pgregory.net/rapid v1.1.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +1,191 @@
package cosmovisor
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"os/exec"
"os/signal"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/fsnotify/fsnotify"
"github.com/otiai10/copy"
"github.com/rs/zerolog"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"cosmossdk.io/log"
"cosmossdk.io/x/upgrade/plan"
upgradetypes "cosmossdk.io/x/upgrade/types"
"github.com/cosmos/cosmos-sdk/client/grpc/cmtservice"
)
type Launcher struct {
logger *zerolog.Logger
logger log.Logger
cfg *Config
fw *fileWatcher
}
func NewLauncher(logger log.Logger, cfg *Config) (Launcher, error) {
fw, err := newUpgradeFileWatcher(logger, cfg.UpgradeInfoFilePath(), cfg.PollInterval)
fw, err := newUpgradeFileWatcher(cfg)
if err != nil {
return Launcher{}, err
}
zl := logger.Impl().(*zerolog.Logger)
return Launcher{logger: zl, cfg: cfg, fw: fw}, nil
return Launcher{logger: logger, cfg: cfg, fw: fw}, nil
}
// loadBatchUpgradeFile loads the batch upgrade file into memory, sorted by
// their upgrade heights
func loadBatchUpgradeFile(cfg *Config) ([]upgradetypes.Plan, error) {
var uInfos []upgradetypes.Plan
upgradeInfoFile, err := os.ReadFile(cfg.UpgradeInfoBatchFilePath())
if os.IsNotExist(err) {
return uInfos, nil
} else if err != nil {
return nil, fmt.Errorf("error while reading %s: %w", cfg.UpgradeInfoBatchFilePath(), err)
}
if err = json.Unmarshal(upgradeInfoFile, &uInfos); err != nil {
return nil, err
}
sort.Slice(uInfos, func(i, j int) bool {
return uInfos[i].Height < uInfos[j].Height
})
return uInfos, nil
}
// BatchUpgradeWatcher starts a watcher loop that swaps upgrade manifests at the correct
// height, given the batch upgrade file. It watches the current state of the chain
// via the websocket API.
func BatchUpgradeWatcher(ctx context.Context, cfg *Config, logger log.Logger) {
// load batch file in memory
uInfos, err := loadBatchUpgradeFile(cfg)
if err != nil {
logger.Warn("failed to load batch upgrade file", "error", err)
uInfos = []upgradetypes.Plan{}
}
watcher, err := fsnotify.NewWatcher()
if err != nil {
logger.Warn("failed to init watcher", "error", err)
return
}
defer watcher.Close()
err = watcher.Add(filepath.Dir(cfg.UpgradeInfoBatchFilePath()))
if err != nil {
logger.Warn("watcher failed to add upgrade directory", "error", err)
return
}
var conn *grpc.ClientConn
var grpcErr error
defer func() {
if conn != nil {
if err := conn.Close(); err != nil {
logger.Warn("couldn't stop gRPC client", "error", err)
}
}
}()
// Wait for the chain process to be ready
pollLoop:
for {
select {
case <-ctx.Done():
return
default:
conn, grpcErr = grpc.NewClient(cfg.GRPCAddress, grpc.WithTransportCredentials(insecure.NewCredentials()))
if grpcErr == nil {
break pollLoop
}
time.Sleep(time.Second)
}
}
client := cmtservice.NewServiceClient(conn)
var prevUpgradeHeight int64 = -1
logger.Info("starting the batch watcher loop")
for {
select {
case event := <-watcher.Events:
if event.Op&(fsnotify.Write|fsnotify.Create) != 0 {
uInfos, err = loadBatchUpgradeFile(cfg)
if err != nil {
logger.Warn("failed to load batch upgrade file", "error", err)
continue
}
}
case <-ctx.Done():
return
default:
if len(uInfos) == 0 {
// prevent spending extra CPU cycles
time.Sleep(time.Second)
continue
}
resp, err := client.GetLatestBlock(ctx, &cmtservice.GetLatestBlockRequest{})
if err != nil {
logger.Warn("error getting latest block", "error", err)
time.Sleep(time.Second)
continue
}
h := resp.SdkBlock.Header.Height
upcomingUpgrade := uInfos[0].Height
// replace upgrade-info and upgrade-info batch file
if h > prevUpgradeHeight && h < upcomingUpgrade {
jsonBytes, err := json.Marshal(uInfos[0])
if err != nil {
logger.Warn("error marshaling JSON for upgrade-info.json", "error", err, "upgrade", uInfos[0])
continue
}
if err := os.WriteFile(cfg.UpgradeInfoFilePath(), jsonBytes, 0o600); err != nil {
logger.Warn("error writing upgrade-info.json", "error", err)
continue
}
uInfos = uInfos[1:]
jsonBytes, err = json.Marshal(uInfos)
if err != nil {
logger.Warn("error marshaling JSON for upgrade-info.json.batch", "error", err, "upgrades", uInfos)
continue
}
if err := os.WriteFile(cfg.UpgradeInfoBatchFilePath(), jsonBytes, 0o600); err != nil {
logger.Warn("error writing upgrade-info.json.batch", "error", err)
// remove the upgrade-info.json.batch file to avoid non-deterministic behavior
err := os.Remove(cfg.UpgradeInfoBatchFilePath())
if err != nil && !os.IsNotExist(err) {
logger.Warn("error removing upgrade-info.json.batch", "error", err)
return
}
continue
}
prevUpgradeHeight = upcomingUpgrade
}
// Add a small delay to avoid hammering the gRPC endpoint
time.Sleep(time.Second)
}
}
}
// Run launches the app in a subprocess and returns when the subprocess (app)
// exits (either when it dies, or *after* a successful upgrade.) and upgrade finished.
// Returns true if the upgrade request was detected and the upgrade process started.
func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
func (l Launcher) Run(args []string, stdin io.Reader, stdout, stderr io.Writer) (bool, error) {
bin, err := l.cfg.CurrentBin()
if err != nil {
return false, fmt.Errorf("error creating symlink to genesis: %w", err)
@ -50,20 +195,32 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
return false, fmt.Errorf("current binary is invalid: %w", err)
}
l.logger.Info().Str("path", bin).Strs("args", args).Msg("running app")
l.logger.Info("running app", "path", bin, "args", args)
cmd := exec.Command(bin, args...)
cmd.Stdin = stdin
cmd.Stdout = stdout
cmd.Stderr = stderr
if err := cmd.Start(); err != nil {
return false, fmt.Errorf("launching process %s %s failed: %w", bin, strings.Join(args, " "), err)
}
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
BatchUpgradeWatcher(ctx, l.cfg, l.logger)
}()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGQUIT, syscall.SIGTERM)
go func() {
sig := <-sigs
cancel()
wg.Wait()
if err := cmd.Process.Signal(sig); err != nil {
l.logger.Fatal().Err(err).Str("bin", bin).Msg("terminated")
l.logger.Error("terminated", "error", err, "bin", bin)
os.Exit(1)
}
}()
@ -78,7 +235,11 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
return false, err
}
if err := UpgradeBinary(log.NewCustomLogger(*l.logger), l.cfg, l.fw.currentInfo); err != nil {
if err := l.doCustomPreUpgrade(); err != nil {
return false, err
}
if err := UpgradeBinary(l.logger, l.cfg, l.fw.currentInfo); err != nil {
return false, err
}
@ -89,6 +250,9 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
return true, nil
}
cancel()
wg.Wait()
return false, nil
}
@ -96,13 +260,14 @@ func (l Launcher) Run(args []string, stdout, stderr io.Writer) (bool, error) {
// When it returns, the process (app) is finished.
//
// It returns (true, nil) if an upgrade should be initiated (and we killed the process)
// It returns (false, err) if the process died by itself, or there was an issue reading the upgrade-info file.
// It returns (false, err) if the process died by itself
// It returns (false, nil) if the process exited normally without triggering an upgrade. This is very unlikely
// to happened with "start" but may happened with short-lived commands like `gaiad export ...`
// to happen with "start" but may happen with short-lived commands like `simd genesis export ...`
func (l Launcher) WaitForUpgradeOrExit(cmd *exec.Cmd) (bool, error) {
currentUpgrade, err := l.cfg.UpgradeInfo()
if err != nil {
l.logger.Error().Err(err)
// upgrade info not found do nothing
currentUpgrade = upgradetypes.Plan{}
}
cmdDone := make(chan error)
@ -113,8 +278,34 @@ func (l Launcher) WaitForUpgradeOrExit(cmd *exec.Cmd) (bool, error) {
select {
case <-l.fw.MonitorUpdate(currentUpgrade):
// upgrade - kill the process and restart
l.logger.Info().Msg("daemon shutting down in an attempt to restart")
_ = cmd.Process.Kill()
l.logger.Info("daemon shutting down in an attempt to restart")
if l.cfg.ShutdownGrace > 0 {
// Interrupt signal
l.logger.Info("sent interrupt to app, waiting for exit")
_ = cmd.Process.Signal(os.Interrupt)
// Wait app exit
psChan := make(chan *os.ProcessState)
go func() {
pstate, _ := cmd.Process.Wait()
psChan <- pstate
}()
// Timeout and kill
select {
case <-psChan:
// Normal Exit
l.logger.Info("app exited normally")
case <-time.After(l.cfg.ShutdownGrace):
l.logger.Info("DAEMON_SHUTDOWN_GRACE exceeded, killing app")
// Kill after grace period
_ = cmd.Process.Kill()
}
} else {
// Default: Immediate app kill
_ = cmd.Process.Kill()
}
case err := <-cmdDone:
l.fw.Stop()
// no error -> command exits normally (eg. short command like `gaiad version`)
@ -135,7 +326,7 @@ func (l Launcher) doBackup() error {
if !l.cfg.UnsafeSkipBackup {
// check if upgrade-info.json is not empty.
var uInfo upgradetypes.Plan
upgradeInfoFile, err := os.ReadFile(filepath.Join(l.cfg.Home, "data", "upgrade-info.json"))
upgradeInfoFile, err := os.ReadFile(l.cfg.UpgradeInfoFilePath())
if err != nil {
return fmt.Errorf("error while reading upgrade-info.json: %w", err)
}
@ -145,15 +336,15 @@ func (l Launcher) doBackup() error {
}
if uInfo.Name == "" {
return fmt.Errorf("upgrade-info.json is empty")
return errors.New("upgrade-info.json is empty")
}
// a destination directory, Format YYYY-MM-DD
st := time.Now()
stStr := fmt.Sprintf("%d-%d-%d", st.Year(), st.Month(), st.Day())
dst := filepath.Join(l.cfg.DataBackupPath, fmt.Sprintf("data"+"-backup-%s", stStr))
ymd := fmt.Sprintf("%d-%d-%d", st.Year(), st.Month(), st.Day())
dst := filepath.Join(l.cfg.DataBackupPath, fmt.Sprintf("data"+"-backup-%s", ymd))
l.logger.Info().Time("backup start time", st).Msg("starting to take backup of data directory")
l.logger.Info("starting to take backup of data directory", "backup start time", st)
// copy the $DAEMON_HOME/data to a backup dir
if err = copy.Copy(filepath.Join(l.cfg.Home, "data"), dst); err != nil {
@ -162,38 +353,100 @@ func (l Launcher) doBackup() error {
// backup is done, lets check endtime to calculate total time taken for backup process
et := time.Now()
l.logger.Info().Str("backup saved at", dst).Time("backup completion time", et).TimeDiff("time taken to complete backup", et, st).Msg("backup completed")
l.logger.Info("backup completed", "backup saved at", dst, "backup completion time", et, "time taken to complete backup", et.Sub(st))
}
return nil
}
// doCustomPreUpgrade executes the custom preupgrade script if provided.
func (l Launcher) doCustomPreUpgrade() error {
if l.cfg.CustomPreUpgrade == "" {
return nil
}
// check if upgrade-info.json is not empty.
var upgradePlan upgradetypes.Plan
upgradeInfoFile, err := os.ReadFile(l.cfg.UpgradeInfoFilePath())
if err != nil {
return fmt.Errorf("error while reading upgrade-info.json: %w", err)
}
if err = json.Unmarshal(upgradeInfoFile, &upgradePlan); err != nil {
return err
}
if err = upgradePlan.ValidateBasic(); err != nil {
return fmt.Errorf("invalid upgrade plan: %w", err)
}
// check if preupgradeFile is executable file
preupgradeFile := filepath.Join(l.cfg.Home, "cosmovisor", l.cfg.CustomPreUpgrade)
l.logger.Info("looking for COSMOVISOR_CUSTOM_PREUPGRADE file", "file", preupgradeFile)
info, err := os.Stat(preupgradeFile)
if err != nil {
l.logger.Error("COSMOVISOR_CUSTOM_PREUPGRADE file missing", "file", preupgradeFile)
return err
}
if !info.Mode().IsRegular() {
_, f := filepath.Split(preupgradeFile)
return fmt.Errorf("COSMOVISOR_CUSTOM_PREUPGRADE: %s is not a regular file", f)
}
// Set the execute bit for only the current user
// Given: Current user - Group - Everyone
// 0o RWX - RWX - RWX
oldMode := info.Mode().Perm()
newMode := oldMode | 0o100
if oldMode != newMode {
if err := os.Chmod(preupgradeFile, newMode); err != nil {
l.logger.Info("COSMOVISOR_CUSTOM_PREUPGRADE could not add execute permission")
return errors.New("COSMOVISOR_CUSTOM_PREUPGRADE could not add execute permission")
}
}
// Run preupgradeFile
cmd := exec.Command(preupgradeFile, upgradePlan.Name, fmt.Sprintf("%d", upgradePlan.Height))
cmd.Dir = l.cfg.Home
result, err := cmd.Output()
if err != nil {
return err
}
l.logger.Info("COSMOVISOR_CUSTOM_PREUPGRADE result", "command", preupgradeFile, "argv1", upgradePlan.Name, "argv2", fmt.Sprintf("%d", upgradePlan.Height), "result", result)
return nil
}
// doPreUpgrade runs the pre-upgrade command defined by the application and handles respective error codes.
// cfg contains the cosmovisor config from env var.
// doPreUpgrade runs the new APP binary in order to process the upgrade (post-upgrade for cosmovisor).
func (l *Launcher) doPreUpgrade() error {
counter := 0
for {
if counter > l.cfg.PreupgradeMaxRetries {
return fmt.Errorf("pre-upgrade command failed. reached max attempt of retries - %d", l.cfg.PreupgradeMaxRetries)
if counter > l.cfg.PreUpgradeMaxRetries {
return fmt.Errorf("pre-upgrade command failed. reached max attempt of retries - %d", l.cfg.PreUpgradeMaxRetries)
}
if err := l.executePreUpgradeCmd(); err != nil {
counter++
switch err.(*exec.ExitError).ProcessState.ExitCode() {
case 1:
l.logger.Info().Msg("pre-upgrade command does not exist. continuing the upgrade.")
return nil
case 30:
return fmt.Errorf("pre-upgrade command failed : %w", err)
case 31:
l.logger.Error().Err(err).Int("attempt", counter).Msg("pre-upgrade command failed. retrying")
continue
var exitErr *exec.ExitError
if errors.As(err, &exitErr) {
switch exitErr.ProcessState.ExitCode() {
case 1:
l.logger.Info("pre-upgrade command does not exist. continuing the upgrade.")
return nil
case 30:
return fmt.Errorf("pre-upgrade command failed : %w", err)
case 31:
l.logger.Error("pre-upgrade command failed. retrying", "error", err, "attempt", counter)
continue
}
}
}
l.logger.Info().Msg("pre-upgrade successful. continuing the upgrade.")
l.logger.Info("pre-upgrade successful. continuing the upgrade.")
return nil
}
}
@ -211,7 +464,7 @@ func (l *Launcher) executePreUpgradeCmd() error {
return err
}
l.logger.Info().Bytes("result", result).Msg("pre-upgrade result")
l.logger.Info("pre-upgrade result", "result", result)
return nil
}

View File

@ -1,171 +1,468 @@
//go:build linux
// +build linux
//go:build linux || darwin
package cosmovisor_test
import (
"bytes"
"fmt"
"io/fs"
"os"
"path/filepath"
"sync"
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"cosmossdk.io/log"
"cosmossdk.io/tools/cosmovisor"
upgradetypes "cosmossdk.io/x/upgrade/types"
)
type processTestSuite struct {
suite.Suite
}
var workDir string
func TestProcessTestSuite(t *testing.T) {
suite.Run(t, new(processTestSuite))
func init() {
workDir, _ = os.Getwd()
}
// TestLaunchProcess will try running the script a few times and watch upgrades work properly
// and args are passed through
func (s *processTestSuite) TestLaunchProcess() {
func TestLaunchProcess(t *testing.T) {
// binaries from testdata/validate directory
require := s.Require()
home := copyTestData(s.T(), "validate")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", PollInterval: 20, UnsafeSkipBackup: true}
logger := log.NewTestLogger(s.T()).With(log.ModuleKey, "cosmosvisor")
cfg := prepareConfig(
t,
fmt.Sprintf("%s/%s", workDir, "testdata/validate"),
cosmovisor.Config{
Name: "dummyd",
PollInterval: 15,
UnsafeSkipBackup: true,
},
)
logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor")
// should run the genesis binary and produce expected output
stdin, _ := os.Open(os.DevNull)
stdout, stderr := newBuffer(), newBuffer()
currentBin, err := cfg.CurrentBin()
require.NoError(err)
require.Equal(cfg.GenesisBin(), currentBin)
require.NoError(t, err)
rPath, err := filepath.EvalSymlinks(cfg.GenesisBin())
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
launcher, err := cosmovisor.NewLauncher(logger, cfg)
require.NoError(err)
require.NoError(t, err)
upgradeFile := cfg.UpgradeInfoFilePath()
args := []string{"foo", "bar", "1234", upgradeFile}
doUpgrade, err := launcher.Run(args, stdout, stderr)
require.NoError(err)
require.True(doUpgrade)
require.Equal("", stderr.String())
require.Equal(fmt.Sprintf("Genesis foo bar 1234 %s\nUPGRADE \"chain2\" NEEDED at height: 49: {}\n", upgradeFile), stdout.String())
doUpgrade, err := launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.True(t, doUpgrade)
require.Empty(t, stderr.String())
require.Equal(t, fmt.Sprintf("Genesis foo bar 1234 %s\nUPGRADE \"chain2\" NEEDED at height: 49: {}\n", upgradeFile), stdout.String())
// ensure this is upgraded now and produces new output
currentBin, err = cfg.CurrentBin()
require.NoError(err)
require.NoError(t, err)
require.Equal(cfg.UpgradeBin("chain2"), currentBin)
rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2"))
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
args = []string{"second", "run", "--verbose"}
stdout.Reset()
stderr.Reset()
doUpgrade, err = launcher.Run(args, stdout, stderr)
require.NoError(err)
require.False(doUpgrade)
require.Equal("", stderr.String())
require.Equal("Chain 2 is live!\nArgs: second run --verbose\nFinished successfully\n", stdout.String())
doUpgrade, err = launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.False(t, doUpgrade)
require.Empty(t, stderr.String())
require.Equal(t, "Chain 2 is live!\nArgs: second run --verbose\nFinished successfully\n", stdout.String())
// ended without other upgrade
require.Equal(cfg.UpgradeBin("chain2"), currentBin)
rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2"))
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
}
func (s *processTestSuite) TestLaunchProcessWithRestartDelay() {
// TestPlanDisableRecase will test upgrades without lower case plan names
func TestPlanDisableRecase(t *testing.T) {
// binaries from testdata/validate directory
require := s.Require()
home := copyTestData(s.T(), "validate")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", RestartDelay: 5 * time.Second, PollInterval: 20, UnsafeSkipBackup: true}
logger := log.NewTestLogger(s.T()).With(log.ModuleKey, "cosmosvisor")
cfg := prepareConfig(
t,
fmt.Sprintf("%s/%s", workDir, "testdata/norecase"),
cosmovisor.Config{
Name: "dummyd",
PollInterval: 20,
UnsafeSkipBackup: true,
DisableRecase: true,
},
)
logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor")
// should run the genesis binary and produce expected output
stdin, _ := os.Open(os.DevNull)
stdout, stderr := newBuffer(), newBuffer()
currentBin, err := cfg.CurrentBin()
require.NoError(err)
require.Equal(cfg.GenesisBin(), currentBin)
require.NoError(t, err)
rPath, err := filepath.EvalSymlinks(cfg.GenesisBin())
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
launcher, err := cosmovisor.NewLauncher(logger, cfg)
require.NoError(err)
require.NoError(t, err)
upgradeFile := cfg.UpgradeInfoFilePath()
args := []string{"foo", "bar", "1234", upgradeFile}
doUpgrade, err := launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.True(t, doUpgrade)
require.Empty(t, stderr.String())
require.Equal(t, fmt.Sprintf("Genesis foo bar 1234 %s\nUPGRADE \"Chain2\" NEEDED at height: 49: {}\n", upgradeFile), stdout.String())
// ensure this is upgraded now and produces new output
currentBin, err = cfg.CurrentBin()
require.NoError(t, err)
rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("Chain2"))
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
args = []string{"second", "run", "--verbose"}
stdout.Reset()
stderr.Reset()
doUpgrade, err = launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.False(t, doUpgrade)
require.Empty(t, stderr.String())
require.Equal(t, "Chain 2 is live!\nArgs: second run --verbose\nFinished successfully\n", stdout.String())
// ended without other upgrade
rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("Chain2"))
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
}
func TestLaunchProcessWithRestartDelay(t *testing.T) {
// binaries from testdata/validate directory
cfg := prepareConfig(
t,
fmt.Sprintf("%s/%s", workDir, "testdata/validate"),
cosmovisor.Config{
Name: "dummyd",
RestartDelay: 5 * time.Second,
PollInterval: 20,
UnsafeSkipBackup: true,
},
)
logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor")
// should run the genesis binary and produce expected output
stdin, _ := os.Open(os.DevNull)
stdout, stderr := newBuffer(), newBuffer()
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
rPath, err := filepath.EvalSymlinks(cfg.GenesisBin())
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
launcher, err := cosmovisor.NewLauncher(logger, cfg)
require.NoError(t, err)
upgradeFile := cfg.UpgradeInfoFilePath()
start := time.Now()
doUpgrade, err := launcher.Run([]string{"foo", "bar", "1234", upgradeFile}, stdout, stderr)
require.NoError(err)
require.True(doUpgrade)
doUpgrade, err := launcher.Run([]string{"foo", "bar", "1234", upgradeFile}, stdin, stdout, stderr)
require.NoError(t, err)
require.True(t, doUpgrade)
// may not be the best way but the fastest way to check we meet the delay
// in addition to comparing both the runtime of this test and TestLaunchProcess in addition
if time.Since(start) < cfg.RestartDelay {
require.FailNow("restart delay not met")
require.FailNow(t, "restart delay not met")
}
}
// TestPlanShutdownGrace will test upgrades without lower case plan names
func TestPlanShutdownGrace(t *testing.T) {
// binaries from testdata/validate directory
cfg := prepareConfig(
t,
fmt.Sprintf("%s/%s", workDir, "testdata/dontdie"),
cosmovisor.Config{
Name: "dummyd",
PollInterval: 15,
UnsafeSkipBackup: true,
ShutdownGrace: 2 * time.Second,
},
)
logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmosvisor")
// should run the genesis binary and produce expected output
stdin, _ := os.Open(os.DevNull)
stdout, stderr := newBuffer(), newBuffer()
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
rPath, err := filepath.EvalSymlinks(cfg.GenesisBin())
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
launcher, err := cosmovisor.NewLauncher(logger, cfg)
require.NoError(t, err)
upgradeFile := cfg.UpgradeInfoFilePath()
args := []string{"foo", "bar", "1234", upgradeFile}
doUpgrade, err := launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.True(t, doUpgrade)
require.Empty(t, stderr.String())
require.Equal(t, fmt.Sprintf("Genesis foo bar 1234 %s\nUPGRADE \"Chain2\" NEEDED at height: 49: {}\nWARN Need Flush\nFlushed\n", upgradeFile), stdout.String())
// ensure this is upgraded now and produces new output
currentBin, err = cfg.CurrentBin()
require.NoError(t, err)
rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2"))
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
args = []string{"second", "run", "--verbose"}
stdout.Reset()
stderr.Reset()
doUpgrade, err = launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.False(t, doUpgrade)
require.Empty(t, stderr.String())
require.Equal(t, "Chain 2 is live!\nArgs: second run --verbose\nFinished successfully\n", stdout.String())
// ended without other upgrade
rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2"))
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
}
// TestLaunchProcess will try running the script a few times and watch upgrades work properly
// and args are passed through
func (s *processTestSuite) TestLaunchProcessWithDownloads() {
func TestLaunchProcessWithDownloads(t *testing.T) {
// test case upgrade path (binaries from testdata/download directory):
// genesis -> chain2-zip_bin
// chain2-zip_bin -> ref_to_chain3-zip_dir.json = (json for the next download instructions) -> chain3-zip_dir
// chain3-zip_dir - doesn't upgrade
require := s.Require()
home := copyTestData(s.T(), "download")
cfg := &cosmovisor.Config{Home: home, Name: "autod", AllowDownloadBinaries: true, PollInterval: 100, UnsafeSkipBackup: true}
logger := log.NewLogger(os.Stdout).With(log.ModuleKey, "cosmovisor")
cfg := prepareConfig(
t,
fmt.Sprintf("%s/%s", workDir, "testdata/download"),
cosmovisor.Config{
Name: "autod",
AllowDownloadBinaries: true,
PollInterval: 100,
UnsafeSkipBackup: true,
},
)
logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmovisor")
upgradeFilename := cfg.UpgradeInfoFilePath()
// should run the genesis binary and produce expected output
currentBin, err := cfg.CurrentBin()
require.NoError(err)
require.Equal(cfg.GenesisBin(), currentBin)
require.NoError(t, err)
rPath, err := filepath.EvalSymlinks(cfg.GenesisBin())
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
launcher, err := cosmovisor.NewLauncher(logger, cfg)
require.NoError(err)
require.NoError(t, err)
stdin, _ := os.Open(os.DevNull)
stdout, stderr := newBuffer(), newBuffer()
args := []string{"some", "args", upgradeFilename}
doUpgrade, err := launcher.Run(args, stdout, stderr)
require.NoError(err)
require.True(doUpgrade)
require.Equal("", stderr.String())
require.Equal("Genesis autod. Args: some args "+upgradeFilename+"\n"+`ERROR: UPGRADE "chain2" NEEDED at height: 49: zip_binary`+"\n", stdout.String())
doUpgrade, err := launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.True(t, doUpgrade)
require.Empty(t, stderr.String())
require.Equal(t, "Genesis autod. Args: some args "+upgradeFilename+"\n"+`ERROR: UPGRADE "chain2" NEEDED at height: 49: zip_binary`+"\n", stdout.String())
currentBin, err = cfg.CurrentBin()
require.NoError(err)
require.Equal(cfg.UpgradeBin("chain2"), currentBin)
require.NoError(t, err)
rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2"))
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
// start chain2
stdout.Reset()
stderr.Reset()
args = []string{"run", "--fast", upgradeFilename}
doUpgrade, err = launcher.Run(args, stdout, stderr)
require.NoError(err)
doUpgrade, err = launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.Equal("", stderr.String())
require.Equal("Chain 2 from zipped binary\nArgs: run --fast "+upgradeFilename+"\n"+`ERROR: UPGRADE "chain3" NEEDED at height: 936: ref_to_chain3-zip_dir.json module=main`+"\n", stdout.String())
require.Empty(t, stderr.String())
require.Equal(t, "Chain 2 from zipped binary\nArgs: run --fast "+upgradeFilename+"\n"+`ERROR: UPGRADE "chain3" NEEDED at height: 936: ref_to_chain3-zip_dir.json module=main`+"\n", stdout.String())
// ended with one more upgrade
require.True(doUpgrade)
require.True(t, doUpgrade)
currentBin, err = cfg.CurrentBin()
require.NoError(err)
require.Equal(cfg.UpgradeBin("chain3"), currentBin)
require.NoError(t, err)
rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain3"))
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
// run the last chain
args = []string{"end", "--halt", upgradeFilename}
stdout.Reset()
stderr.Reset()
doUpgrade, err = launcher.Run(args, stdout, stderr)
require.NoError(err)
require.False(doUpgrade)
require.Equal("", stderr.String())
require.Equal("Chain 3 from zipped directory\nArgs: end --halt "+upgradeFilename+"\n", stdout.String())
doUpgrade, err = launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.False(t, doUpgrade)
require.Empty(t, stderr.String())
require.Equal(t, "Chain 3 from zipped directory\nArgs: end --halt "+upgradeFilename+"\n", stdout.String())
// and this doesn't upgrade
currentBin, err = cfg.CurrentBin()
require.NoError(err)
require.Equal(cfg.UpgradeBin("chain3"), currentBin)
require.NoError(t, err)
rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain3"))
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
}
// TestLaunchProcessWithDownloadsAndMissingPreupgrade will try running the script a few times and watch upgrades work properly
// and args are passed through
func TestLaunchProcessWithDownloadsAndMissingPreupgrade(t *testing.T) {
// test case upgrade path (binaries from testdata/download directory):
// genesis -> chain2-zip_bin
// chain2-zip_bin -> ref_to_chain3-zip_dir.json = (json for the next download instructions) -> chain3-zip_dir
// chain3-zip_dir - doesn't upgrade
cfg := prepareConfig(
t,
fmt.Sprintf("%s/%s", workDir, "testdata/download"),
cosmovisor.Config{
Name: "autod",
AllowDownloadBinaries: true,
PollInterval: 100,
UnsafeSkipBackup: true,
CustomPreUpgrade: "missing.sh",
},
)
logger := log.NewTestLogger(t).With(log.ModuleKey, "cosmovisor")
upgradeFilename := cfg.UpgradeInfoFilePath()
// should run the genesis binary and produce expected output
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
rPath, err := filepath.EvalSymlinks(cfg.GenesisBin())
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
launcher, err := cosmovisor.NewLauncher(logger, cfg)
require.NoError(t, err)
// Missing Preupgrade Script
stdin, _ := os.Open(os.DevNull)
stdout, stderr := newBuffer(), newBuffer()
args := []string{"some", "args", upgradeFilename}
_, err = launcher.Run(args, stdin, stdout, stderr)
require.ErrorContains(t, err, "missing.sh")
require.ErrorIs(t, err, fs.ErrNotExist)
}
// TestLaunchProcessWithDownloadsAndPreupgrade will try running the script a few times and watch upgrades work properly
// and args are passed through
func TestLaunchProcessWithDownloadsAndPreupgrade(t *testing.T) {
// test case upgrade path (binaries from testdata/download directory):
// genesis -> chain2-zip_bin
// chain2-zip_bin -> ref_to_chain3-zip_dir.json = (json for the next download instructions) -> chain3-zip_dir
// chain3-zip_dir - doesn't upgrade
cfg := prepareConfig(
t,
fmt.Sprintf("%s/%s", workDir, "testdata/download"),
cosmovisor.Config{
Name: "autod",
AllowDownloadBinaries: true,
PollInterval: 100,
UnsafeSkipBackup: true,
CustomPreUpgrade: "preupgrade.sh",
},
)
buf := newBuffer() // inspect output using buf.String()
logger := log.NewLogger(buf).With(log.ModuleKey, "cosmovisor")
upgradeFilename := cfg.UpgradeInfoFilePath()
// should run the genesis binary and produce expected output
currentBin, err := cfg.CurrentBin()
require.NoError(t, err)
rPath, err := filepath.EvalSymlinks(cfg.GenesisBin())
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
launcher, err := cosmovisor.NewLauncher(logger, cfg)
require.NoError(t, err)
stdin, _ := os.Open(os.DevNull)
stdout, stderr := newBuffer(), newBuffer()
args := []string{"some", "args", upgradeFilename}
doUpgrade, err := launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.True(t, doUpgrade)
require.Empty(t, stderr.String())
require.Equal(t, "Genesis autod. Args: some args "+upgradeFilename+"\n"+`ERROR: UPGRADE "chain2" NEEDED at height: 49: zip_binary`+"\n", stdout.String())
currentBin, err = cfg.CurrentBin()
require.NoError(t, err)
rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain2"))
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
// should have preupgrade.sh results
require.FileExists(t, filepath.Join(cfg.Home, "upgrade_name_chain2_height_49"))
// start chain2
stdout.Reset()
stderr.Reset()
args = []string{"run", "--fast", upgradeFilename}
doUpgrade, err = launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.Empty(t, stderr.String())
require.Equal(t, "Chain 2 from zipped binary\nArgs: run --fast "+upgradeFilename+"\n"+`ERROR: UPGRADE "chain3" NEEDED at height: 936: ref_to_chain3-zip_dir.json module=main`+"\n", stdout.String())
// ended with one more upgrade
require.True(t, doUpgrade)
currentBin, err = cfg.CurrentBin()
require.NoError(t, err)
rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain3"))
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
// should have preupgrade.sh results
require.FileExists(t, filepath.Join(cfg.Home, "upgrade_name_chain3_height_936"))
// run the last chain
args = []string{"end", "--halt", upgradeFilename}
stdout.Reset()
stderr.Reset()
doUpgrade, err = launcher.Run(args, stdin, stdout, stderr)
require.NoError(t, err)
require.False(t, doUpgrade)
require.Empty(t, stderr.String())
require.Equal(t, "Chain 3 from zipped directory\nArgs: end --halt "+upgradeFilename+"\n", stdout.String())
// and this doesn't upgrade
currentBin, err = cfg.CurrentBin()
require.NoError(t, err)
rPath, err = filepath.EvalSymlinks(cfg.UpgradeBin("chain3"))
require.NoError(t, err)
require.Equal(t, rPath, currentBin)
}
// TestSkipUpgrade tests heights that are identified to be skipped and return if upgrade height matches the skip heights

View File

@ -5,69 +5,78 @@ import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
"github.com/rs/zerolog"
"cosmossdk.io/log"
upgradetypes "cosmossdk.io/x/upgrade/types"
)
var errUntestAble = errors.New("untestable")
type fileWatcher struct {
logger log.Logger
// full path to a watched file
filename string
interval time.Duration
daemonHome string
filename string // full path to a watched file
interval time.Duration
currentBin string
currentInfo upgradetypes.Plan
lastModTime time.Time
cancel chan bool
ticker *time.Ticker
needsUpdate bool
initialized bool
needsUpdate bool
initialized bool
disableRecase bool
}
func newUpgradeFileWatcher(logger log.Logger, filename string, interval time.Duration) (*fileWatcher, error) {
func newUpgradeFileWatcher(cfg *Config) (*fileWatcher, error) {
filename := cfg.UpgradeInfoFilePath()
if filename == "" {
return nil, errors.New("filename undefined")
}
filenameAbs, err := filepath.Abs(filename)
if err != nil {
return nil,
fmt.Errorf("invalid path; %s must be a valid file path: %w", filename, err)
return nil, fmt.Errorf("invalid path: %s must be a valid file path: %w", filename, err)
}
dirname := filepath.Dir(filename)
info, err := os.Stat(dirname)
if err != nil || !info.IsDir() {
return nil, fmt.Errorf("invalid path; %s must be an existing directory: %w", dirname, err)
if info, err := os.Stat(dirname); err != nil || !info.IsDir() {
return nil, fmt.Errorf("invalid path: %s must be an existing directory: %w", dirname, err)
}
bin, err := cfg.CurrentBin()
if err != nil {
return nil, fmt.Errorf("error creating symlink to genesis: %w", err)
}
return &fileWatcher{
logger: logger,
filename: filenameAbs,
interval: interval,
currentInfo: upgradetypes.Plan{},
lastModTime: time.Time{},
cancel: make(chan bool),
ticker: time.NewTicker(interval),
needsUpdate: false,
initialized: false,
daemonHome: cfg.Home,
currentBin: bin,
filename: filenameAbs,
interval: cfg.PollInterval,
currentInfo: upgradetypes.Plan{},
lastModTime: time.Time{},
cancel: make(chan bool),
ticker: time.NewTicker(cfg.PollInterval),
needsUpdate: false,
initialized: false,
disableRecase: cfg.DisableRecase,
}, nil
}
func (fw *fileWatcher) Stop() {
close(fw.cancel)
fw.ticker.Stop()
}
// pools the filesystem to check for new upgrade currentInfo. currentName is the name
// of currently running upgrade. The check is rejected if it finds an upgrade with the same
// name.
// MonitorUpdate pools the filesystem to check for new upgrade currentInfo.
// currentName is the name of currently running upgrade. The check is rejected if it finds
// an upgrade with the same name.
func (fw *fileWatcher) MonitorUpdate(currentUpgrade upgradetypes.Plan) <-chan struct{} {
fw.ticker.Reset(fw.interval)
done := make(chan struct{})
@ -102,18 +111,48 @@ func (fw *fileWatcher) CheckUpdate(currentUpgrade upgradetypes.Plan) bool {
stat, err := os.Stat(fw.filename)
if err != nil {
// file doesn't exists
if os.IsNotExist(err) {
return false
} else {
panic(fmt.Errorf("failed to stat upgrade info file: %w", err))
}
}
// check https://github.com/cosmos/cosmos-sdk/issues/21086
// If new file is still empty, wait a small amount of time for write to complete
if stat.Size() == 0 {
for range 10 {
time.Sleep(2 * time.Millisecond)
stat, err = os.Stat(fw.filename)
if err != nil {
if os.IsNotExist(err) {
return false
} else {
panic(fmt.Errorf("failed to stat upgrade info file: %w", err))
}
}
if stat.Size() == 0 {
break
}
}
}
if stat.Size() == 0 {
return false
}
// no update if the file already exists and has not been modified
if !stat.ModTime().After(fw.lastModTime) {
return false
}
info, err := parseUpgradeInfoFile(fw.filename)
info, err := parseUpgradeInfoFile(fw.filename, fw.disableRecase)
if err != nil {
zl := fw.logger.Impl().(*zerolog.Logger)
zl.Fatal().Err(err).Msg("failed to parse upgrade info file")
panic(fmt.Errorf("failed to parse upgrade info file: %w", err))
}
// file exist but too early in height
currentHeight, err := fw.checkHeight()
if (err != nil || currentHeight < info.Height) && !errors.Is(err, errUntestAble) { // ignore this check for tests
return false
}
@ -123,7 +162,7 @@ func (fw *fileWatcher) CheckUpdate(currentUpgrade upgradetypes.Plan) bool {
fw.currentInfo = info
fw.lastModTime = stat.ModTime()
// Heuristic: Deamon has restarted, so we don't know if we successfully
// Heuristic: Daemon has restarted, so we don't know if we successfully
// downloaded the upgrade or not. So we try to compare the running upgrade
// name (read from the cosmovisor file) with the upgrade info.
if !strings.EqualFold(currentUpgrade.Name, fw.currentInfo.Name) {
@ -142,27 +181,64 @@ func (fw *fileWatcher) CheckUpdate(currentUpgrade upgradetypes.Plan) bool {
return false
}
func parseUpgradeInfoFile(filename string) (upgradetypes.Plan, error) {
var ui upgradetypes.Plan
// checkHeight checks if the current block height
func (fw *fileWatcher) checkHeight() (int64, error) {
if testing.Testing() { // we cannot test the command in the test environment
return 0, errUntestAble
}
f, err := os.Open(filename)
result, err := exec.Command(fw.currentBin, "status", "--home", fw.daemonHome).CombinedOutput() //nolint:gosec // we want to execute the status command
if err != nil {
return 0, err
}
type response struct {
SyncInfo struct {
LatestBlockHeight string `json:"latest_block_height"`
} `json:"sync_info"`
AnotherCasingSyncInfo struct {
LatestBlockHeight string `json:"latest_block_height"`
} `json:"SyncInfo"`
}
var resp response
if err := json.Unmarshal(result, &resp); err != nil {
return 0, err
}
if resp.SyncInfo.LatestBlockHeight != "" {
return strconv.ParseInt(resp.SyncInfo.LatestBlockHeight, 10, 64)
} else if resp.AnotherCasingSyncInfo.LatestBlockHeight != "" {
return strconv.ParseInt(resp.AnotherCasingSyncInfo.LatestBlockHeight, 10, 64)
}
return 0, errors.New("latest block height is empty")
}
func parseUpgradeInfoFile(filename string, disableRecase bool) (upgradetypes.Plan, error) {
f, err := os.ReadFile(filename)
if err != nil {
return upgradetypes.Plan{}, err
}
defer f.Close()
d := json.NewDecoder(f)
if err := d.Decode(&ui); err != nil {
if len(f) == 0 {
return upgradetypes.Plan{}, fmt.Errorf("empty upgrade-info.json in %q", filename)
}
var upgradePlan upgradetypes.Plan
if err := json.Unmarshal(f, &upgradePlan); err != nil {
return upgradetypes.Plan{}, err
}
// required values must be set
if ui.Height <= 0 || ui.Name == "" {
return upgradetypes.Plan{}, fmt.Errorf("invalid upgrade-info.json content; name and height must be not empty; got: %v", ui)
if err := upgradePlan.ValidateBasic(); err != nil {
return upgradetypes.Plan{}, fmt.Errorf("invalid upgrade-info.json content: %w, got: %v", err, upgradePlan)
}
// normalize name to prevent operator error in upgrade name case sensitivity errors.
ui.Name = strings.ToLower(ui.Name)
if !disableRecase {
upgradePlan.Name = strings.ToLower(upgradePlan.Name)
}
return ui, err
return upgradePlan, nil
}

View File

@ -13,52 +13,65 @@ func TestParseUpgradeInfoFile(t *testing.T) {
cases := []struct {
filename string
expectUpgrade upgradetypes.Plan
expectErr bool
disableRecase bool
expectErr string
}{
{
filename: "f1-good.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{Name: "upgrade1", Info: "some info", Height: 123},
expectErr: false,
},
{
filename: "f2-normalized-name.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{Name: "upgrade2", Info: "some info", Height: 125},
expectErr: false,
},
{
filename: "f2-normalized-name.json",
disableRecase: true,
expectUpgrade: upgradetypes.Plan{Name: "Upgrade2", Info: "some info", Height: 125},
},
{
filename: "f2-bad-type.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
expectErr: "cannot unmarshal number into Go struct",
},
{
filename: "f2-bad-type-2.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
expectErr: "height must be greater than 0: invalid request",
},
{
filename: "f3-empty.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
expectErr: "empty upgrade-info.json in",
},
{
filename: "f4-empty-obj.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
expectErr: "invalid upgrade-info.json content: name cannot be empty",
},
{
filename: "f5-partial-obj-1.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
expectErr: "height must be greater than 0",
},
{
filename: "f5-partial-obj-2.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
expectErr: "name cannot be empty: invalid request",
},
{
filename: "unknown.json",
filename: "non-existent.json",
disableRecase: false,
expectUpgrade: upgradetypes.Plan{},
expectErr: true,
expectErr: "no such file or directory",
},
}
@ -66,9 +79,10 @@ func TestParseUpgradeInfoFile(t *testing.T) {
tc := cases[i]
t.Run(tc.filename, func(t *testing.T) {
require := require.New(t)
ui, err := parseUpgradeInfoFile(filepath.Join(".", "testdata", "upgrade-files", tc.filename))
if tc.expectErr {
ui, err := parseUpgradeInfoFile(filepath.Join(".", "testdata", "upgrade-files", tc.filename), tc.disableRecase)
if tc.expectErr != "" {
require.Error(err)
require.Contains(err.Error(), tc.expectErr)
} else {
require.NoError(err)
require.Equal(tc.expectUpgrade, ui)

View File

@ -0,0 +1,20 @@
#!/bin/sh
warn() {
echo "WARN Need Flush"
}
trap warn INT
echo Genesis $@
sleep 1
test -z $4 && exit 1001
echo 'UPGRADE "Chain2" NEEDED at height: 49: {}'
echo '{"name":"Chain2","height":49,"info":""}' > $4
# Shutdown grace test waits 2 seconds for flush
# Flush within 1 second
sleep 1
echo 'Flushed'
# Now chain is halted for shutdown grace test.
sleep 2
echo Did not kill in time. Never should be printed!!!

View File

@ -0,0 +1,6 @@
#!/bin/sh
echo Chain 2 is live!
echo Args: $@
sleep 1
echo Finished successfully

View File

View File

@ -6,7 +6,13 @@ echo 'ERROR: UPGRADE "chain2" NEEDED at height: 49: zip_binary'
# create upgrade info
# this info contains directly information about binaries (in chain2->chain3 update we test with info containing a link to the file with an address for the new chain binary)
echo '{"name":"chain2","height":49,"info":"{\"binaries\":{\"linux/amd64\":\"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain2-zip_bin/autod.zip?checksum=sha256:13767eb0b57bf51a0f43d49f6277d5df97d4dec672dc39822d23a82fb8e70a7b\"}}"}' >$3
cat > "$3" <<EOL
{
"name": "chain2",
"height": 49,
"info": "{\"binaries\":{\"linux/amd64\": \"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain2-zip_bin/autod.zip?checksum=sha256:e84db844e123e3bb888c9ec52f6768ae29fbef7c17eaf8fc7431485a65dae6b9\",\"linux/arm64\": \"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain2-zip_bin/autod.zip?checksum=sha256:e84db844e123e3bb888c9ec52f6768ae29fbef7c17eaf8fc7431485a65dae6b9\",\"darwin/amd64\": \"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain2-zip_bin/autod.zip?checksum=sha256:e84db844e123e3bb888c9ec52f6768ae29fbef7c17eaf8fc7431485a65dae6b9\",\"darwin/arm64\": \"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain2-zip_bin/autod.zip?checksum=sha256:e84db844e123e3bb888c9ec52f6768ae29fbef7c17eaf8fc7431485a65dae6b9\"}}"
}
EOL
sleep 0.1
echo Never should be printed!!!

View File

@ -0,0 +1,4 @@
#!/bin/sh
echo "OK" > upgrade_name_$1_height_$2
echo PWD=`pwd`
echo upgrade_name_$1_height_$2

View File

@ -0,0 +1,9 @@
#!/bin/sh
echo Genesis $@
sleep 1
test -z $4 && exit 1001
echo 'UPGRADE "Chain2" NEEDED at height: 49: {}'
echo '{"name":"Chain2","height":49,"info":""}' > $4
sleep 2
echo Never should be printed!!!

View File

@ -0,0 +1,6 @@
#!/bin/sh
echo Chain 2 is live!
echo Args: $@
sleep 1
echo Finished successfully

View File

View File

@ -6,8 +6,13 @@ echo Args: $@
echo 'ERROR: UPGRADE "chain3" NEEDED at height: 936: ref_to_chain3-zip_dir.json module=main'
# this update info doesn't contain binaries, instead it is a reference for further download instructions.
# echo '{"name":"chain3","height":936,"info":"{\"binaries\":{\"linux/amd64\":\"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/ref_to_chain3-zip_dir.json?checksum=sha256:a95075f4dd83bc9f0f556ef73e64ce000f9bf3a6beeb9d4ae32f594b1417ef7a\"}}"}' > $3
echo '{"name":"chain3","height":936,"info":"https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/ref_to_chain3-zip_dir.json?checksum=sha256:a95075f4dd83bc9f0f556ef73e64ce000f9bf3a6beeb9d4ae32f594b1417ef7a"}' >$3
cat > "$3" <<EOL
{
"name": "chain3",
"height": 936,
"info": "https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/ref_to_chain3-zip_dir.json?checksum=sha256:6758973f7404f6d34381029931b85826fc7a6315584ede03bad4c19e9b787f6c"
}
EOL
sleep 1
echo 'Do not print'

View File

@ -1,5 +1,8 @@
{
"binaries": {
"linux/amd64": "https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4"
"linux/amd64": "https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4",
"linux/arm64": "https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4",
"darwin/amd64": "https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4",
"darwin/arm64": "https://github.com/cosmos/cosmos-sdk/raw/main/tools/cosmovisor/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4"
}
}

View File

@ -1 +0,0 @@

View File

@ -39,15 +39,11 @@ func UpgradeBinary(logger log.Logger, cfg *Config, p upgradetypes.Plan) error {
return fmt.Errorf("unhandled error: %w", err)
}
upgradeInfo, err := plan.ParseInfo(p.Info)
upgradeInfo, err := plan.ParseInfo(p.Info, plan.ParseOptionEnforceChecksum(cfg.DownloadMustHaveChecksum))
if err != nil {
return fmt.Errorf("cannot parse upgrade info: %w", err)
}
if err := upgradeInfo.ValidateFull(cfg.Name); err != nil {
return fmt.Errorf("invalid binaries: %w", err)
}
url, err := GetBinaryURL(upgradeInfo.Binaries)
if err != nil {
return err

View File

@ -1,5 +1,4 @@
//go:build linux
// +build linux
//go:build darwin || linux
package cosmovisor_test
@ -7,6 +6,7 @@ import (
"fmt"
"os"
"path/filepath"
"slices"
"strings"
"testing"
@ -28,13 +28,21 @@ func TestUpgradeTestSuite(t *testing.T) {
}
func (s *upgradeTestSuite) TestCurrentBin() {
home := copyTestData(s.T(), "validate")
cfg := cosmovisor.Config{Home: home, Name: "dummyd"}
cfg := prepareConfig(
s.T(),
fmt.Sprintf("%s/%s", workDir, "testdata/validate"),
cosmovisor.Config{
Name: "dummyd",
},
)
currentBin, err := cfg.CurrentBin()
s.Require().NoError(err)
s.Require().Equal(cfg.GenesisBin(), currentBin)
rPath, err := filepath.EvalSymlinks(cfg.GenesisBin())
s.Require().NoError(err)
s.Require().Equal(rPath, currentBin)
// ensure we cannot set this to an invalid value
for _, name := range []string{"missing", "nobin"} {
@ -43,7 +51,10 @@ func (s *upgradeTestSuite) TestCurrentBin() {
currentBin, err := cfg.CurrentBin()
s.Require().NoError(err)
s.Require().Equal(cfg.GenesisBin(), currentBin, name)
rPath, err := filepath.EvalSymlinks(cfg.GenesisBin())
s.Require().NoError(err)
s.Require().Equal(rPath, currentBin, name)
}
// try a few times to make sure this can be reproduced
@ -55,29 +66,46 @@ func (s *upgradeTestSuite) TestCurrentBin() {
currentBin, err := cfg.CurrentBin()
s.Require().NoError(err)
s.Require().Equal(cfg.UpgradeBin(name), currentBin)
rPath, err := filepath.EvalSymlinks(cfg.UpgradeBin(name))
s.Require().NoError(err)
s.Require().Equal(rPath, currentBin)
}
}
func (s *upgradeTestSuite) TestCurrentAlwaysSymlinkToDirectory() {
home := copyTestData(s.T(), "validate")
cfg := cosmovisor.Config{Home: home, Name: "dummyd"}
cfg := prepareConfig(
s.T(),
fmt.Sprintf("%s/%s", workDir, "testdata/validate"),
cosmovisor.Config{
Name: "dummyd",
},
)
currentBin, err := cfg.CurrentBin()
s.Require().NoError(err)
s.Require().Equal(cfg.GenesisBin(), currentBin)
rPath, err := filepath.EvalSymlinks(cfg.GenesisBin())
s.Require().NoError(err)
s.Require().Equal(rPath, currentBin)
s.assertCurrentLink(cfg, "genesis")
err = cfg.SetCurrentUpgrade(upgradetypes.Plan{Name: "chain2"})
s.Require().NoError(err)
currentBin, err = cfg.CurrentBin()
s.Require().NoError(err)
s.Require().Equal(cfg.UpgradeBin("chain2"), currentBin)
eval, err := filepath.EvalSymlinks(cfg.UpgradeBin("chain2"))
s.Require().NoError(err)
s.Require().Equal(eval, currentBin)
s.assertCurrentLink(cfg, filepath.Join("upgrades", "chain2"))
}
func (s *upgradeTestSuite) assertCurrentLink(cfg cosmovisor.Config, target string) {
func (s *upgradeTestSuite) assertCurrentLink(cfg *cosmovisor.Config, target string) {
link := filepath.Join(cfg.Root(), "current")
// ensure this is a symlink
info, err := os.Lstat(link)
s.Require().NoError(err)
@ -85,20 +113,29 @@ func (s *upgradeTestSuite) assertCurrentLink(cfg cosmovisor.Config, target strin
dest, err := os.Readlink(link)
s.Require().NoError(err)
expected := filepath.Join(cfg.Root(), target)
s.Require().Equal(expected, dest)
s.Require().Equal(target, dest)
}
// TODO: test with download (and test all download functions)
func (s *upgradeTestSuite) TestUpgradeBinaryNoDownloadUrl() {
home := copyTestData(s.T(), "validate")
cfg := &cosmovisor.Config{Home: home, Name: "dummyd", AllowDownloadBinaries: true}
cfg := prepareConfig(
s.T(),
fmt.Sprintf("%s/%s", workDir, "testdata/validate"),
cosmovisor.Config{
Name: "dummyd",
AllowDownloadBinaries: true,
},
)
logger := log.NewLogger(os.Stdout).With(log.ModuleKey, "cosmovisor")
currentBin, err := cfg.CurrentBin()
s.Require().NoError(err)
s.Require().Equal(cfg.GenesisBin(), currentBin)
rPath, err := filepath.EvalSymlinks(cfg.GenesisBin())
s.Require().NoError(err)
s.Require().Equal(rPath, currentBin)
// do upgrade ignores bad files
for _, name := range []string{"missing", "nobin"} {
@ -107,7 +144,11 @@ func (s *upgradeTestSuite) TestUpgradeBinaryNoDownloadUrl() {
s.Require().Error(err, name)
currentBin, err := cfg.CurrentBin()
s.Require().NoError(err)
s.Require().Equal(cfg.GenesisBin(), currentBin, name)
rPath, err := filepath.EvalSymlinks(cfg.GenesisBin())
s.Require().NoError(err)
s.Require().Equal(rPath, currentBin, name)
}
// make sure it updates a few times
@ -121,7 +162,10 @@ func (s *upgradeTestSuite) TestUpgradeBinaryNoDownloadUrl() {
currentBin, err := cfg.CurrentBin()
s.Require().NoError(err)
s.Require().Equal(upgradeBin, currentBin)
rPath, err := filepath.EvalSymlinks(upgradeBin)
s.Require().NoError(err)
s.Require().Equal(rPath, currentBin)
}
}
@ -135,26 +179,25 @@ func (s *upgradeTestSuite) TestUpgradeBinary() {
}{
"get raw binary with checksum": {
// sha256sum ./testdata/repo/raw_binary/autod
url: "./testdata/repo/raw_binary/autod?checksum=sha256:e6bc7851600a2a9917f7bf88eb7bdee1ec162c671101485690b4deb089077b0d",
url: workDir + "/testdata/repo/raw_binary/autod?checksum=sha256:e6bc7851600a2a9917f7bf88eb7bdee1ec162c671101485690b4deb089077b0d",
canDownload: true,
validBinary: true,
},
"get raw binary with invalid checksum": {
url: "./testdata/repo/raw_binary/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906",
url: workDir + "/testdata/repo/raw_binary/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906",
canDownload: false,
},
"get zipped directory with valid checksum": {
// sha256sum ./testdata/repo/chain3-zip_dir/autod.zip
url: "./testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4",
url: workDir + "/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:8951f52a0aea8617de0ae459a20daf704c29d259c425e60d520e363df0f166b4",
canDownload: true,
validBinary: true,
},
"get zipped directory with invalid checksum": {
url: "./testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906",
url: workDir + "/testdata/repo/chain3-zip_dir/autod.zip?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906",
canDownload: false,
},
"invalid url": {
url: "./testdata/repo/bad_dir/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906",
url: workDir + "/testdata/repo/bad_dir/autod?checksum=sha256:73e2bd6cbb99261733caf137015d5cc58e3f96248d8b01da68be8564989dd906",
canDownload: false,
},
"valid remote": {
@ -167,14 +210,14 @@ func (s *upgradeTestSuite) TestUpgradeBinary() {
for label, tc := range cases {
s.Run(label, func() {
var err error
// make temp dir
home := copyTestData(s.T(), "download")
cfg := &cosmovisor.Config{
Home: home,
Name: "autod",
AllowDownloadBinaries: true,
}
cfg := prepareConfig(
s.T(),
fmt.Sprintf("%s/%s", workDir, "testdata/download"),
cosmovisor.Config{
Name: "autod",
AllowDownloadBinaries: true,
},
)
url := tc.url
if strings.HasPrefix(url, "./") {
@ -198,17 +241,37 @@ func (s *upgradeTestSuite) TestUpgradeBinary() {
}
func (s *upgradeTestSuite) TestOsArch() {
// all download tests will fail if we are not on linux...
s.Require().Equal("linux/amd64", cosmovisor.OSArch())
// all download tests will fail if we are not on linux or darwin...
hosts := []string{
"linux/arm64",
"linux/amd64",
"darwin/amd64",
"darwin/arm64",
}
s.Require().True(slices.Contains(hosts, cosmovisor.OSArch()))
}
// copyTestData will make a tempdir and then
// "cp -r" a subdirectory under testdata there
// returns the directory (which can now be used as Config.Home) and modified safely
func copyTestData(t *testing.T, subdir string) string {
func copyTestData(t *testing.T, testData string) string {
t.Helper()
tmpdir := t.TempDir()
require.NoError(t, copy.Copy(filepath.Join("testdata", subdir), tmpdir))
require.NoError(t, copy.Copy(testData, tmpdir))
return tmpdir
}
func prepareConfig(t *testing.T, testData string, config cosmovisor.Config) *cosmovisor.Config {
t.Helper()
tmpdir := copyTestData(t, testData)
config.Home = tmpdir
err := os.Chdir(config.Root())
require.NoError(t, err)
return &config
}

View File

@ -1 +0,0 @@
/hubl

View File

@ -1,32 +0,0 @@
<!--
Guiding Principles:
Changelogs are for humans, not machines.
There should be an entry for every single version.
The same types of changes should be grouped.
Versions and sections should be linkable.
The latest version comes first.
The release date of each version is displayed.
Mention whether you follow Semantic Versioning.
Usage:
Change log entries are to be added to the Unreleased section under the
appropriate stanza (see below). Each entry should ideally include a tag and
the Github issue reference in the following format:
* (<tag>) [#<issue-number>] Changelog message.
Types of changes (Stanzas):
"Features" for new features.
"Improvements" for changes in existing functionality.
"Deprecated" for soon-to-be removed features.
"Bug Fixes" for any bug fixes.
"API Breaking" for breaking exported APIs used by developers building on SDK.
Ref: https://keepachangelog.com/en/1.0.0/
-->
# Changelog
## [Unreleased]

View File

@ -1,11 +0,0 @@
#!/usr/bin/make -f
all: hubl test
hubl:
go build -mod=readonly ./cmd/hubl
test:
go test -mod=readonly -race ./...
.PHONY: all hubl test

View File

@ -1,73 +0,0 @@
---
sidebar_position: 1
---
# Hubl
`Hubl` is a tool that allows you to query any Cosmos SDK based blockchain.
It takes advantage of the new [AutoCLI](https://pkg.go.dev/github.com/cosmos/cosmos-sdk/client/v2@v2.0.0-20220916140313-c5245716b516/cli) feature <!-- TODO replace with AutoCLI docs --> of the Cosmos SDK.
## Installation
Hubl can be installed using `go install`:
```shell
go install cosmossdk.io/tools/hubl/cmd/hubl@latest
```
Or build from source:
```shell
git clone --depth=1 https://github.com/cosmos/cosmos-sdk
make hubl
```
The binary will be located in `tools/hubl`.
## Usage
```shell
hubl --help
```
### Add chain
To configure a new chain just run this command using the --init flag and the name of the chain as it's listed in the chain registry (<https://github.com/cosmos/chain-registry>).
If the chain is not listed in the chain registry, you can use any unique name.
```shell
hubl init [chain-name]
hubl init regen
```
The chain configuration is stored in `~/.hubl/config.toml`.
:::tip
When using an unsecure gRPC endpoint, change the `insecure` field to `true` in the config file.
```toml
[chains]
[chains.regen]
[[chains.regen.trusted-grpc-endpoints]]
endpoint = 'localhost:9090'
insecure = true
```
Or use the `--insecure` flag:
```shell
hubl init regen --insecure
```
:::
### Query
To query a chain, you can use the `query` command.
Then specify which module you want to query and the query itself.
```shell
hubl regen query auth module-accounts
```

View File

@ -1,16 +0,0 @@
package main
import (
"cosmossdk.io/tools/hubl/internal"
)
func main() {
cmd, err := internal.RootCommand()
if err != nil {
panic(err)
}
if err = cmd.Execute(); err != nil {
panic(err)
}
}

View File

@ -1,156 +0,0 @@
module cosmossdk.io/tools/hubl
go 1.23
require (
cosmossdk.io/api v0.7.5
cosmossdk.io/client/v2 v2.0.0-20230719143845-dff6b0e26aa4
cosmossdk.io/errors v1.0.1
github.com/cockroachdb/errors v1.11.1
github.com/cosmos/cosmos-sdk v0.50.0-rc.1
github.com/manifoldco/promptui v0.9.0
github.com/pelletier/go-toml/v2 v2.1.0
github.com/spf13/cobra v1.8.0
google.golang.org/grpc v1.63.2
google.golang.org/protobuf v1.34.2
)
require (
cosmossdk.io/collections v0.4.0 // indirect
cosmossdk.io/core v0.11.0 // indirect
cosmossdk.io/depinject v1.0.0-alpha.4 // indirect
cosmossdk.io/log v1.4.1 // indirect
cosmossdk.io/math v1.3.0 // indirect
cosmossdk.io/store v1.0.0 // indirect
cosmossdk.io/x/tx v0.13.4 // indirect
filippo.io/edwards25519 v1.0.0 // indirect
github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 // indirect
github.com/99designs/keyring v1.2.1 // indirect
github.com/DataDog/zstd v1.5.5 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bgentry/speakeasy v0.1.1-0.20220910012023-760eaf8b6816 // indirect
github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/cespare/xxhash v1.1.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/chzyer/readline v1.5.1 // indirect
github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect
github.com/cockroachdb/pebble v1.1.0 // indirect
github.com/cockroachdb/redact v1.1.5 // indirect
github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect
github.com/cometbft/cometbft v0.38.11 // indirect
github.com/cometbft/cometbft-db v0.9.1 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/cosmos-db v1.0.2 // indirect
github.com/cosmos/cosmos-proto v1.0.0-beta.5 // indirect
github.com/cosmos/go-bip39 v1.0.0 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/gogoproto v1.7.0 // indirect
github.com/cosmos/iavl v1.0.1 // indirect
github.com/cosmos/ics23/go v0.11.0 // indirect
github.com/cosmos/ledger-cosmos-go v0.13.3 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect
github.com/dgraph-io/badger/v2 v2.2007.4 // indirect
github.com/dgraph-io/ristretto v0.1.1 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/emicklei/dot v1.6.1 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/getsentry/sentry-go v0.27.0 // indirect
github.com/go-kit/kit v0.12.0 // indirect
github.com/go-kit/log v0.2.1 // indirect
github.com/go-logfmt/logfmt v0.6.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/googleapis v1.4.1 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/glog v1.2.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/orderedcode v0.0.1 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/hashicorp/go-hclog v1.5.0 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-metrics v0.5.1 // indirect
github.com/hashicorp/go-plugin v1.5.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/yamux v0.1.1 // indirect
github.com/hdevalence/ed25519consensus v0.1.0 // indirect
github.com/huandu/skiplist v1.2.0 // indirect
github.com/iancoleman/strcase v0.3.0 // indirect
github.com/improbable-eng/grpc-web v0.15.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jmhodges/levigo v1.0.0 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lib/pq v1.10.7 // indirect
github.com/linxGnu/grocksdb v1.8.12 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/highwayhash v1.0.2 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/oasisprotocol/curve25519-voi v0.0.0-20230904125328-1f23a7beb09a // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/petermattis/goid v0.0.0-20230904192822-1876fd5063bc // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.18.0 // indirect
github.com/prometheus/client_model v0.6.0 // indirect
github.com/prometheus/common v0.47.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/rs/cors v1.8.3 // indirect
github.com/rs/zerolog v1.33.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.18.2 // indirect
github.com/stretchr/testify v1.9.0 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/syndtr/goleveldb v1.0.1-0.20220721030215-126854af5e6d // indirect
github.com/tendermint/go-amino v0.16.0 // indirect
github.com/tidwall/btree v1.7.0 // indirect
github.com/zondax/hid v0.9.2 // indirect
github.com/zondax/ledger-go v0.14.3 // indirect
go.etcd.io/bbolt v1.3.8 // indirect
go.uber.org/multierr v1.10.0 // indirect
golang.org/x/crypto v0.26.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect
golang.org/x/net v0.24.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/term v0.23.0 // indirect
golang.org/x/text v0.17.0 // indirect
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240227224415-6ceb2ff114de // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240401170217-c3f982113cda // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
gotest.tools/v3 v3.5.1 // indirect
nhooyr.io/websocket v1.8.6 // indirect
pgregory.net/rapid v1.1.0 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect
)

File diff suppressed because it is too large Load Diff

View File

@ -1,280 +0,0 @@
package internal
import (
"context"
"fmt"
"io"
"strings"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection/grpc_reflection_v1"
"google.golang.org/protobuf/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
reflectionv1beta1 "cosmossdk.io/api/cosmos/base/reflection/v1beta1"
)
// loadFileDescriptorsGRPCReflection attempts to load the file descriptor set using gRPC reflection when cosmos.reflection.v1
// is unavailable.
func loadFileDescriptorsGRPCReflection(ctx context.Context, client *grpc.ClientConn) (*descriptorpb.FileDescriptorSet, error) {
fmt.Printf("This chain does not support cosmos.reflection.v1 yet... attempting to use a fallback. Some features may be unsupported and it may not be possible to read all data.\n")
var interfaceImplNames []string
cosmosReflectBetaClient := reflectionv1beta1.NewReflectionServiceClient(client)
interfacesRes, err := cosmosReflectBetaClient.ListAllInterfaces(ctx, &reflectionv1beta1.ListAllInterfacesRequest{})
if err == nil {
for _, iface := range interfacesRes.InterfaceNames {
implRes, err := cosmosReflectBetaClient.ListImplementations(ctx, &reflectionv1beta1.ListImplementationsRequest{
InterfaceName: iface,
})
if err == nil {
interfaceImplNames = append(interfaceImplNames, implRes.ImplementationMessageNames...)
}
}
}
reflectClient, err := grpc_reflection_v1.NewServerReflectionClient(client).ServerReflectionInfo(ctx)
if err != nil {
return nil, err
}
fdMap := map[string]*descriptorpb.FileDescriptorProto{}
waitListServiceRes := make(chan *grpc_reflection_v1.ListServiceResponse)
waitc := make(chan struct{})
go func() {
for {
in, err := reflectClient.Recv()
if err == io.EOF {
// read done.
close(waitc)
return
}
if err != nil {
panic(err)
}
switch res := in.MessageResponse.(type) {
case *grpc_reflection_v1.ServerReflectionResponse_ListServicesResponse:
waitListServiceRes <- res.ListServicesResponse
case *grpc_reflection_v1.ServerReflectionResponse_FileDescriptorResponse:
processFileDescriptorsResponse(res, fdMap)
}
}
}()
if err = reflectClient.Send(&grpc_reflection_v1.ServerReflectionRequest{
MessageRequest: &grpc_reflection_v1.ServerReflectionRequest_ListServices{},
}); err != nil {
return nil, err
}
listServiceRes := <-waitListServiceRes
for _, response := range listServiceRes.Service {
err = reflectClient.Send(&grpc_reflection_v1.ServerReflectionRequest{
MessageRequest: &grpc_reflection_v1.ServerReflectionRequest_FileContainingSymbol{
FileContainingSymbol: response.Name,
},
})
if err != nil {
return nil, err
}
}
for _, msgName := range interfaceImplNames {
err = reflectClient.Send(&grpc_reflection_v1.ServerReflectionRequest{
MessageRequest: &grpc_reflection_v1.ServerReflectionRequest_FileContainingSymbol{
FileContainingSymbol: msgName,
},
})
if err != nil {
return nil, err
}
}
if err = reflectClient.CloseSend(); err != nil {
return nil, err
}
<-waitc
// we loop through all the file descriptor dependencies to capture any file descriptors we haven't loaded yet
cantFind := map[string]bool{}
for {
missing := missingFileDescriptors(fdMap, cantFind)
if len(missing) == 0 {
break
}
err = addMissingFileDescriptors(ctx, client, fdMap, missing)
if err != nil {
return nil, err
}
// mark all deps that we aren't able to resolve as can't find, so we don't keep looping and get a 429 error
for _, dep := range missing {
if fdMap[dep] == nil {
cantFind[dep] = true
}
}
}
for dep := range cantFind {
fmt.Printf("Warning: can't find %s.\n", dep)
}
fdSet := &descriptorpb.FileDescriptorSet{}
for _, descriptorProto := range fdMap {
fdSet.File = append(fdSet.File, descriptorProto)
}
return fdSet, nil
}
func processFileDescriptorsResponse(res *grpc_reflection_v1.ServerReflectionResponse_FileDescriptorResponse, fdMap map[string]*descriptorpb.FileDescriptorProto) {
for _, bz := range res.FileDescriptorResponse.FileDescriptorProto {
fd := &descriptorpb.FileDescriptorProto{}
err := proto.Unmarshal(bz, fd)
if err != nil {
panic(err)
}
fdMap[fd.GetName()] = fd
}
}
func missingFileDescriptors(fdMap map[string]*descriptorpb.FileDescriptorProto, cantFind map[string]bool) []string {
var missing []string
for _, descriptorProto := range fdMap {
for _, dep := range descriptorProto.Dependency {
if fdMap[dep] == nil && !cantFind[dep] /* skip deps we've marked as can't find */ {
missing = append(missing, dep)
}
}
}
return missing
}
func addMissingFileDescriptors(ctx context.Context, client *grpc.ClientConn, fdMap map[string]*descriptorpb.FileDescriptorProto, missingFiles []string) error {
reflectClient, err := grpc_reflection_v1.NewServerReflectionClient(client).ServerReflectionInfo(ctx)
if err != nil {
return err
}
waitc := make(chan struct{})
go func() {
for {
in, err := reflectClient.Recv()
if err == io.EOF {
// read done.
close(waitc)
return
}
if err != nil {
panic(err)
}
if res, ok := in.MessageResponse.(*grpc_reflection_v1.ServerReflectionResponse_FileDescriptorResponse); ok {
processFileDescriptorsResponse(res, fdMap)
}
}
}()
for _, file := range missingFiles {
err = reflectClient.Send(&grpc_reflection_v1.ServerReflectionRequest{
MessageRequest: &grpc_reflection_v1.ServerReflectionRequest_FileByFilename{
FileByFilename: file,
},
})
if err != nil {
return err
}
}
err = reflectClient.CloseSend()
if err != nil {
return err
}
<-waitc
return nil
}
func guessAutocli(files *protoregistry.Files) *autocliv1.AppOptionsResponse {
fmt.Printf("This chain does not support autocli directly yet. Using some default mappings in the meantime to support a subset of the available services.\n")
res := map[string]*autocliv1.ModuleOptions{}
files.RangeFiles(func(descriptor protoreflect.FileDescriptor) bool {
services := descriptor.Services()
n := services.Len()
for i := 0; i < n; i++ {
service := services.Get(i)
serviceName := service.FullName()
mapping, ok := defaultAutocliMappings[serviceName]
if ok {
parts := strings.Split(mapping, " ")
numParts := len(parts)
if numParts < 2 || numParts > 3 {
fmt.Printf("Warning: bad mapping %q found for %q\n", mapping, serviceName)
continue
}
modOpts := res[parts[0]]
if modOpts == nil {
modOpts = &autocliv1.ModuleOptions{}
res[parts[0]] = modOpts
}
switch parts[1] {
case "query":
if modOpts.Query == nil {
modOpts.Query = &autocliv1.ServiceCommandDescriptor{
SubCommands: map[string]*autocliv1.ServiceCommandDescriptor{},
}
}
if numParts == 3 {
modOpts.Query.SubCommands[parts[2]] = &autocliv1.ServiceCommandDescriptor{Service: string(serviceName)}
} else {
modOpts.Query.Service = string(serviceName)
}
case "tx":
if modOpts.Tx == nil {
modOpts.Tx = &autocliv1.ServiceCommandDescriptor{
SubCommands: map[string]*autocliv1.ServiceCommandDescriptor{},
}
}
if numParts == 3 {
modOpts.Tx.SubCommands[parts[2]] = &autocliv1.ServiceCommandDescriptor{Service: string(serviceName)}
} else {
modOpts.Tx.Service = string(serviceName)
}
default:
fmt.Printf("Warning: bad mapping %q found for %q\n", mapping, serviceName)
continue
}
}
}
return true
})
return &autocliv1.AppOptionsResponse{ModuleOptions: res}
}
var defaultAutocliMappings = map[protoreflect.FullName]string{
"cosmos.auth.v1beta1.Query": "auth query",
"cosmos.authz.v1beta1.Query": "authz query",
"cosmos.bank.v1beta1.Query": "bank query",
"cosmos.distribution.v1beta1.Query": "distribution query",
"cosmos.evidence.v1.Query": "evidence query",
"cosmos.feegrant.v1beta1.Query": "feegrant query",
"cosmos.gov.v1.Query": "gov query",
"cosmos.gov.v1beta1.Query": "gov query v1beta1",
"cosmos.group.v1.Query": "group query",
"cosmos.mint.v1beta1.Query": "mint query",
"cosmos.params.v1beta1.Query": "params query",
"cosmos.slashing.v1beta1.Query": "slashing query",
"cosmos.staking.v1beta1.Query": "staking query",
"cosmos.upgrade.v1.Query": "upgrade query",
}

View File

@ -1,68 +0,0 @@
package internal
import (
"bytes"
"os"
"path"
"github.com/pelletier/go-toml/v2"
"cosmossdk.io/errors"
)
type Config struct {
Chains map[string]*ChainConfig `toml:"chains"`
}
type ChainConfig struct {
GRPCEndpoints []GRPCEndpoint `toml:"trusted-grpc-endpoints"`
Bech32Prefix string `toml:"bech32-prefix"`
}
type GRPCEndpoint struct {
Endpoint string `toml:"endpoint"`
Insecure bool `toml:"insecure"`
}
func LoadConfig(configDir string) (*Config, error) {
configPath := configFilename(configDir)
if _, err := os.Stat(configPath); os.IsNotExist(err) {
return &Config{Chains: map[string]*ChainConfig{}}, nil
}
bz, err := os.ReadFile(configPath)
if err != nil {
return nil, errors.Wrapf(err, "can't read config file: %s", configPath)
}
config := &Config{}
if err = toml.Unmarshal(bz, config); err != nil {
return nil, errors.Wrapf(err, "can't load config file: %s", configPath)
}
return config, err
}
func SaveConfig(configDir string, config *Config) error {
buf := &bytes.Buffer{}
enc := toml.NewEncoder(buf)
if err := enc.Encode(config); err != nil {
return err
}
if err := os.MkdirAll(configDir, 0o755); err != nil {
return err
}
configPath := configFilename(configDir)
if err := os.WriteFile(configPath, buf.Bytes(), 0o600); err != nil {
return err
}
return nil
}
func configFilename(configDir string) string {
return path.Join(configDir, "config.toml")
}

View File

@ -1,203 +0,0 @@
package internal
import (
"context"
"crypto/tls"
"errors"
"fmt"
"os"
"path"
cockroachdberrors "github.com/cockroachdb/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
autocliv1 "cosmossdk.io/api/cosmos/autocli/v1"
reflectionv2alpha1 "cosmossdk.io/api/cosmos/base/reflection/v2alpha1"
reflectionv1 "cosmossdk.io/api/cosmos/reflection/v1"
)
const DefaultConfigDirName = ".hubl"
type ChainInfo struct {
client *grpc.ClientConn
Context context.Context
ConfigDir string
Chain string
Config *ChainConfig
ProtoFiles *protoregistry.Files
ModuleOptions map[string]*autocliv1.ModuleOptions
}
func NewChainInfo(configDir, chain string, config *ChainConfig) *ChainInfo {
return &ChainInfo{
Context: context.Background(),
Config: config,
Chain: chain,
ConfigDir: configDir,
}
}
func (c *ChainInfo) getCacheDir() (string, error) {
cacheDir := path.Join(c.ConfigDir, "cache")
return cacheDir, os.MkdirAll(cacheDir, 0o755)
}
func (c *ChainInfo) fdsCacheFilename() (string, error) {
cacheDir, err := c.getCacheDir()
if err != nil {
return "", err
}
return path.Join(cacheDir, fmt.Sprintf("%s.fds", c.Chain)), nil
}
func (c *ChainInfo) appOptsCacheFilename() (string, error) {
cacheDir, err := c.getCacheDir()
if err != nil {
return "", err
}
return path.Join(cacheDir, fmt.Sprintf("%s.autocli", c.Chain)), nil
}
func (c *ChainInfo) Load(reload bool) error {
fdSet := &descriptorpb.FileDescriptorSet{}
fdsFilename, err := c.fdsCacheFilename()
if err != nil {
return err
}
if _, err := os.Stat(fdsFilename); os.IsNotExist(err) || reload {
client, err := c.OpenClient()
if err != nil {
return err
}
reflectionClient := reflectionv1.NewReflectionServiceClient(client)
fdRes, err := reflectionClient.FileDescriptors(c.Context, &reflectionv1.FileDescriptorsRequest{})
if err != nil {
fdSet, err = loadFileDescriptorsGRPCReflection(c.Context, client)
if err != nil {
return err
}
} else {
fdSet = &descriptorpb.FileDescriptorSet{File: fdRes.Files}
}
bz, err := proto.Marshal(fdSet)
if err != nil {
return err
}
if err = os.WriteFile(fdsFilename, bz, 0o600); err != nil {
return err
}
} else {
bz, err := os.ReadFile(fdsFilename)
if err != nil {
return err
}
if err = proto.Unmarshal(bz, fdSet); err != nil {
return err
}
}
c.ProtoFiles, err = protodesc.FileOptions{AllowUnresolvable: true}.NewFiles(fdSet)
if err != nil {
return fmt.Errorf("error building protoregistry.Files: %w", err)
}
appOptsFilename, err := c.appOptsCacheFilename()
if err != nil {
return err
}
if _, err := os.Stat(appOptsFilename); os.IsNotExist(err) || reload {
client, err := c.OpenClient()
if err != nil {
return err
}
autocliQueryClient := autocliv1.NewQueryClient(client)
appOptsRes, err := autocliQueryClient.AppOptions(c.Context, &autocliv1.AppOptionsRequest{})
if err != nil {
appOptsRes = guessAutocli(c.ProtoFiles)
}
bz, err := proto.Marshal(appOptsRes)
if err != nil {
return err
}
if err := os.WriteFile(appOptsFilename, bz, 0o600); err != nil {
return err
}
c.ModuleOptions = appOptsRes.ModuleOptions
} else {
bz, err := os.ReadFile(appOptsFilename)
if err != nil {
return err
}
var appOptsRes autocliv1.AppOptionsResponse
if err := proto.Unmarshal(bz, &appOptsRes); err != nil {
return err
}
c.ModuleOptions = appOptsRes.ModuleOptions
}
return nil
}
func (c *ChainInfo) OpenClient() (*grpc.ClientConn, error) {
if c.client != nil {
return c.client, nil
}
var res error
for _, endpoint := range c.Config.GRPCEndpoints {
var creds credentials.TransportCredentials
if endpoint.Insecure {
creds = insecure.NewCredentials()
} else {
creds = credentials.NewTLS(&tls.Config{
MinVersion: tls.VersionTLS12,
})
}
var err error
c.client, err = grpc.Dial(endpoint.Endpoint, grpc.WithTransportCredentials(creds))
if err != nil {
res = errors.Join(res, err)
continue
}
return c.client, nil
}
return nil, cockroachdberrors.Wrapf(res, "error loading gRPC client")
}
// getAddressPrefix returns the address prefix of the chain.
func getAddressPrefix(ctx context.Context, conn grpc.ClientConnInterface) (string, error) {
reflectionClient := reflectionv2alpha1.NewReflectionServiceClient(conn)
resp, err := reflectionClient.GetConfigurationDescriptor(ctx, &reflectionv2alpha1.GetConfigurationDescriptorRequest{})
if err != nil {
return "", err
}
if resp == nil || resp.Config == nil || resp.Config.Bech32AccountAddressPrefix == "" {
return "", cockroachdberrors.New("bech32 account address prefix is not set")
}
return resp.Config.Bech32AccountAddressPrefix, nil
}

View File

@ -1,101 +0,0 @@
package internal
import (
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
"github.com/manifoldco/promptui"
)
type ChainRegistryEntry struct {
APIs ChainRegistryAPIs `json:"apis"`
}
type ChainRegistryAPIs struct {
GRPC []*APIEntry `json:"grpc"`
}
type APIEntry struct {
Address string
Provider string
}
func GetChainRegistryEntry(chain string) (*ChainRegistryEntry, error) {
res, err := http.Get(fmt.Sprintf("https://raw.githubusercontent.com/cosmos/chain-registry/master/%v/chain.json", chain))
if err != nil {
return nil, err
}
bz, err := io.ReadAll(res.Body)
if err != nil {
return nil, err
}
data := &ChainRegistryEntry{}
if err = json.Unmarshal(bz, data); err != nil {
return nil, err
}
// clean-up the URL
cleanEntries := make([]*APIEntry, 0)
for i, apiEntry := range data.APIs.GRPC {
// clean-up the http(s):// prefix
if strings.Contains(apiEntry.Address, "https://") {
data.APIs.GRPC[i].Address = strings.Replace(apiEntry.Address, "https://", "", 1)
} else if strings.Contains(apiEntry.Address, "http://") {
data.APIs.GRPC[i].Address = strings.Replace(apiEntry.Address, "http://", "", 1)
}
// remove trailing slashes
data.APIs.GRPC[i].Address = strings.TrimSuffix(data.APIs.GRPC[i].Address, "/")
// remove addresses without a port
if !strings.Contains(data.APIs.GRPC[i].Address, ":") {
continue
}
cleanEntries = append(cleanEntries, data.APIs.GRPC[i])
}
data.APIs.GRPC = cleanEntries
fmt.Printf("Found data for %s in the chain registry\n", chain)
return data, nil
}
func SelectGRPCEndpoints(chain string) (string, error) {
entry, err := GetChainRegistryEntry(chain)
if err != nil {
fmt.Printf("Unable to load data for %s in the chain registry. Specify a custom gRPC endpoint manually.\n", chain)
prompt := &promptui.Prompt{
Label: "Enter a gRPC endpoint that you trust",
}
return prompt.Run()
}
var items []string
if entry != nil {
for _, apiEntry := range entry.APIs.GRPC {
items = append(items, fmt.Sprintf("%s: %s", apiEntry.Provider, apiEntry.Address))
}
}
prompt := promptui.SelectWithAdd{
Label: fmt.Sprintf("Select a gRPC endpoint that you trust for the %s network", chain),
Items: items,
AddLabel: "Custom endpoint:",
}
i, ep, err := prompt.Run()
if err != nil {
return "", err
}
// user selected a custom endpoint
if i == -1 {
return ep, nil
}
return entry.APIs.GRPC[i].Address, nil
}

View File

@ -1,258 +0,0 @@
package internal
import (
"context"
"fmt"
"os"
"path"
"strings"
"github.com/spf13/cobra"
"google.golang.org/grpc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/dynamicpb"
"cosmossdk.io/client/v2/autocli"
"cosmossdk.io/client/v2/autocli/flag"
addresscodec "github.com/cosmos/cosmos-sdk/codec/address"
)
var (
flagInsecure = "insecure"
flagUpdate = "update"
flagConfig = "config"
)
func RootCommand() (*cobra.Command, error) {
homeDir, err := os.UserHomeDir()
if err != nil {
return nil, err
}
configDir := path.Join(homeDir, DefaultConfigDirName)
config, err := LoadConfig(configDir)
if err != nil {
return nil, err
}
cmd := &cobra.Command{
Use: "hubl",
Short: "Hubl is a CLI for interacting with Cosmos SDK chains",
Long: "Hubl is a CLI for interacting with Cosmos SDK chains",
}
// add commands
commands, err := RemoteCommand(config, configDir)
if err != nil {
return nil, err
}
commands = append(commands, InitCommand(config, configDir))
cmd.AddCommand(commands...)
return cmd, nil
}
func InitCommand(config *Config, configDir string) *cobra.Command {
var insecure bool
cmd := &cobra.Command{
Use: "init [foochain]",
Short: "Initialize a new chain",
Long: `To configure a new chain just run this command using the --init flag and the name of the chain as it's listed in the chain registry (https://github.com/cosmos/chain-registry).
If the chain is not listed in the chain registry, you can use any unique name.`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
chainName := strings.ToLower(args[0])
return reconfigure(cmd, config, configDir, chainName)
},
}
cmd.Flags().BoolVar(&insecure, flagInsecure, false, "allow setting up insecure gRPC connection")
return cmd
}
func RemoteCommand(config *Config, configDir string) ([]*cobra.Command, error) {
commands := []*cobra.Command{}
for chain, chainConfig := range config.Chains {
chain, chainConfig := chain, chainConfig
// load chain info
chainInfo := NewChainInfo(configDir, chain, chainConfig)
if err := chainInfo.Load(false); err != nil {
commands = append(commands, RemoteErrorCommand(config, configDir, chain, chainConfig, err))
continue
}
appOpts := autocli.AppOptions{
ModuleOptions: chainInfo.ModuleOptions,
}
builder := &autocli.Builder{
Builder: flag.Builder{
AddressCodec: addresscodec.NewBech32Codec(chainConfig.Bech32Prefix),
ValidatorAddressCodec: addresscodec.NewBech32Codec(fmt.Sprintf("%svaloper", chainConfig.Bech32Prefix)),
ConsensusAddressCodec: addresscodec.NewBech32Codec(fmt.Sprintf("%svalcons", chainConfig.Bech32Prefix)),
TypeResolver: &dynamicTypeResolver{chainInfo},
FileResolver: chainInfo.ProtoFiles,
},
GetClientConn: func(command *cobra.Command) (grpc.ClientConnInterface, error) {
return chainInfo.OpenClient()
},
AddQueryConnFlags: func(command *cobra.Command) {},
}
var (
update bool
reconfig bool
insecure bool
)
chainCmd := &cobra.Command{
Use: chain,
Short: fmt.Sprintf("Commands for the %s chain", chain),
RunE: func(cmd *cobra.Command, args []string) error {
switch {
case reconfig:
return reconfigure(cmd, config, configDir, chain)
case update:
cmd.Printf("Updating autocli data for %s\n", chain)
return chainInfo.Load(true)
default:
return cmd.Help()
}
},
}
chainCmd.Flags().BoolVar(&update, flagUpdate, false, "update the CLI commands for the selected chain (should be used after every chain upgrade)")
chainCmd.Flags().BoolVar(&reconfig, flagConfig, false, "re-configure the selected chain (allows choosing a new gRPC endpoint and refreshes data")
chainCmd.Flags().BoolVar(&insecure, flagInsecure, false, "allow re-configuring the selected chain using an insecure gRPC connection")
if err := appOpts.EnhanceRootCommandWithBuilder(chainCmd, builder); err != nil {
return nil, err
}
commands = append(commands, chainCmd)
}
return commands, nil
}
func RemoteErrorCommand(config *Config, configDir, chain string, chainConfig *ChainConfig, err error) *cobra.Command {
cmd := &cobra.Command{
Use: chain,
Short: "Unable to load data",
Long: "Unable to load data, reconfiguration needed.",
RunE: func(cmd *cobra.Command, args []string) error {
cmd.Printf("Error loading chain data for %s: %+v\n", chain, err)
return reconfigure(cmd, config, configDir, chain)
},
}
cmd.Flags().Bool(flagInsecure, chainConfig.GRPCEndpoints[0].Insecure, "allow setting up insecure gRPC connection")
return cmd
}
func reconfigure(cmd *cobra.Command, config *Config, configDir, chain string) error {
insecure, _ := cmd.Flags().GetBool(flagInsecure)
cmd.Printf("Configuring %s\n", chain)
endpoint, err := SelectGRPCEndpoints(chain)
if err != nil {
return err
}
cmd.Printf("%s endpoint selected\n", endpoint)
chainConfig := &ChainConfig{
GRPCEndpoints: []GRPCEndpoint{
{
Endpoint: endpoint,
Insecure: insecure,
},
},
}
chainInfo := NewChainInfo(configDir, chain, chainConfig)
if err = chainInfo.Load(true); err != nil {
return err
}
client, err := chainInfo.OpenClient()
if err != nil {
return err
}
addressPrefix, err := getAddressPrefix(context.Background(), client)
if err != nil {
return err
}
chainConfig.Bech32Prefix = addressPrefix
config.Chains[chain] = chainConfig
if err := SaveConfig(configDir, config); err != nil {
return err
}
cmd.Printf("Configuration saved to %s\n", configDir)
return nil
}
type dynamicTypeResolver struct {
*ChainInfo
}
var (
_ protoregistry.MessageTypeResolver = dynamicTypeResolver{}
_ protoregistry.ExtensionTypeResolver = dynamicTypeResolver{}
)
func (d dynamicTypeResolver) FindMessageByName(message protoreflect.FullName) (protoreflect.MessageType, error) {
desc, err := d.ProtoFiles.FindDescriptorByName(message)
if err != nil {
return nil, err
}
return dynamicpb.NewMessageType(desc.(protoreflect.MessageDescriptor)), nil
}
func (d dynamicTypeResolver) FindMessageByURL(url string) (protoreflect.MessageType, error) {
if i := strings.LastIndexByte(url, '/'); i >= 0 {
url = url[i+len("/"):]
}
return d.FindMessageByName(protoreflect.FullName(url))
}
func (d dynamicTypeResolver) FindExtensionByName(field protoreflect.FullName) (protoreflect.ExtensionType, error) {
desc, err := d.ProtoFiles.FindDescriptorByName(field)
if err != nil {
return nil, err
}
return dynamicpb.NewExtensionType(desc.(protoreflect.ExtensionTypeDescriptor)), nil
}
func (d dynamicTypeResolver) FindExtensionByNumber(message protoreflect.FullName, field protoreflect.FieldNumber) (protoreflect.ExtensionType, error) {
desc, err := d.ProtoFiles.FindDescriptorByName(message)
if err != nil {
return nil, err
}
messageDesc := desc.(protoreflect.MessageDescriptor)
exts := messageDesc.Extensions()
n := exts.Len()
for i := 0; i < n; i++ {
ext := exts.Get(i)
if ext.Number() == field {
return dynamicpb.NewExtensionType(ext), nil
}
}
return nil, protoregistry.NotFound
}