dcrd/dcrjson/cmdparse.go
Dave Collins b6d426241d blockchain: Rework to use new db interface.
This commit is the first stage of several that are planned to convert
the blockchain package into a concurrent safe package that will
ultimately allow support for multi-peer download and concurrent chain
processing.  The goal is to update btcd proper after each step so it can
take advantage of the enhancements as they are developed.

In addition to the aforementioned benefit, this staged approach has been
chosen since it is absolutely critical to maintain consensus.
Separating the changes into several stages makes it easier for reviewers
to logically follow what is happening and therefore helps prevent
consensus bugs.  Naturally there are significant automated tests to help
prevent consensus issues as well.

The main focus of this stage is to convert the blockchain package to use
the new database interface and implement the chain-related functionality
which it no longer handles.  It also aims to improve efficiency in
various areas by making use of the new database and chain capabilities.

The following is an overview of the chain changes:

- Update to use the new database interface
- Add chain-related functionality that the old database used to handle
  - Main chain structure and state
  - Transaction spend tracking
- Implement a new pruned unspent transaction output (utxo) set
  - Provides efficient direct access to the unspent transaction outputs
  - Uses a domain specific compression algorithm that understands the
    standard transaction scripts in order to significantly compress them
  - Removes reliance on the transaction index and paves the way toward
    eventually enabling block pruning
- Modify the New function to accept a Config struct instead of
  inidividual parameters
- Replace the old TxStore type with a new UtxoViewpoint type that makes
  use of the new pruned utxo set
- Convert code to treat the new UtxoViewpoint as a rolling view that is
  used between connects and disconnects to improve efficiency
- Make best chain state always set when the chain instance is created
  - Remove now unnecessary logic for dealing with unset best state
- Make all exported functions concurrent safe
  - Currently using a single chain state lock as it provides a straight
    forward and easy to review path forward however this can be improved
    with more fine grained locking
- Optimize various cases where full blocks were being loaded when only
  the header is needed to help reduce the I/O load
- Add the ability for callers to get a snapshot of the current best
  chain stats in a concurrent safe fashion
  - Does not block callers while new blocks are being processed
- Make error messages that reference transaction outputs consistently
  use <transaction hash>:<output index>
- Introduce a new AssertError type an convert internal consistency
  checks to use it
- Update tests and examples to reflect the changes
- Add a full suite of tests to ensure correct functionality of the new
  code

The following is an overview of the btcd changes:

- Update to use the new database and chain interfaces
- Temporarily remove all code related to the transaction index
- Temporarily remove all code related to the address index
- Convert all code that uses transaction stores to use the new utxo
  view
- Rework several calls that required the block manager for safe
  concurrency to use the chain package directly now that it is
  concurrent safe
- Change all calls to obtain the best hash to use the new best state
  snapshot capability from the chain package
- Remove workaround for limits on fetching height ranges since the new
  database interface no longer imposes them
- Correct the gettxout RPC handler to return the best chain hash as
  opposed the hash the txout was found in
- Optimize various RPC handlers:
  - Change several of the RPC handlers to use the new chain snapshot
    capability to avoid needlessly loading data
  - Update several handlers to use new functionality to avoid accessing
    the block manager so they are able to return the data without
    blocking when the server is busy processing blocks
  - Update non-verbose getblock to avoid deserialization and
    serialization overhead
  - Update getblockheader to request the block height directly from
    chain and only load the header
  - Update getdifficulty to use the new cached data from chain
  - Update getmininginfo to use the new cached data from chain
  - Update non-verbose getrawtransaction to avoid deserialization and
    serialization overhead
  - Update gettxout to use the new utxo store versus loading
    full transactions using the transaction index

The following is an overview of the utility changes:
- Update addblock to use the new database and chain interfaces
- Update findcheckpoint to use the new database and chain interfaces
- Remove the dropafter utility which is no longer supported

NOTE: The transaction index and address index will be reimplemented in
another commit.
2016-08-18 15:42:18 -04:00

552 lines
18 KiB
Go

