Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions client/cmd/bisonw-desktop/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ require (
github.com/athanorlabs/go-dleq v0.1.0 // indirect
github.com/bisoncraft/go-monero v0.1.1 // indirect
github.com/bisoncraft/op-geth v0.0.0-20250729074358-3cfe4f15e91c // indirect
github.com/bits-and-blooms/bitset v1.20.0 // indirect
github.com/bits-and-blooms/bitset v1.22.0 // indirect
github.com/consensys/gnark-crypto v0.18.0 // indirect
github.com/crate-crypto/go-eth-kzg v1.4.0 // indirect
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
Expand Down Expand Up @@ -159,7 +159,7 @@ require (
github.com/olekukonko/tablewriter v0.0.5 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/progrium/darwinkit v0.5.0
github.com/rivo/uniseg v0.2.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect
github.com/tevino/abool v1.2.0 // indirect
Expand Down
7 changes: 4 additions & 3 deletions client/cmd/bisonw-desktop/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ github.com/bisoncraft/op-geth v0.0.0-20250729074358-3cfe4f15e91c h1:cmK4HxTpEutY
github.com/bisoncraft/op-geth v0.0.0-20250729074358-3cfe4f15e91c/go.mod h1:AVdw4MgxUWZ0mhB+VxKOfXH2Z6WJA6rX6YrYrY2bii0=
github.com/bisoncraft/webview_go v0.1.0 h1:F0ZiJSYzDqE4HJhI1u5I+Y7H51bYzprDum0tAtMnOw4=
github.com/bisoncraft/webview_go v0.1.0/go.mod h1:cDmD2SZRZJl3wXKsgU3cRLA64HCcJL9Kxa+Hp5u4so4=
github.com/bits-and-blooms/bitset v1.20.0 h1:2F+rfL86jE2d/bmw7OhqUg2Sj/1rURkBn3MdfoPyRVU=
github.com/bits-and-blooms/bitset v1.20.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bits-and-blooms/bitset v1.22.0 h1:Tquv9S8+SGaS3EhyA+up3FXzmkhxPGjQQCkcs2uw7w4=
github.com/bits-and-blooms/bitset v1.22.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI=
github.com/bluele/gcache v0.0.2/go.mod h1:m15KV+ECjptwSPxKhOhQoAFQVtUFjTVkc3H8o0t/fp0=
Expand Down Expand Up @@ -1022,8 +1022,9 @@ github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:r
github.com/quasilyte/regex/syntax v0.0.0-20200805063351-8f842688393c/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0=
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
Expand Down
224 changes: 224 additions & 0 deletions dex/lexi/cmd/lexidbexplorer/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
// This code is available on the terms of the project LICENSE.md file,
// also available online at https://blueoakcouncil.org/license/1.0.0.

package main

import (
"errors"
"fmt"
"sort"
"strings"

"decred.org/dcrdex/dex/lexi"
"github.com/dgraph-io/badger"
)

// These constants mirror unexported values from the lexi package.
// They are duplicated here because they are internal implementation details
// that shouldn't be exported just for the explorer tool.
const prefixSize = 2 // lexi.prefixSize

// prefixToNamePrefix mirrors lexi.prefixToNamePrefix - the prefix used to
// map table/index prefixes to their names.
var prefixToNamePrefix = [prefixSize]byte{0x00, 0x00}

// nameToPrefixPrefix mirrors lexi.nameToPrefixPrefix - the prefix used to map
// table/index names to their key prefixes.
var nameToPrefixPrefix = [prefixSize]byte{0x00, 0x01}

// idToKeyPrefix mirrors lexi.idToKeyPrefix - the prefix used to map DBID -> key.
var idToKeyPrefix = [prefixSize]byte{0x00, 0x04}

const indexNameSeparator = "__idx__"

// entryData holds the key, optional index key, and value for a database entry.
type entryData struct {
key []byte
indexKey []byte // only set when iterating an index
value []byte
}

func prefixedKey(prefix [prefixSize]byte, k []byte) []byte {
pk := make([]byte, prefixSize+len(k))
copy(pk, prefix[:])
copy(pk[prefixSize:], k)
return pk
}

func cloneBytes(b []byte) []byte {
if len(b) == 0 {
return nil
}
c := make([]byte, len(b))
copy(c, b)
return c
}

func prefixForName(bdb *badger.DB, name string) ([prefixSize]byte, error) {
var prefix [prefixSize]byte
err := bdb.View(func(txn *badger.Txn) error {
it, err := txn.Get(prefixedKey(nameToPrefixPrefix, []byte(name)))
if err != nil {
return err
}
return it.Value(func(b []byte) error {
if len(b) != prefixSize {
return fmt.Errorf("unexpected prefix size %d for name %q", len(b), name)
}
copy(prefix[:], b)
return nil
})
})
return prefix, err
}

// listTablesAndIndexes iterates the prefixToNamePrefix to get all table and
// index names, returning a map of tableName -> []indexNames.
func listTablesAndIndexes(bdb *badger.DB) (map[string][]string, error) {
tables := make(map[string][]string) // tableName -> []indexNames

err := bdb.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.Prefix = prefixToNamePrefix[:]
it := txn.NewIterator(opts)
defer it.Close()

for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
err := item.Value(func(nameB []byte) error {
name := string(nameB)
if strings.Contains(name, indexNameSeparator) {
// It's an index: "tableName__idx__indexName"
parts := strings.SplitN(name, indexNameSeparator, 2)
if len(parts) == 2 {
tableName, indexName := parts[0], parts[1]
tables[tableName] = append(tables[tableName], indexName)
}
} else {
// It's a table
if _, exists := tables[name]; !exists {
tables[name] = []string{}
}
}
return nil
})
if err != nil {
return err
}
}
return nil
})

