github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/iavl/cmd/iaviewer/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "crypto/sha256" 6 "encoding/hex" 7 "fmt" 8 "os" 9 "strconv" 10 "strings" 11 12 dbm "github.com/gnolang/gno/tm2/pkg/db" 13 "github.com/gnolang/gno/tm2/pkg/db/goleveldb" 14 "github.com/gnolang/gno/tm2/pkg/iavl" 15 ) 16 17 // TODO: make this configurable? 18 const ( 19 DefaultCacheSize int = 10000 20 ) 21 22 func main() { 23 args := os.Args[1:] 24 if len(args) < 2 || (args[0] != "data" && args[0] != "shape" && args[0] != "versions") { 25 fmt.Fprintln(os.Stderr, "Usage: iaviewer <data|shape|versions> <leveldb dir> [version number]") 26 os.Exit(1) 27 } 28 29 version := 0 30 if len(args) == 3 { 31 var err error 32 version, err = strconv.Atoi(args[2]) 33 if err != nil { 34 fmt.Fprintf(os.Stderr, "Invalid version number: %s\n", err) 35 os.Exit(1) 36 } 37 } 38 39 tree, err := ReadTree(args[1], version) 40 if err != nil { 41 fmt.Fprintf(os.Stderr, "Error reading data: %s\n", err) 42 os.Exit(1) 43 } 44 45 if args[0] == "data" { 46 PrintKeys(tree) 47 fmt.Printf("Hash: %X\n", tree.Hash()) 48 fmt.Printf("Size: %X\n", tree.Size()) 49 } else if args[0] == "shape" { 50 PrintShape(tree) 51 } else if args[0] == "versions" { 52 PrintVersions(tree) 53 } 54 } 55 56 func OpenDB(dir string) (dbm.DB, error) { 57 if strings.HasSuffix(dir, ".db") { 58 dir = dir[:len(dir)-3] 59 } else if strings.HasSuffix(dir, ".db/") { 60 dir = dir[:len(dir)-4] 61 } else { 62 return nil, fmt.Errorf("database directory must end with .db") 63 } 64 // TODO: doesn't work on windows! 65 cut := strings.LastIndex(dir, "/") 66 if cut == -1 { 67 return nil, fmt.Errorf("cannot cut paths on %s", dir) 68 } 69 name := dir[cut+1:] 70 db, err := goleveldb.NewGoLevelDB(name, dir[:cut]) 71 if err != nil { 72 return nil, err 73 } 74 return db, nil 75 } 76 77 // ReadTree loads an iavl tree from the directory 78 // If version is 0, load latest, otherwise, load named version 79 func ReadTree(dir string, version int) (*iavl.MutableTree, error) { 80 db, err := OpenDB(dir) 81 if err != nil { 82 return nil, err 83 } 84 tree := iavl.NewMutableTree(db, DefaultCacheSize) 85 ver, err := tree.LoadVersion(int64(version)) 86 fmt.Printf("Got version: %d\n", ver) 87 return tree, err 88 } 89 90 func PrintKeys(tree *iavl.MutableTree) { 91 fmt.Println("Printing all keys with hashed values (to detect diff)") 92 tree.Iterate(func(key []byte, value []byte) bool { 93 printKey := parseWeaveKey(key) 94 digest := sha256.Sum256(value) 95 fmt.Printf(" %s\n %X\n", printKey, digest) 96 return false 97 }) 98 } 99 100 // parseWeaveKey assumes a separating : where all in front should be ascii, 101 // and all afterwards may be ascii or binary 102 func parseWeaveKey(key []byte) string { 103 cut := bytes.IndexRune(key, ':') 104 if cut == -1 { 105 return encodeID(key) 106 } 107 prefix := key[:cut] 108 id := key[cut+1:] 109 return fmt.Sprintf("%s:%s", encodeID(prefix), encodeID(id)) 110 } 111 112 // casts to a string if it is printable ascii, hex-encodes otherwise 113 func encodeID(id []byte) string { 114 for _, b := range id { 115 if b < 0x20 || b >= 0x80 { 116 return strings.ToUpper(hex.EncodeToString(id)) 117 } 118 } 119 return string(id) 120 } 121 122 func PrintShape(tree *iavl.MutableTree) { 123 // shape := tree.RenderShape(" ", nil) 124 shape := tree.RenderShape(" ", nodeEncoder) 125 fmt.Println(strings.Join(shape, "\n")) 126 } 127 128 func nodeEncoder(id []byte, depth int, isLeaf bool) string { 129 prefix := fmt.Sprintf("-%d ", depth) 130 if isLeaf { 131 prefix = fmt.Sprintf("*%d ", depth) 132 } 133 if len(id) == 0 { 134 return fmt.Sprintf("%s<nil>", prefix) 135 } 136 return fmt.Sprintf("%s%s", prefix, parseWeaveKey(id)) 137 } 138 139 func PrintVersions(tree *iavl.MutableTree) { 140 versions := tree.AvailableVersions() 141 fmt.Println("Available versions:") 142 for v := range versions { 143 fmt.Printf(" %d\n", v) 144 } 145 }