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  }