2015-05-01 08:28:01 +02:00
|
|
|
// Copyright (c) 2014 The btcsuite developers
|
Reimagine btcjson package with version 2.
This commit implements a reimagining of the way the btcjson package
functions based upon how the project has evolved and lessons learned while
using it since it was first written. It therefore contains significant
changes to the API. For now, it has been implemented in a v2 subdirectory
to prevent breaking existing callers, but the ultimate goal is to update
all callers to use the new version and then to replace the old API with
the new one.
This also removes the need for the btcws completely since those commands
have been rolled in.
The following is an overview of the changes and some reasoning behind why
they were made:
- The infrastructure has been completely changed to be reflection based instead
of requiring thousands and thousands of lines of manual, and therefore error
prone, marshal/unmarshal code
- This makes it much easier to add new commands without making marshalling
mistakes since it is simply a struct definition and a call to register that
new struct (plus a trivial New<foo>Cmd function and tests, of course)
- It also makes it much easier to gain a lot of information from simply
looking at the struct definition which was previously not possible
such as the order of the parameters, which parameters are required
versus optional, and what the default values for optional parameters
are
- Each command now has usage flags associated with them that can be
queried which are intended to allow classification of the commands such
as for chain server and wallet server and websocket-only
- The help infrastructure has been completely redone to provide automatic
generation with caller provided description map and result types. This
is in contrast to the previous method of providing the help directly
which meant it would end up in the binary of anything that imported the
package
- Many of the structs have been renamed to use the terminology from the
JSON-RPC
specification:
- RawCmd/Message is now only a single struct named Request to reflect the fact
it is a JSON-RPC request
- Error is now called RPCError to reflect the fact it is specifically an RPC
error as opposed to many of the other errors that are possible
- All RPC error codes except the standard JSON-RPC 2.0 errors have been
converted from full structs to only codes since an audit of the codebase
has shown that the messages are overridden the vast majority of the time
with specifics (as they should be) and removing them also avoids the
temptation to return non-specific, and therefore not as helpful, error
messages
- There is now an Error which provides a type assertable error with
error codes so callers can better ascertain failure reasons
programatically
- The ID is no longer a part of the command and is instead specified at the time
the command is marshalled into a JSON-RPC request. This aligns better with
the way JSON-RPC functions since it is the caller who manages the ID that is
sent with any given _request_, not the package
- All <Foo>Cmd structs now treat non-pointers as required fields and pointers as
optional fields
- All New<Foo>Cmd functions now accept the exact number of parameters, with
pointers to the appropriate type for optional parameters
- This is preferrable to the old vararg syntax since it means the code will
fail to compile if the optional arguments are changed now which helps
prevent errors creep in over time from missed modifications to optional args
- All of the connection related code has been completely eliminated since this
package is not intended to used a client, rather it is intended to provide
the infrastructure needed to marshal/unmarshal Bitcoin-specific JSON-RPC
requests and replies from static types
- The btcrpcclient package provides a robust client with connection management
and higher-level types that in turn uses the primitives provided by this
package
- Even if the caller does not wish to use btcrpcclient for some reason, they
should still be responsible for connection management since they might want
to use any number of connection features which the package would not
necessarily support
- Synced a few of the commands that have added new optional fields that
have since been added to Bitcoin Core
- Includes all of the commands and notifications that were previously in
btcws
- Now provides 100% test coverage with parallel tests
- The code is completely golint and go vet clean
This has the side effect of addressing nearly everything in, and therefore
closes #26.
Also fixes #18 and closes #19.
2014-12-31 08:05:03 +01:00
|
|
|
// Use of this source code is governed by an ISC
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package btcjson
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"reflect"
|
|
|
|
"sort"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
|
|
|
// UsageFlag define flags that specify additional properties about the
|
|
|
|
// circumstances under which a command can be used.
|
|
|
|
type UsageFlag uint32
|
|
|
|
|
|
|
|
const (
|
|
|
|
// UFWalletOnly indicates that the command can only be used with an RPC
|
|
|
|
// server that supports wallet commands.
|
|
|
|
UFWalletOnly UsageFlag = 1 << iota
|
|
|
|
|
|
|
|
// UFWebsocketOnly indicates that the command can only be used when
|
|
|
|
// communicating with an RPC server over websockets. This typically
|
|
|
|
// applies to notifications and notification registration functions
|
|
|
|
// since neiher makes since when using a single-shot HTTP-POST request.
|
|
|
|
UFWebsocketOnly
|
|
|
|
|
|
|
|
// UFNotification indicates that the command is actually a notification.
|
|
|
|
// This means when it is marshalled, the ID must be nil.
|
|
|
|
UFNotification
|
|
|
|
|
|
|
|
// highestUsageFlagBit is the maximum usage flag bit and is used in the
|
|
|
|
// stringer and tests to ensure all of the above constants have been
|
|
|
|
// tested.
|
|
|
|
highestUsageFlagBit
|
|
|
|
)
|
|
|
|
|
|
|
|
// Map of UsageFlag values back to their constant names for pretty printing.
|
|
|
|
var usageFlagStrings = map[UsageFlag]string{
|
|
|
|
UFWalletOnly: "UFWalletOnly",
|
|
|
|
UFWebsocketOnly: "UFWebsocketOnly",
|
|
|
|
UFNotification: "UFNotification",
|
|
|
|
}
|
|
|
|
|
|
|
|
// String returns the UsageFlag in human-readable form.
|
|
|
|
func (fl UsageFlag) String() string {
|
|
|
|
// No flags are set.
|
|
|
|
if fl == 0 {
|
|
|
|
return "0x0"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add individual bit flags.
|
|
|
|
s := ""
|
|
|
|
for flag := UFWalletOnly; flag < highestUsageFlagBit; flag <<= 1 {
|
|
|
|
if fl&flag == flag {
|
|
|
|
s += usageFlagStrings[flag] + "|"
|
|
|
|
fl -= flag
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add remaining value as raw hex.
|
|
|
|
s = strings.TrimRight(s, "|")
|
|
|
|
if fl != 0 {
|
|
|
|
s += "|0x" + strconv.FormatUint(uint64(fl), 16)
|
|
|
|
}
|
|
|
|
s = strings.TrimLeft(s, "|")
|
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
|
|
|
// methodInfo keeps track of information about each registered method such as
|
|
|
|
// the parameter information.
|
|
|
|
type methodInfo struct {
|
|
|
|
maxParams int
|
|
|
|
numReqParams int
|
|
|
|
numOptParams int
|
|
|
|
defaults map[int]reflect.Value
|
|
|
|
flags UsageFlag
|
|
|
|
usage string
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
// These fields are used to map the registered types to method names.
|
|
|
|
registerLock sync.RWMutex
|
|
|
|
methodToConcreteType = make(map[string]reflect.Type)
|
|
|
|
methodToInfo = make(map[string]methodInfo)
|
|
|
|
concreteTypeToMethod = make(map[reflect.Type]string)
|
|
|
|
)
|
|
|
|
|
|
|
|
// baseKindString returns the base kind for a given reflect.Type after
|
|
|
|
// indirecting through all pointers.
|
|
|
|
func baseKindString(rt reflect.Type) string {
|
|
|
|
numIndirects := 0
|
|
|
|
for rt.Kind() == reflect.Ptr {
|
|
|
|
numIndirects++
|
|
|
|
rt = rt.Elem()
|
|
|
|
}
|
|
|
|
|
|
|
|
return fmt.Sprintf("%s%s", strings.Repeat("*", numIndirects), rt.Kind())
|
|
|
|
}
|
|
|
|
|
|
|
|
// isAcceptableKind returns whether or not the passed field type is a supported
|
|
|
|
// type. It is called after the first pointer indirection, so further pointers
|
|
|
|
// are not supported.
|
|
|
|
func isAcceptableKind(kind reflect.Kind) bool {
|
|
|
|
switch kind {
|
|
|
|
case reflect.Chan:
|
|
|
|
fallthrough
|
|
|
|
case reflect.Complex64:
|
|
|
|
fallthrough
|
|
|
|
case reflect.Complex128:
|
|
|
|
fallthrough
|
|
|
|
case reflect.Func:
|
|
|
|
fallthrough
|
|
|
|
case reflect.Ptr:
|
|
|
|
fallthrough
|
|
|
|
case reflect.Interface:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterCmd registers a new command that will automatically marshal to and
|
|
|
|
// from JSON-RPC with full type checking and positional parameter support. It
|
|
|
|
// also accepts usage flags which identify the circumstances under which the
|
|
|
|
// command can be used.
|
|
|
|
//
|
|
|
|
// This package automatically registers all of the exported commands by default
|
|
|
|
// using this function, however it is also exported so callers can easily
|
|
|
|
// register custom types.
|
|
|
|
//
|
|
|
|
// The type format is very strict since it needs to be able to automatically
|
|
|
|
// marshal to and from JSON-RPC 1.0. The following enumerates the requirements:
|
|
|
|
//
|
2015-04-18 00:10:15 +02:00
|
|
|
// - The provided command must be a single pointer to a struct
|
|
|
|
// - All fields must be exported
|
|
|
|
// - The order of the positional parameters in the marshalled JSON will be in
|
|
|
|
// the same order as declared in the struct definition
|
|
|
|
// - Struct embedding is not supported
|
|
|
|
// - Struct fields may NOT be channels, functions, complex, or interface
|
|
|
|
// - A field in the provided struct with a pointer is treated as optional
|
|
|
|
// - Multiple indirections (i.e **int) are not supported
|
|
|
|
// - Once the first optional field (pointer) is encountered, the remaining
|
|
|
|
// fields must also be optional fields (pointers) as required by positional
|
|
|
|
// params
|
|
|
|
// - A field that has a 'jsonrpcdefault' struct tag must be an optional field
|
|
|
|
// (pointer)
|
Reimagine btcjson package with version 2.
This commit implements a reimagining of the way the btcjson package
functions based upon how the project has evolved and lessons learned while
using it since it was first written. It therefore contains significant
changes to the API. For now, it has been implemented in a v2 subdirectory
to prevent breaking existing callers, but the ultimate goal is to update
all callers to use the new version and then to replace the old API with
the new one.
This also removes the need for the btcws completely since those commands
have been rolled in.
The following is an overview of the changes and some reasoning behind why
they were made:
- The infrastructure has been completely changed to be reflection based instead
of requiring thousands and thousands of lines of manual, and therefore error
prone, marshal/unmarshal code
- This makes it much easier to add new commands without making marshalling
mistakes since it is simply a struct definition and a call to register that
new struct (plus a trivial New<foo>Cmd function and tests, of course)
- It also makes it much easier to gain a lot of information from simply
looking at the struct definition which was previously not possible
such as the order of the parameters, which parameters are required
versus optional, and what the default values for optional parameters
are
- Each command now has usage flags associated with them that can be
queried which are intended to allow classification of the commands such
as for chain server and wallet server and websocket-only
- The help infrastructure has been completely redone to provide automatic
generation with caller provided description map and result types. This
is in contrast to the previous method of providing the help directly
which meant it would end up in the binary of anything that imported the
package
- Many of the structs have been renamed to use the terminology from the
JSON-RPC
specification:
- RawCmd/Message is now only a single struct named Request to reflect the fact
it is a JSON-RPC request
- Error is now called RPCError to reflect the fact it is specifically an RPC
error as opposed to many of the other errors that are possible
- All RPC error codes except the standard JSON-RPC 2.0 errors have been
converted from full structs to only codes since an audit of the codebase
has shown that the messages are overridden the vast majority of the time
with specifics (as they should be) and removing them also avoids the
temptation to return non-specific, and therefore not as helpful, error
messages
- There is now an Error which provides a type assertable error with
error codes so callers can better ascertain failure reasons
programatically
- The ID is no longer a part of the command and is instead specified at the time
the command is marshalled into a JSON-RPC request. This aligns better with
the way JSON-RPC functions since it is the caller who manages the ID that is
sent with any given _request_, not the package
- All <Foo>Cmd structs now treat non-pointers as required fields and pointers as
optional fields
- All New<Foo>Cmd functions now accept the exact number of parameters, with
pointers to the appropriate type for optional parameters
- This is preferrable to the old vararg syntax since it means the code will
fail to compile if the optional arguments are changed now which helps
prevent errors creep in over time from missed modifications to optional args
- All of the connection related code has been completely eliminated since this
package is not intended to used a client, rather it is intended to provide
the infrastructure needed to marshal/unmarshal Bitcoin-specific JSON-RPC
requests and replies from static types
- The btcrpcclient package provides a robust client with connection management
and higher-level types that in turn uses the primitives provided by this
package
- Even if the caller does not wish to use btcrpcclient for some reason, they
should still be responsible for connection management since they might want
to use any number of connection features which the package would not
necessarily support
- Synced a few of the commands that have added new optional fields that
have since been added to Bitcoin Core
- Includes all of the commands and notifications that were previously in
btcws
- Now provides 100% test coverage with parallel tests
- The code is completely golint and go vet clean
This has the side effect of addressing nearly everything in, and therefore
closes #26.
Also fixes #18 and closes #19.
2014-12-31 08:05:03 +01:00
|
|
|
//
|
|
|
|
// NOTE: This function only needs to be able to examine the structure of the
|
|
|
|
// passed struct, so it does not need to be an actual instance. Therefore, it
|
|
|
|
// is recommended to simply pass a nil pointer cast to the appropriate type.
|
|
|
|
// For example, (*FooCmd)(nil).
|
|
|
|
func RegisterCmd(method string, cmd interface{}, flags UsageFlag) error {
|
|
|
|
registerLock.Lock()
|
|
|
|
defer registerLock.Unlock()
|
|
|
|
|
|
|
|
if _, ok := methodToConcreteType[method]; ok {
|
|
|
|
str := fmt.Sprintf("method %q is already registered", method)
|
|
|
|
return makeError(ErrDuplicateMethod, str)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure that no unrecognized flag bits were specified.
|
|
|
|
if ^(highestUsageFlagBit-1)&flags != 0 {
|
|
|
|
str := fmt.Sprintf("invalid usage flags specified for method "+
|
|
|
|
"%s: %v", method, flags)
|
|
|
|
return makeError(ErrInvalidUsageFlags, str)
|
|
|
|
}
|
|
|
|
|
|
|
|
rtp := reflect.TypeOf(cmd)
|
|
|
|
if rtp.Kind() != reflect.Ptr {
|
|
|
|
str := fmt.Sprintf("type must be *struct not '%s (%s)'", rtp,
|
|
|
|
rtp.Kind())
|
|
|
|
return makeError(ErrInvalidType, str)
|
|
|
|
}
|
|
|
|
rt := rtp.Elem()
|
|
|
|
if rt.Kind() != reflect.Struct {
|
|
|
|
str := fmt.Sprintf("type must be *struct not '%s (*%s)'",
|
|
|
|
rtp, rt.Kind())
|
|
|
|
return makeError(ErrInvalidType, str)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Enumerate the struct fields to validate them and gather parameter
|
|
|
|
// information.
|
|
|
|
numFields := rt.NumField()
|
|
|
|
numOptFields := 0
|
|
|
|
defaults := make(map[int]reflect.Value)
|
|
|
|
for i := 0; i < numFields; i++ {
|
|
|
|
rtf := rt.Field(i)
|
|
|
|
if rtf.Anonymous {
|
|
|
|
str := fmt.Sprintf("embedded fields are not supported "+
|
|
|
|
"(field name: %q)", rtf.Name)
|
|
|
|
return makeError(ErrEmbeddedType, str)
|
|
|
|
}
|
|
|
|
if rtf.PkgPath != "" {
|
|
|
|
str := fmt.Sprintf("unexported fields are not supported "+
|
|
|
|
"(field name: %q)", rtf.Name)
|
|
|
|
return makeError(ErrUnexportedField, str)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Disallow types that can't be JSON encoded. Also, determine
|
|
|
|
// if the field is optional based on it being a pointer.
|
|
|
|
var isOptional bool
|
|
|
|
switch kind := rtf.Type.Kind(); kind {
|
|
|
|
case reflect.Ptr:
|
|
|
|
isOptional = true
|
|
|
|
kind = rtf.Type.Elem().Kind()
|
|
|
|
fallthrough
|
|
|
|
default:
|
|
|
|
if !isAcceptableKind(kind) {
|
|
|
|
str := fmt.Sprintf("unsupported field type "+
|
|
|
|
"'%s (%s)' (field name %q)", rtf.Type,
|
|
|
|
baseKindString(rtf.Type), rtf.Name)
|
|
|
|
return makeError(ErrUnsupportedFieldType, str)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Count the optional fields and ensure all fields after the
|
|
|
|
// first optional field are also optional.
|
|
|
|
if isOptional {
|
|
|
|
numOptFields++
|
|
|
|
} else {
|
|
|
|
if numOptFields > 0 {
|
|
|
|
str := fmt.Sprintf("all fields after the first "+
|
|
|
|
"optional field must also be optional "+
|
|
|
|
"(field name %q)", rtf.Name)
|
|
|
|
return makeError(ErrNonOptionalField, str)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the default value can be unsmarshalled into the type
|
|
|
|
// and that defaults are only specified for optional fields.
|
|
|
|
if tag := rtf.Tag.Get("jsonrpcdefault"); tag != "" {
|
|
|
|
if !isOptional {
|
|
|
|
str := fmt.Sprintf("required fields must not "+
|
|
|
|
"have a default specified (field name "+
|
|
|
|
"%q)", rtf.Name)
|
|
|
|
return makeError(ErrNonOptionalDefault, str)
|
|
|
|
}
|
|
|
|
|
|
|
|
rvf := reflect.New(rtf.Type.Elem())
|
|
|
|
err := json.Unmarshal([]byte(tag), rvf.Interface())
|
|
|
|
if err != nil {
|
|
|
|
str := fmt.Sprintf("default value of %q is "+
|
|
|
|
"the wrong type (field name %q)", tag,
|
|
|
|
rtf.Name)
|
|
|
|
return makeError(ErrMismatchedDefault, str)
|
|
|
|
}
|
|
|
|
defaults[i] = rvf
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the registration maps.
|
|
|
|
methodToConcreteType[method] = rtp
|
|
|
|
methodToInfo[method] = methodInfo{
|
|
|
|
maxParams: numFields,
|
|
|
|
numReqParams: numFields - numOptFields,
|
|
|
|
numOptParams: numOptFields,
|
|
|
|
defaults: defaults,
|
|
|
|
flags: flags,
|
|
|
|
}
|
|
|
|
concreteTypeToMethod[rtp] = method
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// MustRegisterCmd performs the same function as RegisterCmd except it panics
|
|
|
|
// if there is an error. This should only be called from package init
|
|
|
|
// functions.
|
|
|
|
func MustRegisterCmd(method string, cmd interface{}, flags UsageFlag) {
|
|
|
|
if err := RegisterCmd(method, cmd, flags); err != nil {
|
|
|
|
panic(fmt.Sprintf("failed to register type %q: %v\n", method,
|
|
|
|
err))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// RegisteredCmdMethods returns a sorted list of methods for all registered
|
|
|
|
// commands.
|
|
|
|
func RegisteredCmdMethods() []string {
|
|
|
|
registerLock.Lock()
|
|
|
|
defer registerLock.Unlock()
|
|
|
|
|
|
|
|
methods := make([]string, 0, len(methodToInfo))
|
|
|
|
for k := range methodToInfo {
|
|
|
|
methods = append(methods, k)
|
|
|
|
}
|
|
|
|
|
2020-05-13 14:58:39 +02:00
|
|
|
sort.Strings(methods)
|
Reimagine btcjson package with version 2.
This commit implements a reimagining of the way the btcjson package
functions based upon how the project has evolved and lessons learned while
using it since it was first written. It therefore contains significant
changes to the API. For now, it has been implemented in a v2 subdirectory
to prevent breaking existing callers, but the ultimate goal is to update
all callers to use the new version and then to replace the old API with
the new one.
This also removes the need for the btcws completely since those commands
have been rolled in.
The following is an overview of the changes and some reasoning behind why
they were made:
- The infrastructure has been completely changed to be reflection based instead
of requiring thousands and thousands of lines of manual, and therefore error
prone, marshal/unmarshal code
- This makes it much easier to add new commands without making marshalling
mistakes since it is simply a struct definition and a call to register that
new struct (plus a trivial New<foo>Cmd function and tests, of course)
- It also makes it much easier to gain a lot of information from simply
looking at the struct definition which was previously not possible
such as the order of the parameters, which parameters are required
versus optional, and what the default values for optional parameters
are
- Each command now has usage flags associated with them that can be
queried which are intended to allow classification of the commands such
as for chain server and wallet server and websocket-only
- The help infrastructure has been completely redone to provide automatic
generation with caller provided description map and result types. This
is in contrast to the previous method of providing the help directly
which meant it would end up in the binary of anything that imported the
package
- Many of the structs have been renamed to use the terminology from the
JSON-RPC
specification:
- RawCmd/Message is now only a single struct named Request to reflect the fact
it is a JSON-RPC request
- Error is now called RPCError to reflect the fact it is specifically an RPC
error as opposed to many of the other errors that are possible
- All RPC error codes except the standard JSON-RPC 2.0 errors have been
converted from full structs to only codes since an audit of the codebase
has shown that the messages are overridden the vast majority of the time
with specifics (as they should be) and removing them also avoids the
temptation to return non-specific, and therefore not as helpful, error
messages
- There is now an Error which provides a type assertable error with
error codes so callers can better ascertain failure reasons
programatically
- The ID is no longer a part of the command and is instead specified at the time
the command is marshalled into a JSON-RPC request. This aligns better with
the way JSON-RPC functions since it is the caller who manages the ID that is
sent with any given _request_, not the package
- All <Foo>Cmd structs now treat non-pointers as required fields and pointers as
optional fields
- All New<Foo>Cmd functions now accept the exact number of parameters, with
pointers to the appropriate type for optional parameters
- This is preferrable to the old vararg syntax since it means the code will
fail to compile if the optional arguments are changed now which helps
prevent errors creep in over time from missed modifications to optional args
- All of the connection related code has been completely eliminated since this
package is not intended to used a client, rather it is intended to provide
the infrastructure needed to marshal/unmarshal Bitcoin-specific JSON-RPC
requests and replies from static types
- The btcrpcclient package provides a robust client with connection management
and higher-level types that in turn uses the primitives provided by this
package
- Even if the caller does not wish to use btcrpcclient for some reason, they
should still be responsible for connection management since they might want
to use any number of connection features which the package would not
necessarily support
- Synced a few of the commands that have added new optional fields that
have since been added to Bitcoin Core
- Includes all of the commands and notifications that were previously in
btcws
- Now provides 100% test coverage with parallel tests
- The code is completely golint and go vet clean
This has the side effect of addressing nearly everything in, and therefore
closes #26.
Also fixes #18 and closes #19.
2014-12-31 08:05:03 +01:00
|
|
|
return methods
|
|
|
|
}
|