// Sort index names for consistent display
for _, indexes := range tables {
sort.Strings(indexes)
}

return tables, err
}

// iterateTable iterates all entries in a table and returns the entries.
func iterateTable(db *lexi.DB, tableName string, limit int) ([]entryData, error) {
table, err := db.Table(tableName)
if err != nil {
return nil, err
}

var entries []entryData
err = table.Iterate(nil, func(it *lexi.Iter) error {
k, err := it.K()
if err != nil {
return err
}

var v []byte
if err := it.V(func(vB []byte) error {
v = cloneBytes(vB)
return nil
}); err != nil {
return err
}

entries = append(entries, entryData{
key: cloneBytes(k),
value: v,
})

if limit > 0 && len(entries) >= limit {
return lexi.ErrEndIteration
}
return nil
})

return entries, err
}

// iterateIndex iterates all entries in an index and returns the entries
// in index order.
func iterateIndex(bdb *badger.DB, db *lexi.DB, tableName, indexName string, limit int) ([]entryData, error) {
table, err := db.Table(tableName)
if err != nil {
return nil, err
}

indexPrefix, err := prefixForName(bdb, tableName+indexNameSeparator+indexName)
if errors.Is(err, badger.ErrKeyNotFound) {
return nil, fmt.Errorf("index %q not found on table %q", indexName, tableName)
}
if err != nil {
return nil, err
}

var entries []entryData
err = bdb.View(func(txn *badger.Txn) error {
opts := badger.DefaultIteratorOptions
opts.Prefix = indexPrefix[:]
it := txn.NewIterator(opts)
defer it.Close()

for it.Rewind(); it.Valid(); it.Next() {
item := it.Item()
idxEntryKey := item.KeyCopy(nil)
if len(idxEntryKey) < prefixSize+8 {
// indexPrefix + (idxKey + dbid) must be at least 8 bytes beyond prefix
continue
}

rest := idxEntryKey[prefixSize:]
if len(rest) < 8 {
continue
}
idxKey := cloneBytes(rest[:len(rest)-8])
dbIDB := rest[len(rest)-8:]

// Resolve the original table key from DBID.
keyItem, err := txn.Get(prefixedKey(idToKeyPrefix, dbIDB))
if err != nil {
return err
}
keyB, err := keyItem.ValueCopy(nil)
if err != nil {
return err
}

// Resolve the value via the table API (decodes the internal datum).
vB, err := table.GetRaw(keyB, lexi.WithGetTxn(txn))
if err != nil {
return err
}

entries = append(entries, entryData{
key: cloneBytes(keyB),
indexKey: idxKey,
value: cloneBytes(vB),
})

if limit > 0 && len(entries) >= limit {
break
}
}
return nil
})

return entries, err
}
Loading