feat(cli): add iavlviewer command (#1922) (#1928)

* port iavlviewer to kava v0.26.x to debug app hash

* add hash subcommand to iavlviewer

additionally, use better error handling

* update changelog

* separate iavlviewer command into subcommands

---------

Co-authored-by: Nick DeLuca <nickdeluca08@gmail.com>
This commit is contained in:
Robert Pirtle 2024-05-30 14:25:21 -07:00 committed by GitHub
parent 0dc30538b7
commit 1f31621b11
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 309 additions and 1 deletions

View File

@ -36,6 +36,9 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased] ## [Unreleased]
### Features
- (cli) [#1922] Add `iavlviewer` CLI command for low-level iavl db debugging.
### Improvements ### Improvements
- (pricefeed) [#1851] optimize EndBlocker to iterate all markets only once - (pricefeed) [#1851] optimize EndBlocker to iterate all markets only once
- (cdp) [#1822] optimize BeginBlocker by removing unnecessary/redundant checks & calculations - (cdp) [#1822] optimize BeginBlocker by removing unnecessary/redundant checks & calculations
@ -334,6 +337,7 @@ the [changelog](https://github.com/cosmos/cosmos-sdk/blob/v0.38.4/CHANGELOG.md).
- [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run - [#257](https://github.com/Kava-Labs/kava/pulls/257) Include scripts to run
large-scale simulations remotely using aws-batch large-scale simulations remotely using aws-batch
[#1922]: https://github.com/Kava-Labs/kava/pull/1922
[#1851]: https://github.com/Kava-Labs/kava/pull/1851 [#1851]: https://github.com/Kava-Labs/kava/pull/1851
[#1846]: https://github.com/Kava-Labs/kava/pull/1846 [#1846]: https://github.com/Kava-Labs/kava/pull/1846
[#1848]: https://github.com/Kava-Labs/kava/pull/1848 [#1848]: https://github.com/Kava-Labs/kava/pull/1848

View File

@ -0,0 +1,56 @@
package iavlviewer
import (
"crypto/sha256"
"fmt"
"github.com/cosmos/iavl"
ethermintserver "github.com/evmos/ethermint/server"
"github.com/spf13/cobra"
)
func newDataCmd(opts ethermintserver.StartOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "data <prefix> [version number]",
Short: "View all keys, hash, & size of tree.",
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
prefix := args[0]
version := 0
if len(args) == 2 {
var err error
version, err = parseVersion(args[1])
if err != nil {
return err
}
}
tree, err := openPrefixTree(opts, cmd, prefix, version)
if err != nil {
return err
}
printKeys(tree)
hash, err := tree.Hash()
if err != nil {
return err
}
fmt.Printf("Hash: %X\n", hash)
fmt.Printf("Size: %X\n", tree.Size())
return nil
},
}
return cmd
}
func printKeys(tree *iavl.MutableTree) {
fmt.Println("Printing all keys with hashed values (to detect diff)")
tree.Iterate(func(key []byte, value []byte) bool { //nolint:errcheck
printKey := parseWeaveKey(key)
digest := sha256.Sum256(value)
fmt.Printf(" %s\n %X\n", printKey, digest)
return false
})
}

View File

@ -0,0 +1,42 @@
package iavlviewer
import (
"fmt"
ethermintserver "github.com/evmos/ethermint/server"
"github.com/spf13/cobra"
)
func newHashCmd(opts ethermintserver.StartOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "hash <prefix> [version number]",
Short: "Print the root hash of the iavl tree.",
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
prefix := args[0]
version := 0
if len(args) == 2 {
var err error
version, err = parseVersion(args[1])
if err != nil {
return err
}
}
tree, err := openPrefixTree(opts, cmd, prefix, version)
if err != nil {
return err
}
hash, err := tree.Hash()
if err != nil {
return err
}
fmt.Printf("Hash: %X\n", hash)
return nil
},
}
return cmd
}

View File

@ -0,0 +1,83 @@
package iavlviewer
import (
"fmt"
"strconv"
dbm "github.com/cometbft/cometbft-db"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/server"
ethermintserver "github.com/evmos/ethermint/server"
"github.com/spf13/cobra"
"github.com/cosmos/iavl"
)
const (
DefaultCacheSize int = 10000
)
func NewCmd(opts ethermintserver.StartOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "iavlviewer <data|hash|shape|versions> <prefix> [version number]",
Short: "Output various data, hashes, and calculations for an iavl tree",
}
cmd.AddCommand(newDataCmd(opts))
cmd.AddCommand(newHashCmd(opts))
cmd.AddCommand(newShapeCmd(opts))
cmd.AddCommand(newVersionsCmd(opts))
return cmd
}
func parseVersion(arg string) (int, error) {
version, err := strconv.Atoi(arg)
if err != nil {
return 0, fmt.Errorf("invalid version number: '%s'", arg)
}
return version, nil
}
func openPrefixTree(opts ethermintserver.StartOptions, cmd *cobra.Command, prefix string, version int) (*iavl.MutableTree, error) {
clientCtx := client.GetClientContextFromCmd(cmd)
ctx := server.GetServerContextFromCmd(cmd)
ctx.Config.SetRoot(clientCtx.HomeDir)
db, err := opts.DBOpener(ctx.Viper, clientCtx.HomeDir, server.GetAppDBBackend(ctx.Viper))
if err != nil {
return nil, fmt.Errorf("failed to open database at %s: %s", clientCtx.HomeDir, err)
}
defer func() {
if err := db.Close(); err != nil {
ctx.Logger.Error("error closing db", "error", err.Error())
}
}()
tree, err := readTree(db, version, []byte(prefix))
if err != nil {
return nil, fmt.Errorf("failed to read tree with prefix %s: %s", prefix, err)
}
return tree, nil
}
// ReadTree loads an iavl tree from the directory
// If version is 0, load latest, otherwise, load named version
// The prefix represents which iavl tree you want to read. The iaviwer will always set a prefix.
func readTree(db dbm.DB, version int, prefix []byte) (*iavl.MutableTree, error) {
if len(prefix) != 0 {
db = dbm.NewPrefixDB(db, prefix)
}
tree, err := iavl.NewMutableTree(db, DefaultCacheSize, false)
if err != nil {
return nil, err
}
ver, err := tree.LoadVersion(int64(version))
if err != nil {
return nil, err
}
fmt.Printf("Latest version: %d\n", ver)
fmt.Printf("Got version: %d\n", version)
return tree, err
}

View File

@ -0,0 +1,47 @@
package iavlviewer
import (
"fmt"
"strings"
"github.com/cosmos/iavl"
ethermintserver "github.com/evmos/ethermint/server"
"github.com/spf13/cobra"
)
func newShapeCmd(opts ethermintserver.StartOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "shape <prefix> [version number]",
Short: "View shape of iavl tree.",
Args: cobra.RangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
prefix := args[0]
version := 0
if len(args) == 2 {
var err error
version, err = parseVersion(args[1])
if err != nil {
return err
}
}
tree, err := openPrefixTree(opts, cmd, prefix, version)
if err != nil {
return err
}
printShape(tree)
return nil
},
}
return cmd
}
func printShape(tree *iavl.MutableTree) {
// shape := tree.RenderShape(" ", nil)
// TODO: handle this error
shape, _ := tree.RenderShape(" ", nodeEncoder)
fmt.Println(strings.Join(shape, "\n"))
}

View File

@ -0,0 +1,74 @@
package iavlviewer
import (
"bytes"
"encoding/hex"
"fmt"
"strings"
"github.com/cosmos/iavl"
ethermintserver "github.com/evmos/ethermint/server"
"github.com/spf13/cobra"
)
func newVersionsCmd(opts ethermintserver.StartOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "versions <prefix>",
Short: "Print all versions of iavl tree",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
prefix := args[0]
tree, err := openPrefixTree(opts, cmd, prefix, 15)
if err != nil {
return err
}
printVersions(tree)
return nil
},
}
return cmd
}
func printVersions(tree *iavl.MutableTree) {
versions := tree.AvailableVersions()
fmt.Println("Available versions:")
for _, v := range versions {
fmt.Printf(" %d\n", v)
}
}
// parseWeaveKey assumes a separating : where all in front should be ascii,
// and all afterwards may be ascii or binary
func parseWeaveKey(key []byte) string {
cut := bytes.IndexRune(key, ':')
if cut == -1 {
return encodeID(key)
}
prefix := key[:cut]
id := key[cut+1:]
return fmt.Sprintf("%s:%s", encodeID(prefix), encodeID(id))
}
// casts to a string if it is printable ascii, hex-encodes otherwise
func encodeID(id []byte) string {
for _, b := range id {
if b < 0x20 || b >= 0x80 {
return strings.ToUpper(hex.EncodeToString(id))
}
}
return string(id)
}
func nodeEncoder(id []byte, depth int, isLeaf bool) string {
prefix := fmt.Sprintf("-%d ", depth)
if isLeaf {
prefix = fmt.Sprintf("*%d ", depth)
}
if len(id) == 0 {
return fmt.Sprintf("%s<nil>", prefix)
}
return fmt.Sprintf("%s%s", prefix, parseWeaveKey(id))
}

View File

@ -25,6 +25,7 @@ import (
"github.com/kava-labs/kava/app" "github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/app/params" "github.com/kava-labs/kava/app/params"
"github.com/kava-labs/kava/cmd/kava/cmd/iavlviewer"
"github.com/kava-labs/kava/cmd/kava/cmd/rocksdb" "github.com/kava-labs/kava/cmd/kava/cmd/rocksdb"
"github.com/kava-labs/kava/cmd/kava/opendb" "github.com/kava-labs/kava/cmd/kava/opendb"
) )
@ -134,5 +135,6 @@ func addSubCmds(rootCmd *cobra.Command, encodingConfig params.EncodingConfig, de
keyCommands(app.DefaultNodeHome), keyCommands(app.DefaultNodeHome),
rocksdb.RocksDBCmd, rocksdb.RocksDBCmd,
newShardCmd(opts), newShardCmd(opts),
iavlviewer.NewCmd(opts),
) )
} }

