herald.go/db/prefixes/generic.go

202 lines
5.4 KiB
Go
Raw Normal View History

Catchup to python-herald schema. Plus lots of refactoring. (#49) * Make prefixes_test.go more resilient against garbage left by a prior crash. Also correct error logging. * Don't do the ones' complement thing with DBStateValue fields HistFlushCount, CompFlushCount, CompCursor. Python-herald doesn't do it, and it presents one more irregular case for (un)marshalling fields. * Simplify type-specific partial packing, and simplify dispatch for pack key/value. * Add struct field annotations and refactor to prepare for use of "restruct" generic packing/unpacking. * Add dynamic pack/unpack based on "restruct" module. Dispatch normal pack/unpack through tableRegistry[] map instead of switch. * Add 5 new prefixes/tables (TrendingNotifications..HashXMempoolStatus). * Undo rename. TouchedOrDeleted -> ClaimDiff. * Fixup callers of eliminated partial pack functions. Have them use key.PartialPack(n). * Add pluggable SerializationAPI. Use it in prefixes_test. Populate PrefixRowKV.RawKey,RawValue when appropriate. * Undo accidental bump of rocksdb version. * Add .vscode dir to gitignore. * Fix ClaimToChannelValue annotation. Implement BlockTxsValue workaround as I can't find the right annotation to get it marshalled/unmarshalled. * Strengthen partial packing verification. Fix bugs in UnpackKey/UnpackValue for new types. * Remove .DS_Store, and ignore in future. * Fix MempoolTxKey, TouchedHashXValue. Remove some unneeded struct tags. * Generate test data and complete the tests for the new tables. Add Fuzz tests for TouchedHashXKey, TouchedHashXValue with happy path test data (only). * Move tableRegistry to prefixes.go and rename it prefixRegistry. Other minor fixes, comments. * Add test that runs through GetPrefixes() contents, and verifies they are registered in prefixRegistry.
2022-08-16 07:45:41 +02:00
package prefixes
import (
"encoding/binary"
"fmt"
"reflect"
"strings"
"github.com/go-restruct/restruct"
"github.com/lbryio/lbcd/chaincfg/chainhash"
)
func init() {
restruct.EnableExprBeta()
}
// Type OnesComplementEffectiveAmount (uint64) has to be encoded specially
// to get the desired sort ordering.
// Implement the Sizer, Packer, Unpacker interface to handle it manually.
func (amt *OnesComplementEffectiveAmount) SizeOf() int {
return 8
}
func (amt *OnesComplementEffectiveAmount) Pack(buf []byte, order binary.ByteOrder) ([]byte, error) {
binary.BigEndian.PutUint64(buf, OnesCompTwiddle64-uint64(*amt))
return buf[8:], nil
}
func (amt *OnesComplementEffectiveAmount) Unpack(buf []byte, order binary.ByteOrder) ([]byte, error) {
*amt = OnesComplementEffectiveAmount(OnesCompTwiddle64 - binary.BigEndian.Uint64(buf))
return buf[8:], nil
}
// Struct BlockTxsValue has a field TxHashes of type []*chainhash.Hash.
// I haven't been able to figure out the right annotations to make
// restruct.Pack,Unpack work automagically.
// Implement the Sizer, Packer, Unpacker interface to handle it manually.
func (kv *BlockTxsValue) SizeOf() int {
return 32 * len(kv.TxHashes)
}
func (kv *BlockTxsValue) Pack(buf []byte, order binary.ByteOrder) ([]byte, error) {
offset := 0
for _, h := range kv.TxHashes {
offset += copy(buf[offset:], h[:])
}
return buf[offset:], nil
}
func (kv *BlockTxsValue) Unpack(buf []byte, order binary.ByteOrder) ([]byte, error) {
offset := 0
kv.TxHashes = make([]*chainhash.Hash, len(buf)/32)
for i := range kv.TxHashes {
kv.TxHashes[i] = (*chainhash.Hash)(buf[offset:32])
offset += 32
}
return buf[offset:], nil
}
func genericNew(prefix []byte, key bool) (interface{}, error) {
t, ok := prefixRegistry[prefix[0]]
if !ok {
panic(fmt.Sprintf("not handled: prefix=%v", prefix))
}
if key {
return t.newKey(), nil
}
return t.newValue(), nil
}
func GenericPack(kv interface{}, fields int) ([]byte, error) {
// Locate the byte offset of the first excluded field.
offset := 0
if fields > 0 {
v := reflect.ValueOf(kv)
t := v.Type()
// Handle indirection to reach kind=Struct.
switch t.Kind() {
case reflect.Interface, reflect.Pointer:
v = v.Elem()
t = v.Type()
default:
panic(fmt.Sprintf("not handled: %v", t.Kind()))
}
count := 0
for _, sf := range reflect.VisibleFields(t) {
if !sf.IsExported() {
continue
}
if sf.Anonymous && strings.HasPrefix(sf.Name, "LengthEncoded") {
fields += 1 // Skip it but process NameLen and Name instead.
continue
}
if count > fields {
break
}
sz, err := restruct.SizeOf(v.FieldByIndex(sf.Index).Interface())
if err != nil {
panic(fmt.Sprintf("not handled: %v: %v", sf.Name, sf.Type.Kind()))
}
offset += sz
count += 1
}
}
// Pack the struct. No ability to partially pack.
buf, err := restruct.Pack(binary.BigEndian, kv)
if err != nil {
panic(fmt.Sprintf("not handled: %v", err))
}
// Return a prefix if some fields were excluded.
if fields > 0 {
return buf[:offset], nil
}
return buf, nil
}
func GenericUnpack(pfx []byte, key bool, buf []byte) (interface{}, error) {
kv, _ := genericNew(pfx, key)
err := restruct.Unpack(buf, binary.BigEndian, kv)
if err != nil {
panic(fmt.Sprintf("not handled: %v", err))
}
return kv, nil
}
func GetSerializationAPI(prefix []byte) *SerializationAPI {
t, ok := prefixRegistry[prefix[0]]
if !ok {
panic(fmt.Sprintf("not handled: prefix=%v", prefix))
}
if t.API != nil {
return t.API
}
return ProductionAPI
}
type SerializationAPI struct {
PackKey func(key BaseKey) ([]byte, error)
PackPartialKey func(key BaseKey, fields int) ([]byte, error)
PackValue func(value BaseValue) ([]byte, error)
UnpackKey func(key []byte) (BaseKey, error)
UnpackValue func(prefix []byte, value []byte) (BaseValue, error)
}
var ProductionAPI = &SerializationAPI{
PackKey: PackGenericKey,
PackPartialKey: PackPartialGenericKey,
PackValue: PackGenericValue,
UnpackKey: UnpackGenericKey,
UnpackValue: UnpackGenericValue,
}
var RegressionAPI_1 = &SerializationAPI{
PackKey: func(key BaseKey) ([]byte, error) {
return GenericPack(key, -1)
},
PackPartialKey: func(key BaseKey, fields int) ([]byte, error) {
return GenericPack(key, fields)
},
PackValue: func(value BaseValue) ([]byte, error) {
return GenericPack(value, -1)
},
UnpackKey: UnpackGenericKey,
UnpackValue: UnpackGenericValue,
}
var RegressionAPI_2 = &SerializationAPI{
PackKey: PackGenericKey,
PackPartialKey: PackPartialGenericKey,
PackValue: PackGenericValue,
UnpackKey: func(key []byte) (BaseKey, error) {
k, err := GenericUnpack(key, true, key)
return k.(BaseKey), err
},
UnpackValue: func(prefix []byte, value []byte) (BaseValue, error) {
k, err := GenericUnpack(prefix, false, value)
return k.(BaseValue), err
},
}
var RegressionAPI_3 = &SerializationAPI{
PackKey: func(key BaseKey) ([]byte, error) {
return GenericPack(key, -1)
},
PackPartialKey: func(key BaseKey, fields int) ([]byte, error) {
return GenericPack(key, fields)
},
PackValue: func(value BaseValue) ([]byte, error) {
return GenericPack(value, -1)
},
UnpackKey: func(key []byte) (BaseKey, error) {
k, err := GenericUnpack(key, true, key)
return k.(BaseKey), err
},
UnpackValue: func(prefix []byte, value []byte) (BaseValue, error) {
k, err := GenericUnpack(prefix, false, value)
return k.(BaseValue), err
},
}