Skip to content

Commit

Permalink
Fork iaviewer (#717)
Browse files Browse the repository at this point in the history
  • Loading branch information
roy-dydx authored Oct 27, 2023
1 parent 6ae2860 commit 0ba123e
Show file tree
Hide file tree
Showing 2 changed files with 195 additions and 1 deletion.
2 changes: 1 addition & 1 deletion protocol/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
194 changes: 194 additions & 0 deletions protocol/scripts/iaviewer/iaviewer.go
Original file line number Diff line number Diff line change
@@ -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 <data|shape|versions> <leveldb dir> <prefix> [version number]")
fmt.Fprintln(os.Stderr, "<prefix> is the prefix of db, and the iavl tree of different modules in cosmos-sdk uses ")
fmt.Fprintln(os.Stderr, "different <prefix> 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<nil>", 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)
}
}

0 comments on commit 0ba123e

Please sign in to comment.