317cdf7129
* Partial blockchain.transaction.yyy RPC implementations. * Register RPC service object. * Move session manager start/stop to a better place. * Attempt to fill in the details of transaction.get_batch, including merkle path. * Correct interpretation of DBStateValue Genesis hash. * Convert Args.Port to int and validate. Run UDP ping server on JSONRPCPort too. * Add BlockHeader to HeightHash notification. * Limit session-based JSON RPC service to IPv4. Client not ready for IPv6. * Adapt to new HeightHash struct. * Fine tune JSON RPC handlers and types to match lbry-sdk expectations. Implement UnmarshalJSON()/MarshalJSON() for several types. * Add more special handling of DBStateValue.Genesis hash. * Set IncludeStop=false generally to avoid returning extra rows. Other misc fixes.
230 lines
6.5 KiB
Go
230 lines
6.5 KiB
Go
package prefixes
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"github.com/go-restruct/restruct"
|
|
"github.com/lbryio/herald.go/internal"
|
|
"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
|
|
}
|
|
|
|
// Struct BigEndianChainHash is a chainhash.Hash stored in external
|
|
// byte-order (opposite of other 32 byte chainhash.Hash values). In order
|
|
// to reuse chainhash.Hash we need to correct the byte-order.
|
|
// Currently this type is used for field Genesis of DBStateValue.
|
|
|
|
func (kv *BigEndianChainHash) SizeOf() int {
|
|
return chainhash.HashSize
|
|
}
|
|
|
|
func (kv *BigEndianChainHash) Pack(buf []byte, order binary.ByteOrder) ([]byte, error) {
|
|
offset := 0
|
|
hash := kv.CloneBytes()
|
|
// HACK: Instances of chainhash.Hash use the internal byte-order.
|
|
// Python scribe writes bytes of genesis hash in external byte-order.
|
|
internal.ReverseBytesInPlace(hash)
|
|
offset += copy(buf[offset:chainhash.HashSize], hash[:])
|
|
return buf[offset:], nil
|
|
}
|
|
|
|
func (kv *BigEndianChainHash) Unpack(buf []byte, order binary.ByteOrder) ([]byte, error) {
|
|
offset := 0
|
|
offset += copy(kv.Hash[:], buf[offset:32])
|
|
// HACK: Instances of chainhash.Hash use the internal byte-order.
|
|
// Python scribe writes bytes of genesis hash in external byte-order.
|
|
internal.ReverseBytesInPlace(kv.Hash[:])
|
|
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
|
|
},
|
|
}
|