071aa2a7ad
* 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.
201 lines
5.4 KiB
Go
201 lines
5.4 KiB
Go
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
|
|
},
|
|
}
|