// Copyright (c) 2014 The btcsuite developers
// Copyright (c) 2015-2016 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package dcrjson
import (
"encoding/json"
"fmt"
"reflect"
"strconv"
"strings"
)
// makeParams creates a slice of interface values for the given struct.
func makeParams(rt reflect.Type, rv reflect.Value) []interface{} {
numFields := rt.NumField()
params := make([]interface{}, 0, numFields)
for i := 0; i < numFields; i++ {
rtf := rt.Field(i)
rvf := rv.Field(i)
if rtf.Type.Kind() == reflect.Ptr {
if rvf.IsNil() {
break
}
rvf.Elem()
}
params = append(params, rvf.Interface())
}
return params
}
// MarshalCmd marshals the passed command to a JSON-RPC request byte slice that
// is suitable for transmission to an RPC server. The provided command type
// must be a registered type. All commands provided by this package are
// registered by default.
func MarshalCmd(id interface{}, cmd interface{}) ([]byte, error) {
// Look up the cmd type and error out if not registered.
rt := reflect.TypeOf(cmd)
registerLock.RLock()
method, ok := concreteTypeToMethod[rt]
registerLock.RUnlock()
if !ok {
str := fmt.Sprintf("%q is not registered", method)
return nil, makeError(ErrUnregisteredMethod, str)
}
// The provided command must not be nil.
rv := reflect.ValueOf(cmd)
if rv.IsNil() {
str := fmt.Sprint("the specified command is nil")
return nil, makeError(ErrInvalidType, str)
}
// Create a slice of interface values in the order of the struct fields
// while respecting pointer fields as optional params and only adding
// them if they are non-nil.
params := makeParams(rt.Elem(), rv.Elem())
// Generate and marshal the final JSON-RPC request.
rawCmd, err := NewRequest(id, method, params)
if err != nil {
return nil, err
}
return json.Marshal(rawCmd)
}
// checkNumParams ensures the supplied number of params is at least the minimum
// required number for the command and less than the maximum allowed.
func checkNumParams(numParams int, info *methodInfo) error {
if numParams < info.numReqParams || numParams > info.maxParams {
if info.numReqParams == info.maxParams {
str := fmt.Sprintf("wrong number of params (expected "+
"%d, received %d)", info.numReqParams,
numParams)
return makeError(ErrNumParams, str)
}
str := fmt.Sprintf("wrong number of params (expected "+
"between %d and %d, received %d)", info.numReqParams,
info.maxParams, numParams)
return makeError(ErrNumParams, str)
}
return nil
}
// populateDefaults populates default values into any remaining optional struct
// fields that did not have parameters explicitly provided. The caller should
// have previously checked that the number of parameters being passed is at
// least the required number of parameters to avoid unnecessary work in this
// function, but since required fields never have default values, it will work
// properly even without the check.
func populateDefaults(numParams int, info *methodInfo, rv reflect.Value) {
// When there are no more parameters left in the supplied parameters,
// any remaining struct fields must be optional. Thus, populate them
// with their associated default value as needed.
for i := numParams; i < info.maxParams; i++ {
rvf := rv.Field(i)
if defaultVal, ok := info.defaults[i]; ok {
rvf.Set(defaultVal)
}
}
}
// UnmarshalCmd unmarshals a JSON-RPC request into a suitable concrete command
// so long as the method type contained within the marshalled request is
// registered.
func UnmarshalCmd(r *Request) (interface{}, error) {
registerLock.RLock()
rtp, ok := methodToConcreteType[r.Method]
info := methodToInfo[r.Method]
registerLock.RUnlock()
if !ok {
str := fmt.Sprintf("%q is not registered", r.Method)
return nil, makeError(ErrUnregisteredMethod, str)
}
rt := rtp.Elem()
rvp := reflect.New(rt)
rv := rvp.Elem()
// Ensure the number of parameters are correct.
numParams := len(r.Params)
if err := checkNumParams(numParams, &info); err != nil {
return nil, err
}
// Loop through each of the struct fields and unmarshal the associated
// parameter into them.
for i := 0; i < numParams; i++ {
rvf := rv.Field(i)
// Unmarshal the parameter into the struct field.
concreteVal := rvf.Addr().Interface()
if err := json.Unmarshal(r.Params[i], &concreteVal); err != nil {
// The most common error is the wrong type, so
// explicitly detect that error and make it nicer.
fieldName := strings.ToLower(rt.Field(i).Name)
if jerr, ok := err.(*json.UnmarshalTypeError); ok {
str := fmt.Sprintf("parameter #%d '%s' must "+
"be type %v (got %v)", i+1, fieldName,
jerr.Type, jerr.Value)
return nil, makeError(ErrInvalidType, str)
}
// Fallback to showing the underlying error.
str := fmt.Sprintf("parameter #%d '%s' failed to "+
"unmarshal: %v", i+1, fieldName, err)
return nil, makeError(ErrInvalidType, str)
}
}
// When there are less supplied parameters than the total number of
// params, any remaining struct fields must be optional. Thus, populate
// them with their associated default value as needed.
if numParams < info.maxParams {
populateDefaults(numParams, &info, rv)
}
return rvp.Interface(), nil
}
// isNumeric returns whether the passed reflect kind is a signed or unsigned
// integer of any magnitude or a float of any magnitude.
func isNumeric(kind reflect.Kind) bool {
switch kind {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64, reflect.Float32, reflect.Float64:
return true
}
return false
}
// typesMaybeCompatible returns whether the source type can possibly be
// assigned to the destination type. This is intended as a relatively quick
// check to weed out obviously invalid conversions.
func typesMaybeCompatible(dest reflect.Type, src reflect.Type) bool {
// The same types are obviously compatible.
if dest == src {
return true
}
// When both types are numeric, they are potentially compatibile.
srcKind := src.Kind()
destKind := dest.Kind()
if isNumeric(destKind) && isNumeric(srcKind) {
return true
}
if srcKind == reflect.String {
// Strings can potentially be converted to numeric types.
if isNumeric(destKind) {
return true
}
switch destKind {
// Strings can potentially be converted to bools by
// strconv.ParseBool.
case reflect.Bool:
return true
// Strings can be converted to any other type which has as
// underlying type of string.
case reflect.String:
return true
// Strings can potentially be converted to arrays, slice,
// structs, and maps via json.Unmarshal.
case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map:
return true
}
}
return false
}
// baseType returns the type of the argument after indirecting through all
// pointers along with how many indirections were necessary.
func baseType(arg reflect.Type) (reflect.Type, int) {
var numIndirects int
for arg.Kind() == reflect.Ptr {
arg = arg.Elem()
numIndirects++
}
return arg, numIndirects
}
// assignField is the main workhorse for the NewCmd function which handles
// assigning the provided source value to the destination field. It supports
// direct type assignments, indirection, conversion of numeric types, and
// unmarshaling of strings into arrays, slices, structs, and maps via
// json.Unmarshal.
func assignField(paramNum int, fieldName string, dest reflect.Value, src reflect.Value) error {
// Just error now when the types have no chance of being compatible.
destBaseType, destIndirects := baseType(dest.Type())
srcBaseType, srcIndirects := baseType(src.Type())
if !typesMaybeCompatible(destBaseType, srcBaseType) {
str := fmt.Sprintf("parameter #%d '%s' must be type %v (got "+
"%v)", paramNum, fieldName, destBaseType, srcBaseType)
return makeError(ErrInvalidType, str)
}
// Check if it's possible to simply set the dest to the provided source.
// This is the case when the base types are the same or they are both
// pointers that can be indirected to be the same without needing to
// create pointers for the destination field.
if destBaseType == srcBaseType && srcIndirects >= destIndirects {
for i := 0; i < srcIndirects-destIndirects; i++ {
src = src.Elem()
}
dest.Set(src)
return nil
}
// When the destination has more indirects than the source, the extra
// pointers have to be created. Only create enough pointers to reach
// the same level of indirection as the source so the dest can simply be
// set to the provided source when the types are the same.
destIndirectsRemaining := destIndirects
if destIndirects > srcIndirects {
indirectDiff := destIndirects - srcIndirects
for i := 0; i < indirectDiff; i++ {
dest.Set(reflect.New(dest.Type().Elem()))
dest = dest.Elem()
destIndirectsRemaining--
}
}
if destBaseType == srcBaseType {
dest.Set(src)
return nil
}
// Make any remaining pointers needed to get to the base dest type since
// the above direct assign was not possible and conversions are done
// against the base types.
for i := 0; i < destIndirectsRemaining; i++ {
dest.Set(reflect.New(dest.Type().Elem()))
dest = dest.Elem()
}
// Indirect through to the base source value.
for src.Kind() == reflect.Ptr {
src = src.Elem()
}
// Perform supported type conversions.
switch src.Kind() {
// Source value is a signed integer of various magnitude.
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
switch dest.Kind() {
// Destination is a signed integer of various magnitude.
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
srcInt := src.Int()
if dest.OverflowInt(srcInt) {
str := fmt.Sprintf("parameter #%d '%s' "+
"overflows destination type %v",
paramNum, fieldName, destBaseType)
return makeError(ErrInvalidType, str)
}
dest.SetInt(srcInt)
// Destination is an unsigned integer of various magnitude.
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64:
srcInt := src.Int()
if srcInt < 0 || dest.OverflowUint(uint64(srcInt)) {
str := fmt.Sprintf("parameter #%d '%s' "+
"overflows destination type %v",
paramNum, fieldName, destBaseType)
return makeError(ErrInvalidType, str)
}
dest.SetUint(uint64(srcInt))
default:
str := fmt.Sprintf("parameter #%d '%s' must be type "+
"%v (got %v)", paramNum, fieldName, destBaseType,
srcBaseType)
return makeError(ErrInvalidType, str)
}
// Source value is an unsigned integer of various magnitude.
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64:
switch dest.Kind() {
// Destination is a signed integer of various magnitude.
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
srcUint := src.Uint()
if srcUint > uint64(1<<63)-1 {
str := fmt.Sprintf("parameter #%d '%s' "+
"overflows destination type %v",
paramNum, fieldName, destBaseType)
return makeError(ErrInvalidType, str)
}
if dest.OverflowInt(int64(srcUint)) {
str := fmt.Sprintf("parameter #%d '%s' "+
"overflows destination type %v",
paramNum, fieldName, destBaseType)
return makeError(ErrInvalidType, str)
}
dest.SetInt(int64(srcUint))
// Destination is an unsigned integer of various magnitude.
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64:
srcUint := src.Uint()
if dest.OverflowUint(srcUint) {
str := fmt.Sprintf("parameter #%d '%s' "+
"overflows destination type %v",
paramNum, fieldName, destBaseType)
return makeError(ErrInvalidType, str)
}
dest.SetUint(srcUint)
default:
str := fmt.Sprintf("parameter #%d '%s' must be type "+
"%v (got %v)", paramNum, fieldName, destBaseType,
srcBaseType)
return makeError(ErrInvalidType, str)
}
// Source value is a float.
case reflect.Float32, reflect.Float64:
destKind := dest.Kind()
if destKind != reflect.Float32 && destKind != reflect.Float64 {
str := fmt.Sprintf("parameter #%d '%s' must be type "+
"%v (got %v)", paramNum, fieldName, destBaseType,
srcBaseType)
return makeError(ErrInvalidType, str)
}
srcFloat := src.Float()
if dest.OverflowFloat(srcFloat) {
str := fmt.Sprintf("parameter #%d '%s' overflows "+
"destination type %v", paramNum, fieldName,
destBaseType)
return makeError(ErrInvalidType, str)
}
dest.SetFloat(srcFloat)
// Source value is a string.
case reflect.String:
switch dest.Kind() {
// String -> bool
case reflect.Bool:
b, err := strconv.ParseBool(src.String())
if err != nil {
str := fmt.Sprintf("parameter #%d '%s' must "+
"parse to a %v", paramNum, fieldName,
destBaseType)
return makeError(ErrInvalidType, str)
}
dest.SetBool(b)
// String -> signed integer of varying size.
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
srcInt, err := strconv.ParseInt(src.String(), 0, 0)
if err != nil {
str := fmt.Sprintf("parameter #%d '%s' must "+
"parse to a %v", paramNum, fieldName,
destBaseType)
return makeError(ErrInvalidType, str)
}
if dest.OverflowInt(srcInt) {
str := fmt.Sprintf("parameter #%d '%s' "+
"overflows destination type %v",
paramNum, fieldName, destBaseType)
return makeError(ErrInvalidType, str)
}
dest.SetInt(srcInt)
// String -> unsigned integer of varying size.
case reflect.Uint, reflect.Uint8, reflect.Uint16,
reflect.Uint32, reflect.Uint64:
srcUint, err := strconv.ParseUint(src.String(), 0, 0)
if err != nil {
str := fmt.Sprintf("parameter #%d '%s' must "+
"parse to a %v", paramNum, fieldName,
destBaseType)
return makeError(ErrInvalidType, str)
}
if dest.OverflowUint(srcUint) {
str := fmt.Sprintf("parameter #%d '%s' "+
"overflows destination type %v",
paramNum, fieldName, destBaseType)
return makeError(ErrInvalidType, str)
}
dest.SetUint(srcUint)
// String -> float of varying size.
case reflect.Float32, reflect.Float64:
srcFloat, err := strconv.ParseFloat(src.String(), 0)
if err != nil {
str := fmt.Sprintf("parameter #%d '%s' must "+
"parse to a %v", paramNum, fieldName,
destBaseType)
return makeError(ErrInvalidType, str)
}
if dest.OverflowFloat(srcFloat) {
str := fmt.Sprintf("parameter #%d '%s' "+
"overflows destination type %v",
paramNum, fieldName, destBaseType)
return makeError(ErrInvalidType, str)
}
dest.SetFloat(srcFloat)
// String -> string (typecast).
case reflect.String:
dest.SetString(src.String())
// String -> arrays, slices, structs, and maps via
// json.Unmarshal.
case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map:
concreteVal := dest.Addr().Interface()
err := json.Unmarshal([]byte(src.String()), &concreteVal)
if err != nil {
str := fmt.Sprintf("parameter #%d '%s' must "+
"be valid JSON which unsmarshals to a %v",
paramNum, fieldName, destBaseType)
return makeError(ErrInvalidType, str)
}
dest.Set(reflect.ValueOf(concreteVal).Elem())
}
}
return nil
}
// NewCmd provides a generic mechanism to create a new command that can marshal
// to a JSON-RPC request while respecting the requirements of the provided
// method. The method must have been registered with the package already along
// with its type definition. All methods associated with the commands exported
// by this package are already registered by default.
//
// The arguments are most efficient when they are the exact same type as the
// underlying field in the command struct associated with the the method,
// however this function also will perform a variety of conversions to make it
// more flexible. This allows, for example, command line args which are strings
// to be passed unaltered. In particular, the following conversions are
// supported:
//
// - Conversion between any size signed or unsigned integer so long as the
// value does not overflow the destination type
// - Conversion between float32 and float64 so long as the value does not
// overflow the destination type
// - Conversion from string to boolean for everything strconv.ParseBool
// recognizes
// - Conversion from string to any size integer for everything
// strconv.ParseInt and strconv.ParseUint recognizes
// - Conversion from string to any size float for everything
// strconv.ParseFloat recognizes
// - Conversion from string to arrays, slices, structs, and maps by treating
// the string as marshalled JSON and calling json.Unmarshal into the
// destination field
func NewCmd(method string, args ...interface{}) (interface{}, error) {
// Look up details about the provided method. Any methods that aren't
// registered are an error.
registerLock.RLock()
rtp, ok := methodToConcreteType[method]
info := methodToInfo[method]
registerLock.RUnlock()
if !ok {
str := fmt.Sprintf("%q is not registered", method)
return nil, makeError(ErrUnregisteredMethod, str)
}
// Ensure the number of parameters are correct.
numParams := len(args)
if err := checkNumParams(numParams, &info); err != nil {
return nil, err
}
// Create the appropriate command type for the method. Since all types
// are enforced to be a pointer to a struct at registration time, it's
// safe to indirect to the struct now.
rvp := reflect.New(rtp.Elem())
rv := rvp.Elem()
rt := rtp.Elem()
// Loop through each of the struct fields and assign the associated
// parameter into them after checking its type validity.
for i := 0; i < numParams; i++ {
// Attempt to assign each of the arguments to the according
// struct field.
rvf := rv.Field(i)
fieldName := strings.ToLower(rt.Field(i).Name)
err := assignField(i+1, fieldName, rvf, reflect.ValueOf(args[i]))
if err != nil {
return nil, err
}
}
return rvp.Interface(), nil
}