2
go.mod
View File

@ -13,6 +13,7 @@ require (
github.com/cosmos/cosmos-sdk v0.47.10 github.com/cosmos/cosmos-sdk v0.47.10
github.com/cosmos/go-bip39 v1.0.0 github.com/cosmos/go-bip39 v1.0.0
github.com/cosmos/gogoproto v1.4.10 github.com/cosmos/gogoproto v1.4.10
github.com/cosmos/iavl v0.20.1
github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7 v7.1.3 github.com/cosmos/ibc-apps/middleware/packet-forward-middleware/v7 v7.1.3
github.com/cosmos/ibc-go/v7 v7.4.0 github.com/cosmos/ibc-go/v7 v7.4.0
github.com/ethereum/go-ethereum v1.10.26 github.com/ethereum/go-ethereum v1.10.26
@ -76,7 +77,6 @@ require (
github.com/confio/ics23/go v0.9.0 // indirect github.com/confio/ics23/go v0.9.0 // indirect
github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/btcutil v1.0.5 // indirect
github.com/cosmos/gogogateway v1.2.0 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect
github.com/cosmos/iavl v0.20.1 // indirect
github.com/cosmos/ics23/go v0.10.0 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect
github.com/cosmos/ledger-cosmos-go v0.13.1 // indirect github.com/cosmos/ledger-cosmos-go v0.13.1 // indirect
github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect