Skip to content

Commit

Permalink
[Chore] Backport GetBlockVerbosity Fixes & Add getbalances RPC client…
Browse files Browse the repository at this point in the history
… command (#389)

* Revert "Fix compatibility with linters"

This reverts commit fd438ce.

* Reverted bad getblock

* fix conflicts

* Backport getBlockVerbosity fixes

* included more results

* Typo

* Update json_rpc_api.md

updated docs for getblock-verbosity fixes

* Add getbalances RPC client command

* Allow the API to accept either an integer or bool for getblock verbosity

* Rename b to v

* Support better handling of int/bool types

* Fix bad comment

* Better clarity on getBlock docs

* Fix typo >.<

Co-authored-by: quest <[email protected]>
  • Loading branch information
JettScythe and zquestz authored Jul 22, 2020
1 parent 695c407 commit 7244caf
Show file tree
Hide file tree
Showing 17 changed files with 429 additions and 163 deletions.
28 changes: 23 additions & 5 deletions btcjson/chainsvrcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ package btcjson

import (
"encoding/json"
"errors"
"fmt"

"github.com/gcash/bchd/wire"
Expand Down Expand Up @@ -133,23 +134,40 @@ func NewGetBestBlockHashCmd() *GetBestBlockHashCmd {
return &GetBestBlockHashCmd{}
}

// VerbosityLevel is a type that can unmarshal a bool or an int into an int field!
// This allows the raw API to receive either an int or a bool.
type VerbosityLevel int

// UnmarshalJSON allows the VerbosityLevel to unmarshal either bool or int.
func (v *VerbosityLevel) UnmarshalJSON(dat []byte) error {
switch string(dat) {
case "0", "false":
*v = 0
case "1", "true":
*v = 1
case "2":
*v = 2
default:
return errors.New("invalid VerbosityLevel value")
}
return nil
}

// GetBlockCmd defines the getblock JSON-RPC command.
type GetBlockCmd struct {
Hash string
Verbose *bool `jsonrpcdefault:"true"`
VerboseTx *bool `jsonrpcdefault:"false"`
Verbosity *VerbosityLevel `jsonrpcdefault:"1"`
}

// NewGetBlockCmd returns a new instance which can be used to issue a getblock
// JSON-RPC command.
//
// The parameters which are pointers indicate they are optional. Passing nil
// for optional parameters will use the default value.
func NewGetBlockCmd(hash string, verbose, verboseTx *bool) *GetBlockCmd {
func NewGetBlockCmd(hash string, verbosity *VerbosityLevel) *GetBlockCmd {
return &GetBlockCmd{
Hash: hash,
Verbose: verbose,
VerboseTx: verboseTx,
Verbosity: verbosity,
}
}

Expand Down
97 changes: 80 additions & 17 deletions btcjson/chainsvrcmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,51 +156,114 @@ func TestChainSvrCmds(t *testing.T) {
},
{
name: "getblock",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getblock", "123", btcjson.Int(0))
},
staticCmd: func() interface{} {
return btcjson.NewGetBlockCmd("123", btcjson.Vlevel(0))
},
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",0],"id":1}`,
unmarshalled: &btcjson.GetBlockCmd{
Hash: "123",
Verbosity: btcjson.Vlevel(0),
},
},
{
name: "getblock default verbosity",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getblock", "123")
},
staticCmd: func() interface{} {
return btcjson.NewGetBlockCmd("123", nil, nil)
return btcjson.NewGetBlockCmd("123", nil)
},
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123"],"id":1}`,
unmarshalled: &btcjson.GetBlockCmd{
Hash: "123",
Verbose: btcjson.Bool(true),
VerboseTx: btcjson.Bool(false),
Verbosity: btcjson.Vlevel(1),
},
},
{
name: "getblock required optional1",
newCmd: func() (interface{}, error) {
// Intentionally use a source param that is
// more pointers than the destination to
// exercise that path.
verbosePtr := btcjson.Bool(true)
return btcjson.NewCmd("getblock", "123", &verbosePtr)
return btcjson.NewCmd("getblock", "123", btcjson.Int(1))
},
staticCmd: func() interface{} {
return btcjson.NewGetBlockCmd("123", btcjson.Bool(true), nil)
return btcjson.NewGetBlockCmd("123", btcjson.Vlevel(1))
},
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",true],"id":1}`,
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",1],"id":1}`,
unmarshalled: &btcjson.GetBlockCmd{
Hash: "123",
Verbose: btcjson.Bool(true),
VerboseTx: btcjson.Bool(false),
Verbosity: btcjson.Vlevel(1),
},
},
{
name: "getblock required optional2",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getblock", "123", true, true)
return btcjson.NewCmd("getblock", "123", btcjson.Int(2))
},
staticCmd: func() interface{} {
return btcjson.NewGetBlockCmd("123", btcjson.Vlevel(2))
},
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",2],"id":1}`,
unmarshalled: &btcjson.GetBlockCmd{
Hash: "123",
Verbosity: btcjson.Vlevel(2),
},
},
{
name: "getblock required optional string true",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getblock", "123", btcjson.String("true"))
},
staticCmd: func() interface{} {
return btcjson.NewGetBlockCmd("123", btcjson.Vlevel(1))
},
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",1],"id":1}`,
unmarshalled: &btcjson.GetBlockCmd{
Hash: "123",
Verbosity: btcjson.Vlevel(1),
},
},
{
name: "getblock required optional string false",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getblock", "123", btcjson.String("false"))
},
staticCmd: func() interface{} {
return btcjson.NewGetBlockCmd("123", btcjson.Vlevel(0))
},
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",0],"id":1}`,
unmarshalled: &btcjson.GetBlockCmd{
Hash: "123",
Verbosity: btcjson.Vlevel(0),
},
},
{
name: "getblock required optional true",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getblock", "123", btcjson.Bool(true))
},
staticCmd: func() interface{} {
return btcjson.NewGetBlockCmd("123", btcjson.Vlevel(1))
},
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",1],"id":1}`,
unmarshalled: &btcjson.GetBlockCmd{
Hash: "123",
Verbosity: btcjson.Vlevel(1),
},
},
{
name: "getblock required optional false",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd("getblock", "123", btcjson.Bool(false))
},
staticCmd: func() interface{} {
return btcjson.NewGetBlockCmd("123", btcjson.Bool(true), btcjson.Bool(true))
return btcjson.NewGetBlockCmd("123", btcjson.Vlevel(0))
},
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",true,true],"id":1}`,
marshalled: `{"jsonrpc":"1.0","method":"getblock","params":["123",0],"id":1}`,
unmarshalled: &btcjson.GetBlockCmd{
Hash: "123",
Verbose: btcjson.Bool(true),
VerboseTx: btcjson.Bool(true),
Verbosity: btcjson.Vlevel(0),
},
},
{
Expand Down
66 changes: 41 additions & 25 deletions btcjson/chainsvrresults.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,37 +24,53 @@ type GetBlockHeaderVerboseResult struct {
NextHash string `json:"nextblockhash,omitempty"`
}

// GetBlockBaseVerboseResult models the common data from the getblock command when
// verbose flag set to 1 or 2. When the verbose flag is not set, getblock
// returns a hex-encoded string.
type GetBlockBaseVerboseResult struct {
Hash string `json:"hash"`
Confirmations int64 `json:"confirmations"`
Size int32 `json:"size"`
Height int64 `json:"height"`
Version int32 `json:"version"`
VersionHex string `json:"versionHex"`
MerkleRoot string `json:"merkleroot"`
Time int64 `json:"time"`
Nonce uint32 `json:"nonce"`
Bits string `json:"bits"`
Difficulty float64 `json:"difficulty"`
PreviousHash string `json:"previousblockhash"`
NextHash string `json:"nextblockhash,omitempty"`
}

// GetBlockVerboseResult models the data from the getblock command when the
// verbose flag is set to 1 (default).
// verbose flag is set to 1. When the verbose flag is set to 0, getblock returns a
// hex-encoded string. When the verbose flag is set to 1, getblock returns an object
// whose tx field is an array of transaction hashes. When the verbose flag is set to 2,
// getblock returns an object whose tx field is an array of raw transactions.
// Use GetBlockVerboseTxResult to unmarshal data received from passing verbose=2 to getblock.
type GetBlockVerboseResult struct {
*GetBlockBaseVerboseResult
Tx []string `json:"tx,omitempty"`
Hash string `json:"hash"`
Confirmations int64 `json:"confirmations"`
StrippedSize int32 `json:"strippedsize"`
Size int32 `json:"size"`
Height int64 `json:"height"`
Version int32 `json:"version"`
VersionHex string `json:"versionHex"`
MerkleRoot string `json:"merkleroot"`
Tx []string `json:"tx,omitempty"`
RawTx []TxRawResult `json:"rawtx,omitempty"` // Note: this field is always empty when verbose != 2.
Time int64 `json:"time"`
Nonce uint32 `json:"nonce"`
Bits string `json:"bits"`
Difficulty float64 `json:"difficulty"`
PreviousHash string `json:"previousblockhash"`
NextHash string `json:"nextblockhash,omitempty"`
}

// GetBlockVerboseTxResult models the data from the getblock command when the
// verbose flag is set to 2.
// verbose flag is set to 2. When the verbose flag is set to 0, getblock returns a
// hex-encoded string. When the verbose flag is set to 1, getblock returns an object
// whose tx field is an array of transaction hashes. When the verbose flag is set to 2,
// getblock returns an object whose tx field is an array of raw transactions.
// Use GetBlockVerboseResult to unmarshal data received from passing verbose=1 to getblock.
type GetBlockVerboseTxResult struct {
*GetBlockBaseVerboseResult
Tx []TxRawResult `json:"tx,omitempty"`
Hash string `json:"hash"`
Confirmations int64 `json:"confirmations"`
StrippedSize int32 `json:"strippedsize"`
Size int32 `json:"size"`
Height int64 `json:"height"`
Version int32 `json:"version"`
VersionHex string `json:"versionHex"`
MerkleRoot string `json:"merkleroot"`
Tx []TxRawResult `json:"tx,omitempty"`
Time int64 `json:"time"`
Nonce uint32 `json:"nonce"`
Bits string `json:"bits"`
Difficulty float64 `json:"difficulty"`
PreviousHash string `json:"previousblockhash"`
NextHash string `json:"nextblockhash,omitempty"`
}

// AddMultisigAddressResult models the data returned from the addmultisigaddress
Expand Down
2 changes: 1 addition & 1 deletion btcjson/cmdinfo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func TestMethodUsageText(t *testing.T) {
{
name: "getblock",
method: "getblock",
expected: `getblock "hash" (verbose=true verbosetx=false)`,
expected: `getblock "hash" (verbosity=1)`,
},
}

Expand Down
53 changes: 51 additions & 2 deletions btcjson/cmdparse.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,12 @@ func typesMaybeCompatible(dest reflect.Type, src reflect.Type) bool {
return true
}

if srcKind == reflect.Bool {
if isNumeric(destKind) {
return true
}
}

if srcKind == reflect.String {
// Strings can potentially be converted to numeric types.
if isNumeric(destKind) {
Expand Down Expand Up @@ -289,6 +295,39 @@ func assignField(paramNum int, fieldName string, dest reflect.Value, src reflect

// Perform supported type conversions.
switch src.Kind() {
case reflect.Bool:
switch dest.Kind() {
// Destination is a signed integer of various magnitude.
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:

srcBool := src.Bool()

switch srcBool {
case true:
dest.SetInt(1)
case false:
dest.SetInt(0)
}

case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
reflect.Uint64:

srcBool := src.Bool()

switch srcBool {
case true:
dest.SetUint(1)
case false:
dest.SetUint(0)
}

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 signed integer of various magnitude.
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:
Expand Down Expand Up @@ -409,7 +448,17 @@ func assignField(paramNum int, fieldName string, dest reflect.Value, src reflect
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
reflect.Int64:

srcInt, err := strconv.ParseInt(src.String(), 0, 0)
// Support coercing booleans into integers.
strVal := src.String()

switch strVal {
case "false":
strVal = "0"
case "true":
strVal = "1"
}

srcInt, err := strconv.ParseInt(strVal, 0, 0)
if err != nil {
str := fmt.Sprintf("parameter #%d '%s' must "+
"parse to a %v", paramNum, fieldName,
Expand Down Expand Up @@ -471,7 +520,7 @@ func assignField(paramNum int, fieldName string, dest reflect.Value, src reflect
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",
"be valid JSON which unmarshals to a %v",
paramNum, fieldName, destBaseType)
return makeError(ErrInvalidType, str)
}
Expand Down
Loading

0 comments on commit 7244caf

Please sign in to comment.