diff --git a/protocol/go.mod b/protocol/go.mod index 47c74b6621..6054578bf1 100644 --- a/protocol/go.mod +++ b/protocol/go.mod @@ -45,6 +45,7 @@ require ( cosmossdk.io/errors v1.0.0 cosmossdk.io/log v1.1.1-0.20230704160919-88f2c830b0ca github.com/burdiyan/kafkautil v0.0.0-20190131162249-eaf83ed22d5b + github.com/cosmos/iavl v0.20.0 github.com/deckarep/golang-set/v2 v2.3.0 github.com/ethereum/go-ethereum v1.12.0 github.com/ory/dockertest/v3 v3.10.0 @@ -114,7 +115,6 @@ require ( github.com/containerd/continuity v0.3.0 // indirect github.com/cosmos/btcutil v1.0.5 // indirect github.com/cosmos/gogogateway v1.2.0 // indirect - github.com/cosmos/iavl v0.20.0 // indirect github.com/cosmos/ics23/go v0.10.0 // indirect github.com/cosmos/ledger-cosmos-go v0.12.1 // indirect github.com/cosmos/rosetta-sdk-go v0.10.0 // indirect diff --git a/protocol/scripts/iaviewer/iaviewer.go b/protocol/scripts/iaviewer/iaviewer.go new file mode 100644 index 0000000000..4312f04683 --- /dev/null +++ b/protocol/scripts/iaviewer/iaviewer.go @@ -0,0 +1,194 @@ +// Originally forked from https://github.com/cosmos/iavl/blob/v0.20.0/cmd/iaviewer/main.go +package main + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + + dbm "github.com/cometbft/cometbft-db" + + "github.com/cosmos/iavl" +) + +// TODO: make this configurable? +const ( + DefaultCacheSize int = 10000 +) + +func main() { + args := os.Args[1:] + if len(args) < 3 || (args[0] != "data" && args[0] != "shape" && args[0] != "versions") { + fmt.Fprintln(os.Stderr, "Usage: iaviewer [version number]") + fmt.Fprintln(os.Stderr, " is the prefix of db, and the iavl tree of different modules in cosmos-sdk uses ") + fmt.Fprintln(os.Stderr, "different to identify, just like \"s/k:gov/\" represents the prefix of gov module") + os.Exit(1) + } + + version := 0 + if len(args) == 4 { + var err error + version, err = strconv.Atoi(args[3]) + if err != nil { + fmt.Fprintf(os.Stderr, "Invalid version number: %s\n", err) + os.Exit(1) + } + } + + tree, err := ReadTree(args[1], version, []byte(args[2])) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading data: %s\n", err) + os.Exit(1) + } + + switch args[0] { + case "data": + PrintKeys(tree) + hash, err := tree.Hash() + if err != nil { + fmt.Fprintf(os.Stderr, "Error hashing tree: %s\n", err) + os.Exit(1) + } + fmt.Printf("Hash: %X\n", hash) + fmt.Printf("Size: %X\n", tree.Size()) + case "shape": + PrintShape(tree) + case "versions": + PrintVersions(tree) + } +} + +func OpenDB(dir string) (dbm.DB, error) { + switch { + case strings.HasSuffix(dir, ".db"): + dir = dir[:len(dir)-3] + case strings.HasSuffix(dir, ".db/"): + dir = dir[:len(dir)-4] + default: + return nil, fmt.Errorf("database directory must end with .db") + } + + dir, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + + // TODO: doesn't work on windows! + cut := strings.LastIndex(dir, "/") + if cut == -1 { + return nil, fmt.Errorf("cannot cut paths on %s", dir) + } + name := dir[cut+1:] + db, err := dbm.NewGoLevelDB(name, dir[:cut]) + if err != nil { + return nil, err + } + return db, nil +} + +func PrintDBStats(db dbm.DB) { + count := 0 + prefix := map[string]int{} + itr, err := db.Iterator(nil, nil) + if err != nil { + panic(err) + } + + defer itr.Close() + for ; itr.Valid(); itr.Next() { + key := itr.Key()[:1] + prefix[string(key)]++ + count++ + } + if err := itr.Error(); err != nil { + panic(err) + } + fmt.Printf("DB contains %d entries\n", count) + for k, v := range prefix { + fmt.Printf(" %s: %d\n", k, v) + } +} + +// 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(dir string, version int, prefix []byte) (*iavl.MutableTree, error) { + db, err := OpenDB(dir) + if err != nil { + return nil, err + } + 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)) + fmt.Printf("Got version: %d\n", ver) + return tree, err +} + +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 + }) +} + +// 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 PrintShape(tree *iavl.MutableTree) { + // shape := tree.RenderShape(" ", nil) + // TODO: handle this error + shape, _ := tree.RenderShape(" ", nodeEncoder) + fmt.Println(strings.Join(shape, "\n")) +} + +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", prefix) + } + return fmt.Sprintf("%s%s", prefix, parseWeaveKey(id)) +} + +func PrintVersions(tree *iavl.MutableTree) { + versions := tree.AvailableVersions() + fmt.Println("Available versions:") + for _, v := range versions { + fmt.Printf(" %d\n", v) + } +}