forked from cerc-io/ipld-eth-server
* Add vendor dir so builds dont require dep * Pin specific version go-eth version
This commit is contained in:
+16
@@ -0,0 +1,16 @@
|
||||
# This is the list of people who have contributed code to the repository.
|
||||
#
|
||||
# Names should be added to this file only after verifying that the individual
|
||||
# or the individual's organization has agreed to the LICENSE.
|
||||
#
|
||||
# Names should be added to this file like so:
|
||||
# Name <email address>
|
||||
|
||||
John C. Vernaleo <jcv@conformal.com>
|
||||
Dave Collins <davec@conformal.com>
|
||||
Owain G. Ainsworth <oga@conformal.com>
|
||||
David Hill <dhill@conformal.com>
|
||||
Josh Rickmar <jrick@conformal.com>
|
||||
Andreas Metsälä <andreas.metsala@gmail.com>
|
||||
Francis Lam <flam@alum.mit.edu>
|
||||
Geert-Johan Riemer <geertjohan.riemer@gmail.com>
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
btcjson
|
||||
=======
|
||||
|
||||
[](https://travis-ci.org/btcsuite/btcd)
|
||||
[](http://copyfree.org)
|
||||
[](http://godoc.org/github.com/btcsuite/btcd/btcjson)
|
||||
|
||||
Package btcjson implements concrete types for marshalling to and from the
|
||||
bitcoin JSON-RPC API. A comprehensive suite of tests is provided to ensure
|
||||
proper functionality.
|
||||
|
||||
Although this package was primarily written for the btcsuite, it has
|
||||
intentionally been designed so it can be used as a standalone package for any
|
||||
projects needing to marshal to and from bitcoin JSON-RPC requests and responses.
|
||||
|
||||
Note that although it's possible to use this package directly to implement an
|
||||
RPC client, it is not recommended since it is only intended as an infrastructure
|
||||
package. Instead, RPC clients should use the
|
||||
[btcrpcclient](https://github.com/btcsuite/btcrpcclient) package which provides
|
||||
a full blown RPC client with many features such as automatic connection
|
||||
management, websocket support, automatic notification re-registration on
|
||||
reconnect, and conversion from the raw underlying RPC types (strings, floats,
|
||||
ints, etc) to higher-level types with many nice and useful properties.
|
||||
|
||||
## Installation and Updating
|
||||
|
||||
```bash
|
||||
$ go get -u github.com/btcsuite/btcd/btcjson
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
* [Marshal Command](http://godoc.org/github.com/btcsuite/btcd/btcjson#example-MarshalCmd)
|
||||
Demonstrates how to create and marshal a command into a JSON-RPC request.
|
||||
|
||||
* [Unmarshal Command](http://godoc.org/github.com/btcsuite/btcd/btcjson#example-UnmarshalCmd)
|
||||
Demonstrates how to unmarshal a JSON-RPC request and then unmarshal the
|
||||
concrete request into a concrete command.
|
||||
|
||||
* [Marshal Response](http://godoc.org/github.com/btcsuite/btcd/btcjson#example-MarshalResponse)
|
||||
Demonstrates how to marshal a JSON-RPC response.
|
||||
|
||||
* [Unmarshal Response](http://godoc.org/github.com/btcsuite/btcd/btcjson#example-package--UnmarshalResponse)
|
||||
Demonstrates how to unmarshal a JSON-RPC response and then unmarshal the
|
||||
result field in the response to a concrete type.
|
||||
|
||||
## GPG Verification Key
|
||||
|
||||
All official release tags are signed by Conformal so users can ensure the code
|
||||
has not been tampered with and is coming from the btcsuite developers. To
|
||||
verify the signature perform the following:
|
||||
|
||||
- Download the public key from the Conformal website at
|
||||
https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt
|
||||
|
||||
- Import the public key into your GPG keyring:
|
||||
```bash
|
||||
gpg --import GIT-GPG-KEY-conformal.txt
|
||||
```
|
||||
|
||||
- Verify the release tag with the following command where `TAG_NAME` is a
|
||||
placeholder for the specific tag:
|
||||
```bash
|
||||
git tag -v TAG_NAME
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
Package btcjson is licensed under the [copyfree](http://copyfree.org) ISC
|
||||
License.
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
// Copyright (c) 2014-2016 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.
|
||||
|
||||
// NOTE: This file is intended to house the RPC commands that are supported by
|
||||
// a chain server with btcd extensions.
|
||||
|
||||
package btcjson
|
||||
|
||||
// NodeSubCmd defines the type used in the addnode JSON-RPC command for the
|
||||
// sub command field.
|
||||
type NodeSubCmd string
|
||||
|
||||
const (
|
||||
// NConnect indicates the specified host that should be connected to.
|
||||
NConnect NodeSubCmd = "connect"
|
||||
|
||||
// NRemove indicates the specified peer that should be removed as a
|
||||
// persistent peer.
|
||||
NRemove NodeSubCmd = "remove"
|
||||
|
||||
// NDisconnect indicates the specified peer should be disonnected.
|
||||
NDisconnect NodeSubCmd = "disconnect"
|
||||
)
|
||||
|
||||
// NodeCmd defines the dropnode JSON-RPC command.
|
||||
type NodeCmd struct {
|
||||
SubCmd NodeSubCmd `jsonrpcusage:"\"connect|remove|disconnect\""`
|
||||
Target string
|
||||
ConnectSubCmd *string `jsonrpcusage:"\"perm|temp\""`
|
||||
}
|
||||
|
||||
// NewNodeCmd returns a new instance which can be used to issue a `node`
|
||||
// JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewNodeCmd(subCmd NodeSubCmd, target string, connectSubCmd *string) *NodeCmd {
|
||||
return &NodeCmd{
|
||||
SubCmd: subCmd,
|
||||
Target: target,
|
||||
ConnectSubCmd: connectSubCmd,
|
||||
}
|
||||
}
|
||||
|
||||
// DebugLevelCmd defines the debuglevel JSON-RPC command. This command is not a
|
||||
// standard Bitcoin command. It is an extension for btcd.
|
||||
type DebugLevelCmd struct {
|
||||
LevelSpec string
|
||||
}
|
||||
|
||||
// NewDebugLevelCmd returns a new DebugLevelCmd which can be used to issue a
|
||||
// debuglevel JSON-RPC command. This command is not a standard Bitcoin command.
|
||||
// It is an extension for btcd.
|
||||
func NewDebugLevelCmd(levelSpec string) *DebugLevelCmd {
|
||||
return &DebugLevelCmd{
|
||||
LevelSpec: levelSpec,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateCmd defines the generate JSON-RPC command.
|
||||
type GenerateCmd struct {
|
||||
NumBlocks uint32
|
||||
}
|
||||
|
||||
// NewGenerateCmd returns a new instance which can be used to issue a generate
|
||||
// JSON-RPC command.
|
||||
func NewGenerateCmd(numBlocks uint32) *GenerateCmd {
|
||||
return &GenerateCmd{
|
||||
NumBlocks: numBlocks,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBestBlockCmd defines the getbestblock JSON-RPC command.
|
||||
type GetBestBlockCmd struct{}
|
||||
|
||||
// NewGetBestBlockCmd returns a new instance which can be used to issue a
|
||||
// getbestblock JSON-RPC command.
|
||||
func NewGetBestBlockCmd() *GetBestBlockCmd {
|
||||
return &GetBestBlockCmd{}
|
||||
}
|
||||
|
||||
// GetCurrentNetCmd defines the getcurrentnet JSON-RPC command.
|
||||
type GetCurrentNetCmd struct{}
|
||||
|
||||
// NewGetCurrentNetCmd returns a new instance which can be used to issue a
|
||||
// getcurrentnet JSON-RPC command.
|
||||
func NewGetCurrentNetCmd() *GetCurrentNetCmd {
|
||||
return &GetCurrentNetCmd{}
|
||||
}
|
||||
|
||||
// GetHeadersCmd defines the getheaders JSON-RPC command.
|
||||
//
|
||||
// NOTE: This is a btcsuite extension ported from
|
||||
// github.com/decred/dcrd/dcrjson.
|
||||
type GetHeadersCmd struct {
|
||||
BlockLocators []string `json:"blocklocators"`
|
||||
HashStop string `json:"hashstop"`
|
||||
}
|
||||
|
||||
// NewGetHeadersCmd returns a new instance which can be used to issue a
|
||||
// getheaders JSON-RPC command.
|
||||
//
|
||||
// NOTE: This is a btcsuite extension ported from
|
||||
// github.com/decred/dcrd/dcrjson.
|
||||
func NewGetHeadersCmd(blockLocators []string, hashStop string) *GetHeadersCmd {
|
||||
return &GetHeadersCmd{
|
||||
BlockLocators: blockLocators,
|
||||
HashStop: hashStop,
|
||||
}
|
||||
}
|
||||
|
||||
// VersionCmd defines the version JSON-RPC command.
|
||||
//
|
||||
// NOTE: This is a btcsuite extension ported from
|
||||
// github.com/decred/dcrd/dcrjson.
|
||||
type VersionCmd struct{}
|
||||
|
||||
// NewVersionCmd returns a new instance which can be used to issue a JSON-RPC
|
||||
// version command.
|
||||
//
|
||||
// NOTE: This is a btcsuite extension ported from
|
||||
// github.com/decred/dcrd/dcrjson.
|
||||
func NewVersionCmd() *VersionCmd { return new(VersionCmd) }
|
||||
|
||||
func init() {
|
||||
// No special flags for commands in this file.
|
||||
flags := UsageFlag(0)
|
||||
|
||||
MustRegisterCmd("debuglevel", (*DebugLevelCmd)(nil), flags)
|
||||
MustRegisterCmd("node", (*NodeCmd)(nil), flags)
|
||||
MustRegisterCmd("generate", (*GenerateCmd)(nil), flags)
|
||||
MustRegisterCmd("getbestblock", (*GetBestBlockCmd)(nil), flags)
|
||||
MustRegisterCmd("getcurrentnet", (*GetCurrentNetCmd)(nil), flags)
|
||||
MustRegisterCmd("getheaders", (*GetHeadersCmd)(nil), flags)
|
||||
MustRegisterCmd("version", (*VersionCmd)(nil), flags)
|
||||
}
|
||||
+257
@@ -0,0 +1,257 @@
|
||||
// Copyright (c) 2014-2016 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 btcjson_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestBtcdExtCmds tests all of the btcd extended commands marshal and unmarshal
|
||||
// into valid results include handling of optional fields being omitted in the
|
||||
// marshalled command, while optional fields with defaults have the default
|
||||
// assigned on unmarshalled commands.
|
||||
func TestBtcdExtCmds(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testID := int(1)
|
||||
tests := []struct {
|
||||
name string
|
||||
newCmd func() (interface{}, error)
|
||||
staticCmd func() interface{}
|
||||
marshalled string
|
||||
unmarshalled interface{}
|
||||
}{
|
||||
{
|
||||
name: "debuglevel",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("debuglevel", "trace")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewDebugLevelCmd("trace")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"debuglevel","params":["trace"],"id":1}`,
|
||||
unmarshalled: &btcjson.DebugLevelCmd{
|
||||
LevelSpec: "trace",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("node", btcjson.NRemove, "1.1.1.1")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewNodeCmd("remove", "1.1.1.1", nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"node","params":["remove","1.1.1.1"],"id":1}`,
|
||||
unmarshalled: &btcjson.NodeCmd{
|
||||
SubCmd: btcjson.NRemove,
|
||||
Target: "1.1.1.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("node", btcjson.NDisconnect, "1.1.1.1")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewNodeCmd("disconnect", "1.1.1.1", nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"node","params":["disconnect","1.1.1.1"],"id":1}`,
|
||||
unmarshalled: &btcjson.NodeCmd{
|
||||
SubCmd: btcjson.NDisconnect,
|
||||
Target: "1.1.1.1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("node", btcjson.NConnect, "1.1.1.1", "perm")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewNodeCmd("connect", "1.1.1.1", btcjson.String("perm"))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"node","params":["connect","1.1.1.1","perm"],"id":1}`,
|
||||
unmarshalled: &btcjson.NodeCmd{
|
||||
SubCmd: btcjson.NConnect,
|
||||
Target: "1.1.1.1",
|
||||
ConnectSubCmd: btcjson.String("perm"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("node", btcjson.NConnect, "1.1.1.1", "temp")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewNodeCmd("connect", "1.1.1.1", btcjson.String("temp"))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"node","params":["connect","1.1.1.1","temp"],"id":1}`,
|
||||
unmarshalled: &btcjson.NodeCmd{
|
||||
SubCmd: btcjson.NConnect,
|
||||
Target: "1.1.1.1",
|
||||
ConnectSubCmd: btcjson.String("temp"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "generate",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("generate", 1)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGenerateCmd(1)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"generate","params":[1],"id":1}`,
|
||||
unmarshalled: &btcjson.GenerateCmd{
|
||||
NumBlocks: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "getbestblock",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("getbestblock")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGetBestBlockCmd()
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getbestblock","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.GetBestBlockCmd{},
|
||||
},
|
||||
{
|
||||
name: "getcurrentnet",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("getcurrentnet")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGetCurrentNetCmd()
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getcurrentnet","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.GetCurrentNetCmd{},
|
||||
},
|
||||
{
|
||||
name: "getheaders",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("getheaders", []string{}, "")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGetHeadersCmd(
|
||||
[]string{},
|
||||
"",
|
||||
)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getheaders","params":[[],""],"id":1}`,
|
||||
unmarshalled: &btcjson.GetHeadersCmd{
|
||||
BlockLocators: []string{},
|
||||
HashStop: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "getheaders - with arguments",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("getheaders", []string{"000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16", "0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10"}, "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGetHeadersCmd(
|
||||
[]string{
|
||||
"000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16",
|
||||
"0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10",
|
||||
},
|
||||
"000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7",
|
||||
)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getheaders","params":[["000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16","0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10"],"000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7"],"id":1}`,
|
||||
unmarshalled: &btcjson.GetHeadersCmd{
|
||||
BlockLocators: []string{
|
||||
"000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16",
|
||||
"0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10",
|
||||
},
|
||||
HashStop: "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "version",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("version")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewVersionCmd()
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"version","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.VersionCmd{},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Marshal the command as created by the new static command
|
||||
// creation function.
|
||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.marshalled)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the command is created without error via the generic
|
||||
// new command creation function.
|
||||
cmd, err := test.newCmd()
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||
i, test.name, err)
|
||||
}
|
||||
|
||||
// Marshal the command as created by the generic new command
|
||||
// creation function.
|
||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.marshalled)
|
||||
continue
|
||||
}
|
||||
|
||||
var request btcjson.Request
|
||||
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||
"unmarshalling JSON-RPC request: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||
if err != nil {
|
||||
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||
"- got %s, want %s", i, test.name,
|
||||
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) 2016-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2017 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
// VersionResult models objects included in the version response. In the actual
|
||||
// result, these objects are keyed by the program or API name.
|
||||
//
|
||||
// NOTE: This is a btcsuite extension ported from
|
||||
// github.com/decred/dcrd/dcrjson.
|
||||
type VersionResult struct {
|
||||
VersionString string `json:"versionstring"`
|
||||
Major uint32 `json:"major"`
|
||||
Minor uint32 `json:"minor"`
|
||||
Patch uint32 `json:"patch"`
|
||||
Prerelease string `json:"prerelease"`
|
||||
BuildMetadata string `json:"buildmetadata"`
|
||||
}
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2016-2017 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 btcjson_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestBtcdExtCustomResults ensures any results that have custom marshalling
|
||||
// work as inteded.
|
||||
// and unmarshal code of results are as expected.
|
||||
func TestBtcdExtCustomResults(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
result interface{}
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "versionresult",
|
||||
result: &btcjson.VersionResult{
|
||||
VersionString: "1.0.0",
|
||||
Major: 1,
|
||||
Minor: 0,
|
||||
Patch: 0,
|
||||
Prerelease: "pr",
|
||||
BuildMetadata: "bm",
|
||||
},
|
||||
expected: `{"versionstring":"1.0.0","major":1,"minor":0,"patch":0,"prerelease":"pr","buildmetadata":"bm"}`,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
marshalled, err := json.Marshal(test.result)
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
if string(marshalled) != test.expected {
|
||||
t.Errorf("Test #%d (%s) unexpected marhsalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// NOTE: This file is intended to house the RPC commands that are supported by
|
||||
// a wallet server with btcwallet extensions.
|
||||
|
||||
package btcjson
|
||||
|
||||
// CreateNewAccountCmd defines the createnewaccount JSON-RPC command.
|
||||
type CreateNewAccountCmd struct {
|
||||
Account string
|
||||
}
|
||||
|
||||
// NewCreateNewAccountCmd returns a new instance which can be used to issue a
|
||||
// createnewaccount JSON-RPC command.
|
||||
func NewCreateNewAccountCmd(account string) *CreateNewAccountCmd {
|
||||
return &CreateNewAccountCmd{
|
||||
Account: account,
|
||||
}
|
||||
}
|
||||
|
||||
// DumpWalletCmd defines the dumpwallet JSON-RPC command.
|
||||
type DumpWalletCmd struct {
|
||||
Filename string
|
||||
}
|
||||
|
||||
// NewDumpWalletCmd returns a new instance which can be used to issue a
|
||||
// dumpwallet JSON-RPC command.
|
||||
func NewDumpWalletCmd(filename string) *DumpWalletCmd {
|
||||
return &DumpWalletCmd{
|
||||
Filename: filename,
|
||||
}
|
||||
}
|
||||
|
||||
// ImportAddressCmd defines the importaddress JSON-RPC command.
|
||||
type ImportAddressCmd struct {
|
||||
Address string
|
||||
Rescan *bool `jsonrpcdefault:"true"`
|
||||
}
|
||||
|
||||
// NewImportAddressCmd returns a new instance which can be used to issue an
|
||||
// importaddress JSON-RPC command.
|
||||
func NewImportAddressCmd(address string, rescan *bool) *ImportAddressCmd {
|
||||
return &ImportAddressCmd{
|
||||
Address: address,
|
||||
Rescan: rescan,
|
||||
}
|
||||
}
|
||||
|
||||
// ImportPubKeyCmd defines the importpubkey JSON-RPC command.
|
||||
type ImportPubKeyCmd struct {
|
||||
PubKey string
|
||||
Rescan *bool `jsonrpcdefault:"true"`
|
||||
}
|
||||
|
||||
// NewImportPubKeyCmd returns a new instance which can be used to issue an
|
||||
// importpubkey JSON-RPC command.
|
||||
func NewImportPubKeyCmd(pubKey string, rescan *bool) *ImportPubKeyCmd {
|
||||
return &ImportPubKeyCmd{
|
||||
PubKey: pubKey,
|
||||
Rescan: rescan,
|
||||
}
|
||||
}
|
||||
|
||||
// ImportWalletCmd defines the importwallet JSON-RPC command.
|
||||
type ImportWalletCmd struct {
|
||||
Filename string
|
||||
}
|
||||
|
||||
// NewImportWalletCmd returns a new instance which can be used to issue a
|
||||
// importwallet JSON-RPC command.
|
||||
func NewImportWalletCmd(filename string) *ImportWalletCmd {
|
||||
return &ImportWalletCmd{
|
||||
Filename: filename,
|
||||
}
|
||||
}
|
||||
|
||||
// RenameAccountCmd defines the renameaccount JSON-RPC command.
|
||||
type RenameAccountCmd struct {
|
||||
OldAccount string
|
||||
NewAccount string
|
||||
}
|
||||
|
||||
// NewRenameAccountCmd returns a new instance which can be used to issue a
|
||||
// renameaccount JSON-RPC command.
|
||||
func NewRenameAccountCmd(oldAccount, newAccount string) *RenameAccountCmd {
|
||||
return &RenameAccountCmd{
|
||||
OldAccount: oldAccount,
|
||||
NewAccount: newAccount,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// The commands in this file are only usable with a wallet server.
|
||||
flags := UFWalletOnly
|
||||
|
||||
MustRegisterCmd("createnewaccount", (*CreateNewAccountCmd)(nil), flags)
|
||||
MustRegisterCmd("dumpwallet", (*DumpWalletCmd)(nil), flags)
|
||||
MustRegisterCmd("importaddress", (*ImportAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("importpubkey", (*ImportPubKeyCmd)(nil), flags)
|
||||
MustRegisterCmd("importwallet", (*ImportWalletCmd)(nil), flags)
|
||||
MustRegisterCmd("renameaccount", (*RenameAccountCmd)(nil), flags)
|
||||
}
|
||||
+208
@@ -0,0 +1,208 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestBtcWalletExtCmds tests all of the btcwallet extended commands marshal and
|
||||
// unmarshal into valid results include handling of optional fields being
|
||||
// omitted in the marshalled command, while optional fields with defaults have
|
||||
// the default assigned on unmarshalled commands.
|
||||
func TestBtcWalletExtCmds(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testID := int(1)
|
||||
tests := []struct {
|
||||
name string
|
||||
newCmd func() (interface{}, error)
|
||||
staticCmd func() interface{}
|
||||
marshalled string
|
||||
unmarshalled interface{}
|
||||
}{
|
||||
{
|
||||
name: "createnewaccount",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("createnewaccount", "acct")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewCreateNewAccountCmd("acct")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"createnewaccount","params":["acct"],"id":1}`,
|
||||
unmarshalled: &btcjson.CreateNewAccountCmd{
|
||||
Account: "acct",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "dumpwallet",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("dumpwallet", "filename")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewDumpWalletCmd("filename")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"dumpwallet","params":["filename"],"id":1}`,
|
||||
unmarshalled: &btcjson.DumpWalletCmd{
|
||||
Filename: "filename",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "importaddress",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("importaddress", "1Address")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewImportAddressCmd("1Address", nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"importaddress","params":["1Address"],"id":1}`,
|
||||
unmarshalled: &btcjson.ImportAddressCmd{
|
||||
Address: "1Address",
|
||||
Rescan: btcjson.Bool(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "importaddress optional",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("importaddress", "1Address", false)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewImportAddressCmd("1Address", btcjson.Bool(false))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"importaddress","params":["1Address",false],"id":1}`,
|
||||
unmarshalled: &btcjson.ImportAddressCmd{
|
||||
Address: "1Address",
|
||||
Rescan: btcjson.Bool(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "importpubkey",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("importpubkey", "031234")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewImportPubKeyCmd("031234", nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"importpubkey","params":["031234"],"id":1}`,
|
||||
unmarshalled: &btcjson.ImportPubKeyCmd{
|
||||
PubKey: "031234",
|
||||
Rescan: btcjson.Bool(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "importpubkey optional",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("importpubkey", "031234", false)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewImportPubKeyCmd("031234", btcjson.Bool(false))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"importpubkey","params":["031234",false],"id":1}`,
|
||||
unmarshalled: &btcjson.ImportPubKeyCmd{
|
||||
PubKey: "031234",
|
||||
Rescan: btcjson.Bool(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "importwallet",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("importwallet", "filename")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewImportWalletCmd("filename")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"importwallet","params":["filename"],"id":1}`,
|
||||
unmarshalled: &btcjson.ImportWalletCmd{
|
||||
Filename: "filename",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "renameaccount",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("renameaccount", "oldacct", "newacct")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewRenameAccountCmd("oldacct", "newacct")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"renameaccount","params":["oldacct","newacct"],"id":1}`,
|
||||
unmarshalled: &btcjson.RenameAccountCmd{
|
||||
OldAccount: "oldacct",
|
||||
NewAccount: "newacct",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Marshal the command as created by the new static command
|
||||
// creation function.
|
||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.marshalled)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the command is created without error via the generic
|
||||
// new command creation function.
|
||||
cmd, err := test.newCmd()
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||
i, test.name, err)
|
||||
}
|
||||
|
||||
// Marshal the command as created by the generic new command
|
||||
// creation function.
|
||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.marshalled)
|
||||
continue
|
||||
}
|
||||
|
||||
var request btcjson.Request
|
||||
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||
"unmarshalling JSON-RPC request: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||
if err != nil {
|
||||
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||
"- got %s, want %s", i, test.name,
|
||||
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+793
@@ -0,0 +1,793 @@
|
||||
// Copyright (c) 2014-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// NOTE: This file is intended to house the RPC commands that are supported by
|
||||
// a chain server.
|
||||
|
||||
package btcjson
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// AddNodeSubCmd defines the type used in the addnode JSON-RPC command for the
|
||||
// sub command field.
|
||||
type AddNodeSubCmd string
|
||||
|
||||
const (
|
||||
// ANAdd indicates the specified host should be added as a persistent
|
||||
// peer.
|
||||
ANAdd AddNodeSubCmd = "add"
|
||||
|
||||
// ANRemove indicates the specified peer should be removed.
|
||||
ANRemove AddNodeSubCmd = "remove"
|
||||
|
||||
// ANOneTry indicates the specified host should try to connect once,
|
||||
// but it should not be made persistent.
|
||||
ANOneTry AddNodeSubCmd = "onetry"
|
||||
)
|
||||
|
||||
// AddNodeCmd defines the addnode JSON-RPC command.
|
||||
type AddNodeCmd struct {
|
||||
Addr string
|
||||
SubCmd AddNodeSubCmd `jsonrpcusage:"\"add|remove|onetry\""`
|
||||
}
|
||||
|
||||
// NewAddNodeCmd returns a new instance which can be used to issue an addnode
|
||||
// JSON-RPC command.
|
||||
func NewAddNodeCmd(addr string, subCmd AddNodeSubCmd) *AddNodeCmd {
|
||||
return &AddNodeCmd{
|
||||
Addr: addr,
|
||||
SubCmd: subCmd,
|
||||
}
|
||||
}
|
||||
|
||||
// TransactionInput represents the inputs to a transaction. Specifically a
|
||||
// transaction hash and output number pair.
|
||||
type TransactionInput struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
}
|
||||
|
||||
// CreateRawTransactionCmd defines the createrawtransaction JSON-RPC command.
|
||||
type CreateRawTransactionCmd struct {
|
||||
Inputs []TransactionInput
|
||||
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
|
||||
LockTime *int64
|
||||
}
|
||||
|
||||
// NewCreateRawTransactionCmd returns a new instance which can be used to issue
|
||||
// a createrawtransaction JSON-RPC command.
|
||||
//
|
||||
// Amounts are in BTC.
|
||||
func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]float64,
|
||||
lockTime *int64) *CreateRawTransactionCmd {
|
||||
|
||||
return &CreateRawTransactionCmd{
|
||||
Inputs: inputs,
|
||||
Amounts: amounts,
|
||||
LockTime: lockTime,
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeRawTransactionCmd defines the decoderawtransaction JSON-RPC command.
|
||||
type DecodeRawTransactionCmd struct {
|
||||
HexTx string
|
||||
}
|
||||
|
||||
// NewDecodeRawTransactionCmd returns a new instance which can be used to issue
|
||||
// a decoderawtransaction JSON-RPC command.
|
||||
func NewDecodeRawTransactionCmd(hexTx string) *DecodeRawTransactionCmd {
|
||||
return &DecodeRawTransactionCmd{
|
||||
HexTx: hexTx,
|
||||
}
|
||||
}
|
||||
|
||||
// DecodeScriptCmd defines the decodescript JSON-RPC command.
|
||||
type DecodeScriptCmd struct {
|
||||
HexScript string
|
||||
}
|
||||
|
||||
// NewDecodeScriptCmd returns a new instance which can be used to issue a
|
||||
// decodescript JSON-RPC command.
|
||||
func NewDecodeScriptCmd(hexScript string) *DecodeScriptCmd {
|
||||
return &DecodeScriptCmd{
|
||||
HexScript: hexScript,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAddedNodeInfoCmd defines the getaddednodeinfo JSON-RPC command.
|
||||
type GetAddedNodeInfoCmd struct {
|
||||
DNS bool
|
||||
Node *string
|
||||
}
|
||||
|
||||
// NewGetAddedNodeInfoCmd returns a new instance which can be used to issue a
|
||||
// getaddednodeinfo JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetAddedNodeInfoCmd(dns bool, node *string) *GetAddedNodeInfoCmd {
|
||||
return &GetAddedNodeInfoCmd{
|
||||
DNS: dns,
|
||||
Node: node,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBestBlockHashCmd defines the getbestblockhash JSON-RPC command.
|
||||
type GetBestBlockHashCmd struct{}
|
||||
|
||||
// NewGetBestBlockHashCmd returns a new instance which can be used to issue a
|
||||
// getbestblockhash JSON-RPC command.
|
||||
func NewGetBestBlockHashCmd() *GetBestBlockHashCmd {
|
||||
return &GetBestBlockHashCmd{}
|
||||
}
|
||||
|
||||
// GetBlockCmd defines the getblock JSON-RPC command.
|
||||
type GetBlockCmd struct {
|
||||
Hash string
|
||||
Verbose *bool `jsonrpcdefault:"true"`
|
||||
VerboseTx *bool `jsonrpcdefault:"false"`
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return &GetBlockCmd{
|
||||
Hash: hash,
|
||||
Verbose: verbose,
|
||||
VerboseTx: verboseTx,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBlockChainInfoCmd defines the getblockchaininfo JSON-RPC command.
|
||||
type GetBlockChainInfoCmd struct{}
|
||||
|
||||
// NewGetBlockChainInfoCmd returns a new instance which can be used to issue a
|
||||
// getblockchaininfo JSON-RPC command.
|
||||
func NewGetBlockChainInfoCmd() *GetBlockChainInfoCmd {
|
||||
return &GetBlockChainInfoCmd{}
|
||||
}
|
||||
|
||||
// GetBlockCountCmd defines the getblockcount JSON-RPC command.
|
||||
type GetBlockCountCmd struct{}
|
||||
|
||||
// NewGetBlockCountCmd returns a new instance which can be used to issue a
|
||||
// getblockcount JSON-RPC command.
|
||||
func NewGetBlockCountCmd() *GetBlockCountCmd {
|
||||
return &GetBlockCountCmd{}
|
||||
}
|
||||
|
||||
// GetBlockHashCmd defines the getblockhash JSON-RPC command.
|
||||
type GetBlockHashCmd struct {
|
||||
Index int64
|
||||
}
|
||||
|
||||
// NewGetBlockHashCmd returns a new instance which can be used to issue a
|
||||
// getblockhash JSON-RPC command.
|
||||
func NewGetBlockHashCmd(index int64) *GetBlockHashCmd {
|
||||
return &GetBlockHashCmd{
|
||||
Index: index,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBlockHeaderCmd defines the getblockheader JSON-RPC command.
|
||||
type GetBlockHeaderCmd struct {
|
||||
Hash string
|
||||
Verbose *bool `jsonrpcdefault:"true"`
|
||||
}
|
||||
|
||||
// NewGetBlockHeaderCmd returns a new instance which can be used to issue a
|
||||
// getblockheader JSON-RPC command.
|
||||
func NewGetBlockHeaderCmd(hash string, verbose *bool) *GetBlockHeaderCmd {
|
||||
return &GetBlockHeaderCmd{
|
||||
Hash: hash,
|
||||
Verbose: verbose,
|
||||
}
|
||||
}
|
||||
|
||||
// TemplateRequest is a request object as defined in BIP22
|
||||
// (https://en.bitcoin.it/wiki/BIP_0022), it is optionally provided as an
|
||||
// pointer argument to GetBlockTemplateCmd.
|
||||
type TemplateRequest struct {
|
||||
Mode string `json:"mode,omitempty"`
|
||||
Capabilities []string `json:"capabilities,omitempty"`
|
||||
|
||||
// Optional long polling.
|
||||
LongPollID string `json:"longpollid,omitempty"`
|
||||
|
||||
// Optional template tweaking. SigOpLimit and SizeLimit can be int64
|
||||
// or bool.
|
||||
SigOpLimit interface{} `json:"sigoplimit,omitempty"`
|
||||
SizeLimit interface{} `json:"sizelimit,omitempty"`
|
||||
MaxVersion uint32 `json:"maxversion,omitempty"`
|
||||
|
||||
// Basic pool extension from BIP 0023.
|
||||
Target string `json:"target,omitempty"`
|
||||
|
||||
// Block proposal from BIP 0023. Data is only provided when Mode is
|
||||
// "proposal".
|
||||
Data string `json:"data,omitempty"`
|
||||
WorkID string `json:"workid,omitempty"`
|
||||
}
|
||||
|
||||
// convertTemplateRequestField potentially converts the provided value as
|
||||
// needed.
|
||||
func convertTemplateRequestField(fieldName string, iface interface{}) (interface{}, error) {
|
||||
switch val := iface.(type) {
|
||||
case nil:
|
||||
return nil, nil
|
||||
case bool:
|
||||
return val, nil
|
||||
case float64:
|
||||
if val == float64(int64(val)) {
|
||||
return int64(val), nil
|
||||
}
|
||||
}
|
||||
|
||||
str := fmt.Sprintf("the %s field must be unspecified, a boolean, or "+
|
||||
"a 64-bit integer", fieldName)
|
||||
return nil, makeError(ErrInvalidType, str)
|
||||
}
|
||||
|
||||
// UnmarshalJSON provides a custom Unmarshal method for TemplateRequest. This
|
||||
// is necessary because the SigOpLimit and SizeLimit fields can only be specific
|
||||
// types.
|
||||
func (t *TemplateRequest) UnmarshalJSON(data []byte) error {
|
||||
type templateRequest TemplateRequest
|
||||
|
||||
request := (*templateRequest)(t)
|
||||
if err := json.Unmarshal(data, &request); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// The SigOpLimit field can only be nil, bool, or int64.
|
||||
val, err := convertTemplateRequestField("sigoplimit", request.SigOpLimit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.SigOpLimit = val
|
||||
|
||||
// The SizeLimit field can only be nil, bool, or int64.
|
||||
val, err = convertTemplateRequestField("sizelimit", request.SizeLimit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
request.SizeLimit = val
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetBlockTemplateCmd defines the getblocktemplate JSON-RPC command.
|
||||
type GetBlockTemplateCmd struct {
|
||||
Request *TemplateRequest
|
||||
}
|
||||
|
||||
// NewGetBlockTemplateCmd returns a new instance which can be used to issue a
|
||||
// getblocktemplate JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetBlockTemplateCmd(request *TemplateRequest) *GetBlockTemplateCmd {
|
||||
return &GetBlockTemplateCmd{
|
||||
Request: request,
|
||||
}
|
||||
}
|
||||
|
||||
// GetChainTipsCmd defines the getchaintips JSON-RPC command.
|
||||
type GetChainTipsCmd struct{}
|
||||
|
||||
// NewGetChainTipsCmd returns a new instance which can be used to issue a
|
||||
// getchaintips JSON-RPC command.
|
||||
func NewGetChainTipsCmd() *GetChainTipsCmd {
|
||||
return &GetChainTipsCmd{}
|
||||
}
|
||||
|
||||
// GetConnectionCountCmd defines the getconnectioncount JSON-RPC command.
|
||||
type GetConnectionCountCmd struct{}
|
||||
|
||||
// NewGetConnectionCountCmd returns a new instance which can be used to issue a
|
||||
// getconnectioncount JSON-RPC command.
|
||||
func NewGetConnectionCountCmd() *GetConnectionCountCmd {
|
||||
return &GetConnectionCountCmd{}
|
||||
}
|
||||
|
||||
// GetDifficultyCmd defines the getdifficulty JSON-RPC command.
|
||||
type GetDifficultyCmd struct{}
|
||||
|
||||
// NewGetDifficultyCmd returns a new instance which can be used to issue a
|
||||
// getdifficulty JSON-RPC command.
|
||||
func NewGetDifficultyCmd() *GetDifficultyCmd {
|
||||
return &GetDifficultyCmd{}
|
||||
}
|
||||
|
||||
// GetGenerateCmd defines the getgenerate JSON-RPC command.
|
||||
type GetGenerateCmd struct{}
|
||||
|
||||
// NewGetGenerateCmd returns a new instance which can be used to issue a
|
||||
// getgenerate JSON-RPC command.
|
||||
func NewGetGenerateCmd() *GetGenerateCmd {
|
||||
return &GetGenerateCmd{}
|
||||
}
|
||||
|
||||
// GetHashesPerSecCmd defines the gethashespersec JSON-RPC command.
|
||||
type GetHashesPerSecCmd struct{}
|
||||
|
||||
// NewGetHashesPerSecCmd returns a new instance which can be used to issue a
|
||||
// gethashespersec JSON-RPC command.
|
||||
func NewGetHashesPerSecCmd() *GetHashesPerSecCmd {
|
||||
return &GetHashesPerSecCmd{}
|
||||
}
|
||||
|
||||
// GetInfoCmd defines the getinfo JSON-RPC command.
|
||||
type GetInfoCmd struct{}
|
||||
|
||||
// NewGetInfoCmd returns a new instance which can be used to issue a
|
||||
// getinfo JSON-RPC command.
|
||||
func NewGetInfoCmd() *GetInfoCmd {
|
||||
return &GetInfoCmd{}
|
||||
}
|
||||
|
||||
// GetMempoolEntryCmd defines the getmempoolentry JSON-RPC command.
|
||||
type GetMempoolEntryCmd struct {
|
||||
TxID string
|
||||
}
|
||||
|
||||
// NewGetMempoolEntryCmd returns a new instance which can be used to issue a
|
||||
// getmempoolentry JSON-RPC command.
|
||||
func NewGetMempoolEntryCmd(txHash string) *GetMempoolEntryCmd {
|
||||
return &GetMempoolEntryCmd{
|
||||
TxID: txHash,
|
||||
}
|
||||
}
|
||||
|
||||
// GetMempoolInfoCmd defines the getmempoolinfo JSON-RPC command.
|
||||
type GetMempoolInfoCmd struct{}
|
||||
|
||||
// NewGetMempoolInfoCmd returns a new instance which can be used to issue a
|
||||
// getmempool JSON-RPC command.
|
||||
func NewGetMempoolInfoCmd() *GetMempoolInfoCmd {
|
||||
return &GetMempoolInfoCmd{}
|
||||
}
|
||||
|
||||
// GetMiningInfoCmd defines the getmininginfo JSON-RPC command.
|
||||
type GetMiningInfoCmd struct{}
|
||||
|
||||
// NewGetMiningInfoCmd returns a new instance which can be used to issue a
|
||||
// getmininginfo JSON-RPC command.
|
||||
func NewGetMiningInfoCmd() *GetMiningInfoCmd {
|
||||
return &GetMiningInfoCmd{}
|
||||
}
|
||||
|
||||
// GetNetworkInfoCmd defines the getnetworkinfo JSON-RPC command.
|
||||
type GetNetworkInfoCmd struct{}
|
||||
|
||||
// NewGetNetworkInfoCmd returns a new instance which can be used to issue a
|
||||
// getnetworkinfo JSON-RPC command.
|
||||
func NewGetNetworkInfoCmd() *GetNetworkInfoCmd {
|
||||
return &GetNetworkInfoCmd{}
|
||||
}
|
||||
|
||||
// GetNetTotalsCmd defines the getnettotals JSON-RPC command.
|
||||
type GetNetTotalsCmd struct{}
|
||||
|
||||
// NewGetNetTotalsCmd returns a new instance which can be used to issue a
|
||||
// getnettotals JSON-RPC command.
|
||||
func NewGetNetTotalsCmd() *GetNetTotalsCmd {
|
||||
return &GetNetTotalsCmd{}
|
||||
}
|
||||
|
||||
// GetNetworkHashPSCmd defines the getnetworkhashps JSON-RPC command.
|
||||
type GetNetworkHashPSCmd struct {
|
||||
Blocks *int `jsonrpcdefault:"120"`
|
||||
Height *int `jsonrpcdefault:"-1"`
|
||||
}
|
||||
|
||||
// NewGetNetworkHashPSCmd returns a new instance which can be used to issue a
|
||||
// getnetworkhashps JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetNetworkHashPSCmd(numBlocks, height *int) *GetNetworkHashPSCmd {
|
||||
return &GetNetworkHashPSCmd{
|
||||
Blocks: numBlocks,
|
||||
Height: height,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPeerInfoCmd defines the getpeerinfo JSON-RPC command.
|
||||
type GetPeerInfoCmd struct{}
|
||||
|
||||
// NewGetPeerInfoCmd returns a new instance which can be used to issue a getpeer
|
||||
// JSON-RPC command.
|
||||
func NewGetPeerInfoCmd() *GetPeerInfoCmd {
|
||||
return &GetPeerInfoCmd{}
|
||||
}
|
||||
|
||||
// GetRawMempoolCmd defines the getmempool JSON-RPC command.
|
||||
type GetRawMempoolCmd struct {
|
||||
Verbose *bool `jsonrpcdefault:"false"`
|
||||
}
|
||||
|
||||
// NewGetRawMempoolCmd returns a new instance which can be used to issue a
|
||||
// getrawmempool JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetRawMempoolCmd(verbose *bool) *GetRawMempoolCmd {
|
||||
return &GetRawMempoolCmd{
|
||||
Verbose: verbose,
|
||||
}
|
||||
}
|
||||
|
||||
// GetRawTransactionCmd defines the getrawtransaction JSON-RPC command.
|
||||
//
|
||||
// NOTE: This field is an int versus a bool to remain compatible with Bitcoin
|
||||
// Core even though it really should be a bool.
|
||||
type GetRawTransactionCmd struct {
|
||||
Txid string
|
||||
Verbose *int `jsonrpcdefault:"0"`
|
||||
}
|
||||
|
||||
// NewGetRawTransactionCmd returns a new instance which can be used to issue a
|
||||
// getrawtransaction JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetRawTransactionCmd(txHash string, verbose *int) *GetRawTransactionCmd {
|
||||
return &GetRawTransactionCmd{
|
||||
Txid: txHash,
|
||||
Verbose: verbose,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTxOutCmd defines the gettxout JSON-RPC command.
|
||||
type GetTxOutCmd struct {
|
||||
Txid string
|
||||
Vout uint32
|
||||
IncludeMempool *bool `jsonrpcdefault:"true"`
|
||||
}
|
||||
|
||||
// NewGetTxOutCmd returns a new instance which can be used to issue a gettxout
|
||||
// JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetTxOutCmd(txHash string, vout uint32, includeMempool *bool) *GetTxOutCmd {
|
||||
return &GetTxOutCmd{
|
||||
Txid: txHash,
|
||||
Vout: vout,
|
||||
IncludeMempool: includeMempool,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTxOutProofCmd defines the gettxoutproof JSON-RPC command.
|
||||
type GetTxOutProofCmd struct {
|
||||
TxIDs []string
|
||||
BlockHash *string
|
||||
}
|
||||
|
||||
// NewGetTxOutProofCmd returns a new instance which can be used to issue a
|
||||
// gettxoutproof JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetTxOutProofCmd(txIDs []string, blockHash *string) *GetTxOutProofCmd {
|
||||
return &GetTxOutProofCmd{
|
||||
TxIDs: txIDs,
|
||||
BlockHash: blockHash,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTxOutSetInfoCmd defines the gettxoutsetinfo JSON-RPC command.
|
||||
type GetTxOutSetInfoCmd struct{}
|
||||
|
||||
// NewGetTxOutSetInfoCmd returns a new instance which can be used to issue a
|
||||
// gettxoutsetinfo JSON-RPC command.
|
||||
func NewGetTxOutSetInfoCmd() *GetTxOutSetInfoCmd {
|
||||
return &GetTxOutSetInfoCmd{}
|
||||
}
|
||||
|
||||
// GetWorkCmd defines the getwork JSON-RPC command.
|
||||
type GetWorkCmd struct {
|
||||
Data *string
|
||||
}
|
||||
|
||||
// NewGetWorkCmd returns a new instance which can be used to issue a getwork
|
||||
// JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetWorkCmd(data *string) *GetWorkCmd {
|
||||
return &GetWorkCmd{
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// HelpCmd defines the help JSON-RPC command.
|
||||
type HelpCmd struct {
|
||||
Command *string
|
||||
}
|
||||
|
||||
// NewHelpCmd returns a new instance which can be used to issue a help JSON-RPC
|
||||
// command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewHelpCmd(command *string) *HelpCmd {
|
||||
return &HelpCmd{
|
||||
Command: command,
|
||||
}
|
||||
}
|
||||
|
||||
// InvalidateBlockCmd defines the invalidateblock JSON-RPC command.
|
||||
type InvalidateBlockCmd struct {
|
||||
BlockHash string
|
||||
}
|
||||
|
||||
// NewInvalidateBlockCmd returns a new instance which can be used to issue a
|
||||
// invalidateblock JSON-RPC command.
|
||||
func NewInvalidateBlockCmd(blockHash string) *InvalidateBlockCmd {
|
||||
return &InvalidateBlockCmd{
|
||||
BlockHash: blockHash,
|
||||
}
|
||||
}
|
||||
|
||||
// PingCmd defines the ping JSON-RPC command.
|
||||
type PingCmd struct{}
|
||||
|
||||
// NewPingCmd returns a new instance which can be used to issue a ping JSON-RPC
|
||||
// command.
|
||||
func NewPingCmd() *PingCmd {
|
||||
return &PingCmd{}
|
||||
}
|
||||
|
||||
// PreciousBlockCmd defines the preciousblock JSON-RPC command.
|
||||
type PreciousBlockCmd struct {
|
||||
BlockHash string
|
||||
}
|
||||
|
||||
// NewPreciousBlockCmd returns a new instance which can be used to issue a
|
||||
// preciousblock JSON-RPC command.
|
||||
func NewPreciousBlockCmd(blockHash string) *PreciousBlockCmd {
|
||||
return &PreciousBlockCmd{
|
||||
BlockHash: blockHash,
|
||||
}
|
||||
}
|
||||
|
||||
// ReconsiderBlockCmd defines the reconsiderblock JSON-RPC command.
|
||||
type ReconsiderBlockCmd struct {
|
||||
BlockHash string
|
||||
}
|
||||
|
||||
// NewReconsiderBlockCmd returns a new instance which can be used to issue a
|
||||
// reconsiderblock JSON-RPC command.
|
||||
func NewReconsiderBlockCmd(blockHash string) *ReconsiderBlockCmd {
|
||||
return &ReconsiderBlockCmd{
|
||||
BlockHash: blockHash,
|
||||
}
|
||||
}
|
||||
|
||||
// SearchRawTransactionsCmd defines the searchrawtransactions JSON-RPC command.
|
||||
type SearchRawTransactionsCmd struct {
|
||||
Address string
|
||||
Verbose *int `jsonrpcdefault:"1"`
|
||||
Skip *int `jsonrpcdefault:"0"`
|
||||
Count *int `jsonrpcdefault:"100"`
|
||||
VinExtra *int `jsonrpcdefault:"0"`
|
||||
Reverse *bool `jsonrpcdefault:"false"`
|
||||
FilterAddrs *[]string
|
||||
}
|
||||
|
||||
// NewSearchRawTransactionsCmd returns a new instance which can be used to issue a
|
||||
// sendrawtransaction JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewSearchRawTransactionsCmd(address string, verbose, skip, count *int, vinExtra *int, reverse *bool, filterAddrs *[]string) *SearchRawTransactionsCmd {
|
||||
return &SearchRawTransactionsCmd{
|
||||
Address: address,
|
||||
Verbose: verbose,
|
||||
Skip: skip,
|
||||
Count: count,
|
||||
VinExtra: vinExtra,
|
||||
Reverse: reverse,
|
||||
FilterAddrs: filterAddrs,
|
||||
}
|
||||
}
|
||||
|
||||
// SendRawTransactionCmd defines the sendrawtransaction JSON-RPC command.
|
||||
type SendRawTransactionCmd struct {
|
||||
HexTx string
|
||||
AllowHighFees *bool `jsonrpcdefault:"false"`
|
||||
}
|
||||
|
||||
// NewSendRawTransactionCmd returns a new instance which can be used to issue a
|
||||
// sendrawtransaction JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewSendRawTransactionCmd(hexTx string, allowHighFees *bool) *SendRawTransactionCmd {
|
||||
return &SendRawTransactionCmd{
|
||||
HexTx: hexTx,
|
||||
AllowHighFees: allowHighFees,
|
||||
}
|
||||
}
|
||||
|
||||
// SetGenerateCmd defines the setgenerate JSON-RPC command.
|
||||
type SetGenerateCmd struct {
|
||||
Generate bool
|
||||
GenProcLimit *int `jsonrpcdefault:"-1"`
|
||||
}
|
||||
|
||||
// NewSetGenerateCmd returns a new instance which can be used to issue a
|
||||
// setgenerate JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewSetGenerateCmd(generate bool, genProcLimit *int) *SetGenerateCmd {
|
||||
return &SetGenerateCmd{
|
||||
Generate: generate,
|
||||
GenProcLimit: genProcLimit,
|
||||
}
|
||||
}
|
||||
|
||||
// StopCmd defines the stop JSON-RPC command.
|
||||
type StopCmd struct{}
|
||||
|
||||
// NewStopCmd returns a new instance which can be used to issue a stop JSON-RPC
|
||||
// command.
|
||||
func NewStopCmd() *StopCmd {
|
||||
return &StopCmd{}
|
||||
}
|
||||
|
||||
// SubmitBlockOptions represents the optional options struct provided with a
|
||||
// SubmitBlockCmd command.
|
||||
type SubmitBlockOptions struct {
|
||||
// must be provided if server provided a workid with template.
|
||||
WorkID string `json:"workid,omitempty"`
|
||||
}
|
||||
|
||||
// SubmitBlockCmd defines the submitblock JSON-RPC command.
|
||||
type SubmitBlockCmd struct {
|
||||
HexBlock string
|
||||
Options *SubmitBlockOptions
|
||||
}
|
||||
|
||||
// NewSubmitBlockCmd returns a new instance which can be used to issue a
|
||||
// submitblock JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewSubmitBlockCmd(hexBlock string, options *SubmitBlockOptions) *SubmitBlockCmd {
|
||||
return &SubmitBlockCmd{
|
||||
HexBlock: hexBlock,
|
||||
Options: options,
|
||||
}
|
||||
}
|
||||
|
||||
// UptimeCmd defines the uptime JSON-RPC command.
|
||||
type UptimeCmd struct{}
|
||||
|
||||
// NewUptimeCmd returns a new instance which can be used to issue an uptime JSON-RPC command.
|
||||
func NewUptimeCmd() *UptimeCmd {
|
||||
return &UptimeCmd{}
|
||||
}
|
||||
|
||||
// ValidateAddressCmd defines the validateaddress JSON-RPC command.
|
||||
type ValidateAddressCmd struct {
|
||||
Address string
|
||||
}
|
||||
|
||||
// NewValidateAddressCmd returns a new instance which can be used to issue a
|
||||
// validateaddress JSON-RPC command.
|
||||
func NewValidateAddressCmd(address string) *ValidateAddressCmd {
|
||||
return &ValidateAddressCmd{
|
||||
Address: address,
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyChainCmd defines the verifychain JSON-RPC command.
|
||||
type VerifyChainCmd struct {
|
||||
CheckLevel *int32 `jsonrpcdefault:"3"`
|
||||
CheckDepth *int32 `jsonrpcdefault:"288"` // 0 = all
|
||||
}
|
||||
|
||||
// NewVerifyChainCmd returns a new instance which can be used to issue a
|
||||
// verifychain JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewVerifyChainCmd(checkLevel, checkDepth *int32) *VerifyChainCmd {
|
||||
return &VerifyChainCmd{
|
||||
CheckLevel: checkLevel,
|
||||
CheckDepth: checkDepth,
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyMessageCmd defines the verifymessage JSON-RPC command.
|
||||
type VerifyMessageCmd struct {
|
||||
Address string
|
||||
Signature string
|
||||
Message string
|
||||
}
|
||||
|
||||
// NewVerifyMessageCmd returns a new instance which can be used to issue a
|
||||
// verifymessage JSON-RPC command.
|
||||
func NewVerifyMessageCmd(address, signature, message string) *VerifyMessageCmd {
|
||||
return &VerifyMessageCmd{
|
||||
Address: address,
|
||||
Signature: signature,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// VerifyTxOutProofCmd defines the verifytxoutproof JSON-RPC command.
|
||||
type VerifyTxOutProofCmd struct {
|
||||
Proof string
|
||||
}
|
||||
|
||||
// NewVerifyTxOutProofCmd returns a new instance which can be used to issue a
|
||||
// verifytxoutproof JSON-RPC command.
|
||||
func NewVerifyTxOutProofCmd(proof string) *VerifyTxOutProofCmd {
|
||||
return &VerifyTxOutProofCmd{
|
||||
Proof: proof,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// No special flags for commands in this file.
|
||||
flags := UsageFlag(0)
|
||||
|
||||
MustRegisterCmd("addnode", (*AddNodeCmd)(nil), flags)
|
||||
MustRegisterCmd("createrawtransaction", (*CreateRawTransactionCmd)(nil), flags)
|
||||
MustRegisterCmd("decoderawtransaction", (*DecodeRawTransactionCmd)(nil), flags)
|
||||
MustRegisterCmd("decodescript", (*DecodeScriptCmd)(nil), flags)
|
||||
MustRegisterCmd("getaddednodeinfo", (*GetAddedNodeInfoCmd)(nil), flags)
|
||||
MustRegisterCmd("getbestblockhash", (*GetBestBlockHashCmd)(nil), flags)
|
||||
MustRegisterCmd("getblock", (*GetBlockCmd)(nil), flags)
|
||||
MustRegisterCmd("getblockchaininfo", (*GetBlockChainInfoCmd)(nil), flags)
|
||||
MustRegisterCmd("getblockcount", (*GetBlockCountCmd)(nil), flags)
|
||||
MustRegisterCmd("getblockhash", (*GetBlockHashCmd)(nil), flags)
|
||||
MustRegisterCmd("getblockheader", (*GetBlockHeaderCmd)(nil), flags)
|
||||
MustRegisterCmd("getblocktemplate", (*GetBlockTemplateCmd)(nil), flags)
|
||||
MustRegisterCmd("getchaintips", (*GetChainTipsCmd)(nil), flags)
|
||||
MustRegisterCmd("getconnectioncount", (*GetConnectionCountCmd)(nil), flags)
|
||||
MustRegisterCmd("getdifficulty", (*GetDifficultyCmd)(nil), flags)
|
||||
MustRegisterCmd("getgenerate", (*GetGenerateCmd)(nil), flags)
|
||||
MustRegisterCmd("gethashespersec", (*GetHashesPerSecCmd)(nil), flags)
|
||||
MustRegisterCmd("getinfo", (*GetInfoCmd)(nil), flags)
|
||||
MustRegisterCmd("getmempoolentry", (*GetMempoolEntryCmd)(nil), flags)
|
||||
MustRegisterCmd("getmempoolinfo", (*GetMempoolInfoCmd)(nil), flags)
|
||||
MustRegisterCmd("getmininginfo", (*GetMiningInfoCmd)(nil), flags)
|
||||
MustRegisterCmd("getnetworkinfo", (*GetNetworkInfoCmd)(nil), flags)
|
||||
MustRegisterCmd("getnettotals", (*GetNetTotalsCmd)(nil), flags)
|
||||
MustRegisterCmd("getnetworkhashps", (*GetNetworkHashPSCmd)(nil), flags)
|
||||
MustRegisterCmd("getpeerinfo", (*GetPeerInfoCmd)(nil), flags)
|
||||
MustRegisterCmd("getrawmempool", (*GetRawMempoolCmd)(nil), flags)
|
||||
MustRegisterCmd("getrawtransaction", (*GetRawTransactionCmd)(nil), flags)
|
||||
MustRegisterCmd("gettxout", (*GetTxOutCmd)(nil), flags)
|
||||
MustRegisterCmd("gettxoutproof", (*GetTxOutProofCmd)(nil), flags)
|
||||
MustRegisterCmd("gettxoutsetinfo", (*GetTxOutSetInfoCmd)(nil), flags)
|
||||
MustRegisterCmd("getwork", (*GetWorkCmd)(nil), flags)
|
||||
MustRegisterCmd("help", (*HelpCmd)(nil), flags)
|
||||
MustRegisterCmd("invalidateblock", (*InvalidateBlockCmd)(nil), flags)
|
||||
MustRegisterCmd("ping", (*PingCmd)(nil), flags)
|
||||
MustRegisterCmd("preciousblock", (*PreciousBlockCmd)(nil), flags)
|
||||
MustRegisterCmd("reconsiderblock", (*ReconsiderBlockCmd)(nil), flags)
|
||||
MustRegisterCmd("searchrawtransactions", (*SearchRawTransactionsCmd)(nil), flags)
|
||||
MustRegisterCmd("sendrawtransaction", (*SendRawTransactionCmd)(nil), flags)
|
||||
MustRegisterCmd("setgenerate", (*SetGenerateCmd)(nil), flags)
|
||||
MustRegisterCmd("stop", (*StopCmd)(nil), flags)
|
||||
MustRegisterCmd("submitblock", (*SubmitBlockCmd)(nil), flags)
|
||||
MustRegisterCmd("uptime", (*UptimeCmd)(nil), flags)
|
||||
MustRegisterCmd("validateaddress", (*ValidateAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("verifychain", (*VerifyChainCmd)(nil), flags)
|
||||
MustRegisterCmd("verifymessage", (*VerifyMessageCmd)(nil), flags)
|
||||
MustRegisterCmd("verifytxoutproof", (*VerifyTxOutProofCmd)(nil), flags)
|
||||
}
|
||||
+1176
File diff suppressed because it is too large
Load Diff
+551
@@ -0,0 +1,551 @@
|
||||
// Copyright (c) 2014-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// GetBlockHeaderVerboseResult models the data from the getblockheader command when
|
||||
// the verbose flag is set. When the verbose flag is not set, getblockheader
|
||||
// returns a hex-encoded string.
|
||||
type GetBlockHeaderVerboseResult struct {
|
||||
Hash string `json:"hash"`
|
||||
Confirmations uint64 `json:"confirmations"`
|
||||
Height int32 `json:"height"`
|
||||
Version int32 `json:"version"`
|
||||
VersionHex string `json:"versionHex"`
|
||||
MerkleRoot string `json:"merkleroot"`
|
||||
Time int64 `json:"time"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
Bits string `json:"bits"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
PreviousHash string `json:"previousblockhash,omitempty"`
|
||||
NextHash string `json:"nextblockhash,omitempty"`
|
||||
}
|
||||
|
||||
// GetBlockVerboseResult models the data from the getblock command when the
|
||||
// verbose flag is set. When the verbose flag is not set, getblock returns a
|
||||
// hex-encoded string.
|
||||
type GetBlockVerboseResult struct {
|
||||
Hash string `json:"hash"`
|
||||
Confirmations uint64 `json:"confirmations"`
|
||||
StrippedSize int32 `json:"strippedsize"`
|
||||
Size int32 `json:"size"`
|
||||
Weight int32 `json:"weight"`
|
||||
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"`
|
||||
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"`
|
||||
}
|
||||
|
||||
// CreateMultiSigResult models the data returned from the createmultisig
|
||||
// command.
|
||||
type CreateMultiSigResult struct {
|
||||
Address string `json:"address"`
|
||||
RedeemScript string `json:"redeemScript"`
|
||||
}
|
||||
|
||||
// DecodeScriptResult models the data returned from the decodescript command.
|
||||
type DecodeScriptResult struct {
|
||||
Asm string `json:"asm"`
|
||||
ReqSigs int32 `json:"reqSigs,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Addresses []string `json:"addresses,omitempty"`
|
||||
P2sh string `json:"p2sh,omitempty"`
|
||||
}
|
||||
|
||||
// GetAddedNodeInfoResultAddr models the data of the addresses portion of the
|
||||
// getaddednodeinfo command.
|
||||
type GetAddedNodeInfoResultAddr struct {
|
||||
Address string `json:"address"`
|
||||
Connected string `json:"connected"`
|
||||
}
|
||||
|
||||
// GetAddedNodeInfoResult models the data from the getaddednodeinfo command.
|
||||
type GetAddedNodeInfoResult struct {
|
||||
AddedNode string `json:"addednode"`
|
||||
Connected *bool `json:"connected,omitempty"`
|
||||
Addresses *[]GetAddedNodeInfoResultAddr `json:"addresses,omitempty"`
|
||||
}
|
||||
|
||||
// SoftForkDescription describes the current state of a soft-fork which was
|
||||
// deployed using a super-majority block signalling.
|
||||
type SoftForkDescription struct {
|
||||
ID string `json:"id"`
|
||||
Version uint32 `json:"version"`
|
||||
Reject struct {
|
||||
Status bool `json:"status"`
|
||||
} `json:"reject"`
|
||||
}
|
||||
|
||||
// Bip9SoftForkDescription describes the current state of a defined BIP0009
|
||||
// version bits soft-fork.
|
||||
type Bip9SoftForkDescription struct {
|
||||
Status string `json:"status"`
|
||||
Bit uint8 `json:"bit"`
|
||||
StartTime int64 `json:"startTime"`
|
||||
Timeout int64 `json:"timeout"`
|
||||
Since int32 `json:"since"`
|
||||
}
|
||||
|
||||
// GetBlockChainInfoResult models the data returned from the getblockchaininfo
|
||||
// command.
|
||||
type GetBlockChainInfoResult struct {
|
||||
Chain string `json:"chain"`
|
||||
Blocks int32 `json:"blocks"`
|
||||
Headers int32 `json:"headers"`
|
||||
BestBlockHash string `json:"bestblockhash"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
MedianTime int64 `json:"mediantime"`
|
||||
VerificationProgress float64 `json:"verificationprogress,omitempty"`
|
||||
Pruned bool `json:"pruned"`
|
||||
PruneHeight int32 `json:"pruneheight,omitempty"`
|
||||
ChainWork string `json:"chainwork,omitempty"`
|
||||
SoftForks []*SoftForkDescription `json:"softforks"`
|
||||
Bip9SoftForks map[string]*Bip9SoftForkDescription `json:"bip9_softforks"`
|
||||
}
|
||||
|
||||
// GetBlockTemplateResultTx models the transactions field of the
|
||||
// getblocktemplate command.
|
||||
type GetBlockTemplateResultTx struct {
|
||||
Data string `json:"data"`
|
||||
Hash string `json:"hash"`
|
||||
Depends []int64 `json:"depends"`
|
||||
Fee int64 `json:"fee"`
|
||||
SigOps int64 `json:"sigops"`
|
||||
Weight int64 `json:"weight"`
|
||||
}
|
||||
|
||||
// GetBlockTemplateResultAux models the coinbaseaux field of the
|
||||
// getblocktemplate command.
|
||||
type GetBlockTemplateResultAux struct {
|
||||
Flags string `json:"flags"`
|
||||
}
|
||||
|
||||
// GetBlockTemplateResult models the data returned from the getblocktemplate
|
||||
// command.
|
||||
type GetBlockTemplateResult struct {
|
||||
// Base fields from BIP 0022. CoinbaseAux is optional. One of
|
||||
// CoinbaseTxn or CoinbaseValue must be specified, but not both.
|
||||
Bits string `json:"bits"`
|
||||
CurTime int64 `json:"curtime"`
|
||||
Height int64 `json:"height"`
|
||||
PreviousHash string `json:"previousblockhash"`
|
||||
SigOpLimit int64 `json:"sigoplimit,omitempty"`
|
||||
SizeLimit int64 `json:"sizelimit,omitempty"`
|
||||
WeightLimit int64 `json:"weightlimit,omitempty"`
|
||||
Transactions []GetBlockTemplateResultTx `json:"transactions"`
|
||||
Version int32 `json:"version"`
|
||||
CoinbaseAux *GetBlockTemplateResultAux `json:"coinbaseaux,omitempty"`
|
||||
CoinbaseTxn *GetBlockTemplateResultTx `json:"coinbasetxn,omitempty"`
|
||||
CoinbaseValue *int64 `json:"coinbasevalue,omitempty"`
|
||||
WorkID string `json:"workid,omitempty"`
|
||||
|
||||
// Witness commitment defined in BIP 0141.
|
||||
DefaultWitnessCommitment string `json:"default_witness_commitment,omitempty"`
|
||||
|
||||
// Optional long polling from BIP 0022.
|
||||
LongPollID string `json:"longpollid,omitempty"`
|
||||
LongPollURI string `json:"longpolluri,omitempty"`
|
||||
SubmitOld *bool `json:"submitold,omitempty"`
|
||||
|
||||
// Basic pool extension from BIP 0023.
|
||||
Target string `json:"target,omitempty"`
|
||||
Expires int64 `json:"expires,omitempty"`
|
||||
|
||||
// Mutations from BIP 0023.
|
||||
MaxTime int64 `json:"maxtime,omitempty"`
|
||||
MinTime int64 `json:"mintime,omitempty"`
|
||||
Mutable []string `json:"mutable,omitempty"`
|
||||
NonceRange string `json:"noncerange,omitempty"`
|
||||
|
||||
// Block proposal from BIP 0023.
|
||||
Capabilities []string `json:"capabilities,omitempty"`
|
||||
RejectReasion string `json:"reject-reason,omitempty"`
|
||||
}
|
||||
|
||||
// GetMempoolEntryResult models the data returned from the getmempoolentry
|
||||
// command.
|
||||
type GetMempoolEntryResult struct {
|
||||
Size int32 `json:"size"`
|
||||
Fee float64 `json:"fee"`
|
||||
ModifiedFee float64 `json:"modifiedfee"`
|
||||
Time int64 `json:"time"`
|
||||
Height int64 `json:"height"`
|
||||
StartingPriority float64 `json:"startingpriority"`
|
||||
CurrentPriority float64 `json:"currentpriority"`
|
||||
DescendantCount int64 `json:"descendantcount"`
|
||||
DescendantSize int64 `json:"descendantsize"`
|
||||
DescendantFees float64 `json:"descendantfees"`
|
||||
AncestorCount int64 `json:"ancestorcount"`
|
||||
AncestorSize int64 `json:"ancestorsize"`
|
||||
AncestorFees float64 `json:"ancestorfees"`
|
||||
Depends []string `json:"depends"`
|
||||
}
|
||||
|
||||
// GetMempoolInfoResult models the data returned from the getmempoolinfo
|
||||
// command.
|
||||
type GetMempoolInfoResult struct {
|
||||
Size int64 `json:"size"`
|
||||
Bytes int64 `json:"bytes"`
|
||||
}
|
||||
|
||||
// NetworksResult models the networks data from the getnetworkinfo command.
|
||||
type NetworksResult struct {
|
||||
Name string `json:"name"`
|
||||
Limited bool `json:"limited"`
|
||||
Reachable bool `json:"reachable"`
|
||||
Proxy string `json:"proxy"`
|
||||
ProxyRandomizeCredentials bool `json:"proxy_randomize_credentials"`
|
||||
}
|
||||
|
||||
// LocalAddressesResult models the localaddresses data from the getnetworkinfo
|
||||
// command.
|
||||
type LocalAddressesResult struct {
|
||||
Address string `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
Score int32 `json:"score"`
|
||||
}
|
||||
|
||||
// GetNetworkInfoResult models the data returned from the getnetworkinfo
|
||||
// command.
|
||||
type GetNetworkInfoResult struct {
|
||||
Version int32 `json:"version"`
|
||||
SubVersion string `json:"subversion"`
|
||||
ProtocolVersion int32 `json:"protocolversion"`
|
||||
LocalServices string `json:"localservices"`
|
||||
LocalRelay bool `json:"localrelay"`
|
||||
TimeOffset int64 `json:"timeoffset"`
|
||||
Connections int32 `json:"connections"`
|
||||
NetworkActive bool `json:"networkactive"`
|
||||
Networks []NetworksResult `json:"networks"`
|
||||
RelayFee float64 `json:"relayfee"`
|
||||
IncrementalFee float64 `json:"incrementalfee"`
|
||||
LocalAddresses []LocalAddressesResult `json:"localaddresses"`
|
||||
Warnings string `json:"warnings"`
|
||||
}
|
||||
|
||||
// GetPeerInfoResult models the data returned from the getpeerinfo command.
|
||||
type GetPeerInfoResult struct {
|
||||
ID int32 `json:"id"`
|
||||
Addr string `json:"addr"`
|
||||
AddrLocal string `json:"addrlocal,omitempty"`
|
||||
Services string `json:"services"`
|
||||
RelayTxes bool `json:"relaytxes"`
|
||||
LastSend int64 `json:"lastsend"`
|
||||
LastRecv int64 `json:"lastrecv"`
|
||||
BytesSent uint64 `json:"bytessent"`
|
||||
BytesRecv uint64 `json:"bytesrecv"`
|
||||
ConnTime int64 `json:"conntime"`
|
||||
TimeOffset int64 `json:"timeoffset"`
|
||||
PingTime float64 `json:"pingtime"`
|
||||
PingWait float64 `json:"pingwait,omitempty"`
|
||||
Version uint32 `json:"version"`
|
||||
SubVer string `json:"subver"`
|
||||
Inbound bool `json:"inbound"`
|
||||
StartingHeight int32 `json:"startingheight"`
|
||||
CurrentHeight int32 `json:"currentheight,omitempty"`
|
||||
BanScore int32 `json:"banscore"`
|
||||
FeeFilter int64 `json:"feefilter"`
|
||||
SyncNode bool `json:"syncnode"`
|
||||
}
|
||||
|
||||
// GetRawMempoolVerboseResult models the data returned from the getrawmempool
|
||||
// command when the verbose flag is set. When the verbose flag is not set,
|
||||
// getrawmempool returns an array of transaction hashes.
|
||||
type GetRawMempoolVerboseResult struct {
|
||||
Size int32 `json:"size"`
|
||||
Vsize int32 `json:"vsize"`
|
||||
Fee float64 `json:"fee"`
|
||||
Time int64 `json:"time"`
|
||||
Height int64 `json:"height"`
|
||||
StartingPriority float64 `json:"startingpriority"`
|
||||
CurrentPriority float64 `json:"currentpriority"`
|
||||
Depends []string `json:"depends"`
|
||||
}
|
||||
|
||||
// ScriptPubKeyResult models the scriptPubKey data of a tx script. It is
|
||||
// defined separately since it is used by multiple commands.
|
||||
type ScriptPubKeyResult struct {
|
||||
Asm string `json:"asm"`
|
||||
Hex string `json:"hex,omitempty"`
|
||||
ReqSigs int32 `json:"reqSigs,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Addresses []string `json:"addresses,omitempty"`
|
||||
}
|
||||
|
||||
// GetTxOutResult models the data from the gettxout command.
|
||||
type GetTxOutResult struct {
|
||||
BestBlock string `json:"bestblock"`
|
||||
Confirmations int64 `json:"confirmations"`
|
||||
Value float64 `json:"value"`
|
||||
ScriptPubKey ScriptPubKeyResult `json:"scriptPubKey"`
|
||||
Version int32 `json:"version"`
|
||||
Coinbase bool `json:"coinbase"`
|
||||
}
|
||||
|
||||
// GetNetTotalsResult models the data returned from the getnettotals command.
|
||||
type GetNetTotalsResult struct {
|
||||
TotalBytesRecv uint64 `json:"totalbytesrecv"`
|
||||
TotalBytesSent uint64 `json:"totalbytessent"`
|
||||
TimeMillis int64 `json:"timemillis"`
|
||||
}
|
||||
|
||||
// ScriptSig models a signature script. It is defined separately since it only
|
||||
// applies to non-coinbase. Therefore the field in the Vin structure needs
|
||||
// to be a pointer.
|
||||
type ScriptSig struct {
|
||||
Asm string `json:"asm"`
|
||||
Hex string `json:"hex"`
|
||||
}
|
||||
|
||||
// Vin models parts of the tx data. It is defined separately since
|
||||
// getrawtransaction, decoderawtransaction, and searchrawtransaction use the
|
||||
// same structure.
|
||||
type Vin struct {
|
||||
Coinbase string `json:"coinbase"`
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
ScriptSig *ScriptSig `json:"scriptSig"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
Witness []string `json:"txinwitness"`
|
||||
}
|
||||
|
||||
// IsCoinBase returns a bool to show if a Vin is a Coinbase one or not.
|
||||
func (v *Vin) IsCoinBase() bool {
|
||||
return len(v.Coinbase) > 0
|
||||
}
|
||||
|
||||
// HasWitness returns a bool to show if a Vin has any witness data associated
|
||||
// with it or not.
|
||||
func (v *Vin) HasWitness() bool {
|
||||
return len(v.Witness) > 0
|
||||
}
|
||||
|
||||
// MarshalJSON provides a custom Marshal method for Vin.
|
||||
func (v *Vin) MarshalJSON() ([]byte, error) {
|
||||
if v.IsCoinBase() {
|
||||
coinbaseStruct := struct {
|
||||
Coinbase string `json:"coinbase"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
Witness []string `json:"witness,omitempty"`
|
||||
}{
|
||||
Coinbase: v.Coinbase,
|
||||
Sequence: v.Sequence,
|
||||
Witness: v.Witness,
|
||||
}
|
||||
return json.Marshal(coinbaseStruct)
|
||||
}
|
||||
|
||||
if v.HasWitness() {
|
||||
txStruct := struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
ScriptSig *ScriptSig `json:"scriptSig"`
|
||||
Witness []string `json:"txinwitness"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
}{
|
||||
Txid: v.Txid,
|
||||
Vout: v.Vout,
|
||||
ScriptSig: v.ScriptSig,
|
||||
Witness: v.Witness,
|
||||
Sequence: v.Sequence,
|
||||
}
|
||||
return json.Marshal(txStruct)
|
||||
}
|
||||
|
||||
txStruct := struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
ScriptSig *ScriptSig `json:"scriptSig"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
}{
|
||||
Txid: v.Txid,
|
||||
Vout: v.Vout,
|
||||
ScriptSig: v.ScriptSig,
|
||||
Sequence: v.Sequence,
|
||||
}
|
||||
return json.Marshal(txStruct)
|
||||
}
|
||||
|
||||
// PrevOut represents previous output for an input Vin.
|
||||
type PrevOut struct {
|
||||
Addresses []string `json:"addresses,omitempty"`
|
||||
Value float64 `json:"value"`
|
||||
}
|
||||
|
||||
// VinPrevOut is like Vin except it includes PrevOut. It is used by searchrawtransaction
|
||||
type VinPrevOut struct {
|
||||
Coinbase string `json:"coinbase"`
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
ScriptSig *ScriptSig `json:"scriptSig"`
|
||||
Witness []string `json:"txinwitness"`
|
||||
PrevOut *PrevOut `json:"prevOut"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
}
|
||||
|
||||
// IsCoinBase returns a bool to show if a Vin is a Coinbase one or not.
|
||||
func (v *VinPrevOut) IsCoinBase() bool {
|
||||
return len(v.Coinbase) > 0
|
||||
}
|
||||
|
||||
// HasWitness returns a bool to show if a Vin has any witness data associated
|
||||
// with it or not.
|
||||
func (v *VinPrevOut) HasWitness() bool {
|
||||
return len(v.Witness) > 0
|
||||
}
|
||||
|
||||
// MarshalJSON provides a custom Marshal method for VinPrevOut.
|
||||
func (v *VinPrevOut) MarshalJSON() ([]byte, error) {
|
||||
if v.IsCoinBase() {
|
||||
coinbaseStruct := struct {
|
||||
Coinbase string `json:"coinbase"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
}{
|
||||
Coinbase: v.Coinbase,
|
||||
Sequence: v.Sequence,
|
||||
}
|
||||
return json.Marshal(coinbaseStruct)
|
||||
}
|
||||
|
||||
if v.HasWitness() {
|
||||
txStruct := struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
ScriptSig *ScriptSig `json:"scriptSig"`
|
||||
Witness []string `json:"txinwitness"`
|
||||
PrevOut *PrevOut `json:"prevOut,omitempty"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
}{
|
||||
Txid: v.Txid,
|
||||
Vout: v.Vout,
|
||||
ScriptSig: v.ScriptSig,
|
||||
Witness: v.Witness,
|
||||
PrevOut: v.PrevOut,
|
||||
Sequence: v.Sequence,
|
||||
}
|
||||
return json.Marshal(txStruct)
|
||||
}
|
||||
|
||||
txStruct := struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
ScriptSig *ScriptSig `json:"scriptSig"`
|
||||
PrevOut *PrevOut `json:"prevOut,omitempty"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
}{
|
||||
Txid: v.Txid,
|
||||
Vout: v.Vout,
|
||||
ScriptSig: v.ScriptSig,
|
||||
PrevOut: v.PrevOut,
|
||||
Sequence: v.Sequence,
|
||||
}
|
||||
return json.Marshal(txStruct)
|
||||
}
|
||||
|
||||
// Vout models parts of the tx data. It is defined separately since both
|
||||
// getrawtransaction and decoderawtransaction use the same structure.
|
||||
type Vout struct {
|
||||
Value float64 `json:"value"`
|
||||
N uint32 `json:"n"`
|
||||
ScriptPubKey ScriptPubKeyResult `json:"scriptPubKey"`
|
||||
}
|
||||
|
||||
// GetMiningInfoResult models the data from the getmininginfo command.
|
||||
type GetMiningInfoResult struct {
|
||||
Blocks int64 `json:"blocks"`
|
||||
CurrentBlockSize uint64 `json:"currentblocksize"`
|
||||
CurrentBlockWeight uint64 `json:"currentblockweight"`
|
||||
CurrentBlockTx uint64 `json:"currentblocktx"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
Errors string `json:"errors"`
|
||||
Generate bool `json:"generate"`
|
||||
GenProcLimit int32 `json:"genproclimit"`
|
||||
HashesPerSec int64 `json:"hashespersec"`
|
||||
NetworkHashPS int64 `json:"networkhashps"`
|
||||
PooledTx uint64 `json:"pooledtx"`
|
||||
TestNet bool `json:"testnet"`
|
||||
}
|
||||
|
||||
// GetWorkResult models the data from the getwork command.
|
||||
type GetWorkResult struct {
|
||||
Data string `json:"data"`
|
||||
Hash1 string `json:"hash1"`
|
||||
Midstate string `json:"midstate"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
// InfoChainResult models the data returned by the chain server getinfo command.
|
||||
type InfoChainResult struct {
|
||||
Version int32 `json:"version"`
|
||||
ProtocolVersion int32 `json:"protocolversion"`
|
||||
Blocks int32 `json:"blocks"`
|
||||
TimeOffset int64 `json:"timeoffset"`
|
||||
Connections int32 `json:"connections"`
|
||||
Proxy string `json:"proxy"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
TestNet bool `json:"testnet"`
|
||||
RelayFee float64 `json:"relayfee"`
|
||||
Errors string `json:"errors"`
|
||||
}
|
||||
|
||||
// TxRawResult models the data from the getrawtransaction command.
|
||||
type TxRawResult struct {
|
||||
Hex string `json:"hex"`
|
||||
Txid string `json:"txid"`
|
||||
Hash string `json:"hash,omitempty"`
|
||||
Size int32 `json:"size,omitempty"`
|
||||
Vsize int32 `json:"vsize,omitempty"`
|
||||
Version int32 `json:"version"`
|
||||
LockTime uint32 `json:"locktime"`
|
||||
Vin []Vin `json:"vin"`
|
||||
Vout []Vout `json:"vout"`
|
||||
BlockHash string `json:"blockhash,omitempty"`
|
||||
Confirmations uint64 `json:"confirmations,omitempty"`
|
||||
Time int64 `json:"time,omitempty"`
|
||||
Blocktime int64 `json:"blocktime,omitempty"`
|
||||
}
|
||||
|
||||
// SearchRawTransactionsResult models the data from the searchrawtransaction
|
||||
// command.
|
||||
type SearchRawTransactionsResult struct {
|
||||
Hex string `json:"hex,omitempty"`
|
||||
Txid string `json:"txid"`
|
||||
Hash string `json:"hash"`
|
||||
Size string `json:"size"`
|
||||
Vsize string `json:"vsize"`
|
||||
Version int32 `json:"version"`
|
||||
LockTime uint32 `json:"locktime"`
|
||||
Vin []VinPrevOut `json:"vin"`
|
||||
Vout []Vout `json:"vout"`
|
||||
BlockHash string `json:"blockhash,omitempty"`
|
||||
Confirmations uint64 `json:"confirmations,omitempty"`
|
||||
Time int64 `json:"time,omitempty"`
|
||||
Blocktime int64 `json:"blocktime,omitempty"`
|
||||
}
|
||||
|
||||
// TxRawDecodeResult models the data from the decoderawtransaction command.
|
||||
type TxRawDecodeResult struct {
|
||||
Txid string `json:"txid"`
|
||||
Version int32 `json:"version"`
|
||||
Locktime uint32 `json:"locktime"`
|
||||
Vin []Vin `json:"vin"`
|
||||
Vout []Vout `json:"vout"`
|
||||
}
|
||||
|
||||
// ValidateAddressChainResult models the data returned by the chain server
|
||||
// validateaddress command.
|
||||
type ValidateAddressChainResult struct {
|
||||
IsValid bool `json:"isvalid"`
|
||||
Address string `json:"address,omitempty"`
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestChainSvrCustomResults ensures any results that have custom marshalling
|
||||
// work as inteded.
|
||||
// and unmarshal code of results are as expected.
|
||||
func TestChainSvrCustomResults(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
result interface{}
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "custom vin marshal with coinbase",
|
||||
result: &btcjson.Vin{
|
||||
Coinbase: "021234",
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
expected: `{"coinbase":"021234","sequence":4294967295}`,
|
||||
},
|
||||
{
|
||||
name: "custom vin marshal without coinbase",
|
||||
result: &btcjson.Vin{
|
||||
Txid: "123",
|
||||
Vout: 1,
|
||||
ScriptSig: &btcjson.ScriptSig{
|
||||
Asm: "0",
|
||||
Hex: "00",
|
||||
},
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
expected: `{"txid":"123","vout":1,"scriptSig":{"asm":"0","hex":"00"},"sequence":4294967295}`,
|
||||
},
|
||||
{
|
||||
name: "custom vinprevout marshal with coinbase",
|
||||
result: &btcjson.VinPrevOut{
|
||||
Coinbase: "021234",
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
expected: `{"coinbase":"021234","sequence":4294967295}`,
|
||||
},
|
||||
{
|
||||
name: "custom vinprevout marshal without coinbase",
|
||||
result: &btcjson.VinPrevOut{
|
||||
Txid: "123",
|
||||
Vout: 1,
|
||||
ScriptSig: &btcjson.ScriptSig{
|
||||
Asm: "0",
|
||||
Hex: "00",
|
||||
},
|
||||
PrevOut: &btcjson.PrevOut{
|
||||
Addresses: []string{"addr1"},
|
||||
Value: 0,
|
||||
},
|
||||
Sequence: 4294967295,
|
||||
},
|
||||
expected: `{"txid":"123","vout":1,"scriptSig":{"asm":"0","hex":"00"},"prevOut":{"addresses":["addr1"],"value":0},"sequence":4294967295}`,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
marshalled, err := json.Marshal(test.result)
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
if string(marshalled) != test.expected {
|
||||
t.Errorf("Test #%d (%s) unexpected marhsalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
// Copyright (c) 2014-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2017 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// NOTE: This file is intended to house the RPC commands that are supported by
|
||||
// a chain server, but are only available via websockets.
|
||||
|
||||
package btcjson
|
||||
|
||||
// AuthenticateCmd defines the authenticate JSON-RPC command.
|
||||
type AuthenticateCmd struct {
|
||||
Username string
|
||||
Passphrase string
|
||||
}
|
||||
|
||||
// NewAuthenticateCmd returns a new instance which can be used to issue an
|
||||
// authenticate JSON-RPC command.
|
||||
func NewAuthenticateCmd(username, passphrase string) *AuthenticateCmd {
|
||||
return &AuthenticateCmd{
|
||||
Username: username,
|
||||
Passphrase: passphrase,
|
||||
}
|
||||
}
|
||||
|
||||
// NotifyBlocksCmd defines the notifyblocks JSON-RPC command.
|
||||
type NotifyBlocksCmd struct{}
|
||||
|
||||
// NewNotifyBlocksCmd returns a new instance which can be used to issue a
|
||||
// notifyblocks JSON-RPC command.
|
||||
func NewNotifyBlocksCmd() *NotifyBlocksCmd {
|
||||
return &NotifyBlocksCmd{}
|
||||
}
|
||||
|
||||
// StopNotifyBlocksCmd defines the stopnotifyblocks JSON-RPC command.
|
||||
type StopNotifyBlocksCmd struct{}
|
||||
|
||||
// NewStopNotifyBlocksCmd returns a new instance which can be used to issue a
|
||||
// stopnotifyblocks JSON-RPC command.
|
||||
func NewStopNotifyBlocksCmd() *StopNotifyBlocksCmd {
|
||||
return &StopNotifyBlocksCmd{}
|
||||
}
|
||||
|
||||
// NotifyNewTransactionsCmd defines the notifynewtransactions JSON-RPC command.
|
||||
type NotifyNewTransactionsCmd struct {
|
||||
Verbose *bool `jsonrpcdefault:"false"`
|
||||
}
|
||||
|
||||
// NewNotifyNewTransactionsCmd returns a new instance which can be used to issue
|
||||
// a notifynewtransactions JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewNotifyNewTransactionsCmd(verbose *bool) *NotifyNewTransactionsCmd {
|
||||
return &NotifyNewTransactionsCmd{
|
||||
Verbose: verbose,
|
||||
}
|
||||
}
|
||||
|
||||
// SessionCmd defines the session JSON-RPC command.
|
||||
type SessionCmd struct{}
|
||||
|
||||
// NewSessionCmd returns a new instance which can be used to issue a session
|
||||
// JSON-RPC command.
|
||||
func NewSessionCmd() *SessionCmd {
|
||||
return &SessionCmd{}
|
||||
}
|
||||
|
||||
// StopNotifyNewTransactionsCmd defines the stopnotifynewtransactions JSON-RPC command.
|
||||
type StopNotifyNewTransactionsCmd struct{}
|
||||
|
||||
// NewStopNotifyNewTransactionsCmd returns a new instance which can be used to issue
|
||||
// a stopnotifynewtransactions JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewStopNotifyNewTransactionsCmd() *StopNotifyNewTransactionsCmd {
|
||||
return &StopNotifyNewTransactionsCmd{}
|
||||
}
|
||||
|
||||
// NotifyReceivedCmd defines the notifyreceived JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use LoadTxFilterCmd instead.
|
||||
type NotifyReceivedCmd struct {
|
||||
Addresses []string
|
||||
}
|
||||
|
||||
// NewNotifyReceivedCmd returns a new instance which can be used to issue a
|
||||
// notifyreceived JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewLoadTxFilterCmd instead.
|
||||
func NewNotifyReceivedCmd(addresses []string) *NotifyReceivedCmd {
|
||||
return &NotifyReceivedCmd{
|
||||
Addresses: addresses,
|
||||
}
|
||||
}
|
||||
|
||||
// OutPoint describes a transaction outpoint that will be marshalled to and
|
||||
// from JSON.
|
||||
type OutPoint struct {
|
||||
Hash string `json:"hash"`
|
||||
Index uint32 `json:"index"`
|
||||
}
|
||||
|
||||
// LoadTxFilterCmd defines the loadtxfilter request parameters to load or
|
||||
// reload a transaction filter.
|
||||
//
|
||||
// NOTE: This is a btcd extension ported from github.com/decred/dcrd/dcrjson
|
||||
// and requires a websocket connection.
|
||||
type LoadTxFilterCmd struct {
|
||||
Reload bool
|
||||
Addresses []string
|
||||
OutPoints []OutPoint
|
||||
}
|
||||
|
||||
// NewLoadTxFilterCmd returns a new instance which can be used to issue a
|
||||
// loadtxfilter JSON-RPC command.
|
||||
//
|
||||
// NOTE: This is a btcd extension ported from github.com/decred/dcrd/dcrjson
|
||||
// and requires a websocket connection.
|
||||
func NewLoadTxFilterCmd(reload bool, addresses []string, outPoints []OutPoint) *LoadTxFilterCmd {
|
||||
return &LoadTxFilterCmd{
|
||||
Reload: reload,
|
||||
Addresses: addresses,
|
||||
OutPoints: outPoints,
|
||||
}
|
||||
}
|
||||
|
||||
// NotifySpentCmd defines the notifyspent JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use LoadTxFilterCmd instead.
|
||||
type NotifySpentCmd struct {
|
||||
OutPoints []OutPoint
|
||||
}
|
||||
|
||||
// NewNotifySpentCmd returns a new instance which can be used to issue a
|
||||
// notifyspent JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewLoadTxFilterCmd instead.
|
||||
func NewNotifySpentCmd(outPoints []OutPoint) *NotifySpentCmd {
|
||||
return &NotifySpentCmd{
|
||||
OutPoints: outPoints,
|
||||
}
|
||||
}
|
||||
|
||||
// StopNotifyReceivedCmd defines the stopnotifyreceived JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use LoadTxFilterCmd instead.
|
||||
type StopNotifyReceivedCmd struct {
|
||||
Addresses []string
|
||||
}
|
||||
|
||||
// NewStopNotifyReceivedCmd returns a new instance which can be used to issue a
|
||||
// stopnotifyreceived JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewLoadTxFilterCmd instead.
|
||||
func NewStopNotifyReceivedCmd(addresses []string) *StopNotifyReceivedCmd {
|
||||
return &StopNotifyReceivedCmd{
|
||||
Addresses: addresses,
|
||||
}
|
||||
}
|
||||
|
||||
// StopNotifySpentCmd defines the stopnotifyspent JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use LoadTxFilterCmd instead.
|
||||
type StopNotifySpentCmd struct {
|
||||
OutPoints []OutPoint
|
||||
}
|
||||
|
||||
// NewStopNotifySpentCmd returns a new instance which can be used to issue a
|
||||
// stopnotifyspent JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewLoadTxFilterCmd instead.
|
||||
func NewStopNotifySpentCmd(outPoints []OutPoint) *StopNotifySpentCmd {
|
||||
return &StopNotifySpentCmd{
|
||||
OutPoints: outPoints,
|
||||
}
|
||||
}
|
||||
|
||||
// RescanCmd defines the rescan JSON-RPC command.
|
||||
//
|
||||
// NOTE: Deprecated. Use RescanBlocksCmd instead.
|
||||
type RescanCmd struct {
|
||||
BeginBlock string
|
||||
Addresses []string
|
||||
OutPoints []OutPoint
|
||||
EndBlock *string
|
||||
}
|
||||
|
||||
// NewRescanCmd returns a new instance which can be used to issue a rescan
|
||||
// JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewRescanBlocksCmd instead.
|
||||
func NewRescanCmd(beginBlock string, addresses []string, outPoints []OutPoint, endBlock *string) *RescanCmd {
|
||||
return &RescanCmd{
|
||||
BeginBlock: beginBlock,
|
||||
Addresses: addresses,
|
||||
OutPoints: outPoints,
|
||||
EndBlock: endBlock,
|
||||
}
|
||||
}
|
||||
|
||||
// RescanBlocksCmd defines the rescan JSON-RPC command.
|
||||
//
|
||||
// NOTE: This is a btcd extension ported from github.com/decred/dcrd/dcrjson
|
||||
// and requires a websocket connection.
|
||||
type RescanBlocksCmd struct {
|
||||
// Block hashes as a string array.
|
||||
BlockHashes []string
|
||||
}
|
||||
|
||||
// NewRescanBlocksCmd returns a new instance which can be used to issue a rescan
|
||||
// JSON-RPC command.
|
||||
//
|
||||
// NOTE: This is a btcd extension ported from github.com/decred/dcrd/dcrjson
|
||||
// and requires a websocket connection.
|
||||
func NewRescanBlocksCmd(blockHashes []string) *RescanBlocksCmd {
|
||||
return &RescanBlocksCmd{BlockHashes: blockHashes}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// The commands in this file are only usable by websockets.
|
||||
flags := UFWebsocketOnly
|
||||
|
||||
MustRegisterCmd("authenticate", (*AuthenticateCmd)(nil), flags)
|
||||
MustRegisterCmd("loadtxfilter", (*LoadTxFilterCmd)(nil), flags)
|
||||
MustRegisterCmd("notifyblocks", (*NotifyBlocksCmd)(nil), flags)
|
||||
MustRegisterCmd("notifynewtransactions", (*NotifyNewTransactionsCmd)(nil), flags)
|
||||
MustRegisterCmd("notifyreceived", (*NotifyReceivedCmd)(nil), flags)
|
||||
MustRegisterCmd("notifyspent", (*NotifySpentCmd)(nil), flags)
|
||||
MustRegisterCmd("session", (*SessionCmd)(nil), flags)
|
||||
MustRegisterCmd("stopnotifyblocks", (*StopNotifyBlocksCmd)(nil), flags)
|
||||
MustRegisterCmd("stopnotifynewtransactions", (*StopNotifyNewTransactionsCmd)(nil), flags)
|
||||
MustRegisterCmd("stopnotifyspent", (*StopNotifySpentCmd)(nil), flags)
|
||||
MustRegisterCmd("stopnotifyreceived", (*StopNotifyReceivedCmd)(nil), flags)
|
||||
MustRegisterCmd("rescan", (*RescanCmd)(nil), flags)
|
||||
MustRegisterCmd("rescanblocks", (*RescanBlocksCmd)(nil), flags)
|
||||
}
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
// Copyright (c) 2014-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2017 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestChainSvrWsCmds tests all of the chain server websocket-specific commands
|
||||
// marshal and unmarshal into valid results include handling of optional fields
|
||||
// being omitted in the marshalled command, while optional fields with defaults
|
||||
// have the default assigned on unmarshalled commands.
|
||||
func TestChainSvrWsCmds(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testID := int(1)
|
||||
tests := []struct {
|
||||
name string
|
||||
newCmd func() (interface{}, error)
|
||||
staticCmd func() interface{}
|
||||
marshalled string
|
||||
unmarshalled interface{}
|
||||
}{
|
||||
{
|
||||
name: "authenticate",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("authenticate", "user", "pass")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewAuthenticateCmd("user", "pass")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"authenticate","params":["user","pass"],"id":1}`,
|
||||
unmarshalled: &btcjson.AuthenticateCmd{Username: "user", Passphrase: "pass"},
|
||||
},
|
||||
{
|
||||
name: "notifyblocks",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("notifyblocks")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewNotifyBlocksCmd()
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"notifyblocks","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.NotifyBlocksCmd{},
|
||||
},
|
||||
{
|
||||
name: "stopnotifyblocks",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("stopnotifyblocks")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewStopNotifyBlocksCmd()
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"stopnotifyblocks","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.StopNotifyBlocksCmd{},
|
||||
},
|
||||
{
|
||||
name: "notifynewtransactions",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("notifynewtransactions")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewNotifyNewTransactionsCmd(nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"notifynewtransactions","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.NotifyNewTransactionsCmd{
|
||||
Verbose: btcjson.Bool(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "notifynewtransactions optional",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("notifynewtransactions", true)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewNotifyNewTransactionsCmd(btcjson.Bool(true))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"notifynewtransactions","params":[true],"id":1}`,
|
||||
unmarshalled: &btcjson.NotifyNewTransactionsCmd{
|
||||
Verbose: btcjson.Bool(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stopnotifynewtransactions",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("stopnotifynewtransactions")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewStopNotifyNewTransactionsCmd()
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"stopnotifynewtransactions","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.StopNotifyNewTransactionsCmd{},
|
||||
},
|
||||
{
|
||||
name: "notifyreceived",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("notifyreceived", []string{"1Address"})
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewNotifyReceivedCmd([]string{"1Address"})
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"notifyreceived","params":[["1Address"]],"id":1}`,
|
||||
unmarshalled: &btcjson.NotifyReceivedCmd{
|
||||
Addresses: []string{"1Address"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stopnotifyreceived",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("stopnotifyreceived", []string{"1Address"})
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewStopNotifyReceivedCmd([]string{"1Address"})
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"stopnotifyreceived","params":[["1Address"]],"id":1}`,
|
||||
unmarshalled: &btcjson.StopNotifyReceivedCmd{
|
||||
Addresses: []string{"1Address"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "notifyspent",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("notifyspent", `[{"hash":"123","index":0}]`)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
ops := []btcjson.OutPoint{{Hash: "123", Index: 0}}
|
||||
return btcjson.NewNotifySpentCmd(ops)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"notifyspent","params":[[{"hash":"123","index":0}]],"id":1}`,
|
||||
unmarshalled: &btcjson.NotifySpentCmd{
|
||||
OutPoints: []btcjson.OutPoint{{Hash: "123", Index: 0}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "stopnotifyspent",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("stopnotifyspent", `[{"hash":"123","index":0}]`)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
ops := []btcjson.OutPoint{{Hash: "123", Index: 0}}
|
||||
return btcjson.NewStopNotifySpentCmd(ops)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"stopnotifyspent","params":[[{"hash":"123","index":0}]],"id":1}`,
|
||||
unmarshalled: &btcjson.StopNotifySpentCmd{
|
||||
OutPoints: []btcjson.OutPoint{{Hash: "123", Index: 0}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rescan",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("rescan", "123", `["1Address"]`, `[{"hash":"0000000000000000000000000000000000000000000000000000000000000123","index":0}]`)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
addrs := []string{"1Address"}
|
||||
ops := []btcjson.OutPoint{{
|
||||
Hash: "0000000000000000000000000000000000000000000000000000000000000123",
|
||||
Index: 0,
|
||||
}}
|
||||
return btcjson.NewRescanCmd("123", addrs, ops, nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"rescan","params":["123",["1Address"],[{"hash":"0000000000000000000000000000000000000000000000000000000000000123","index":0}]],"id":1}`,
|
||||
unmarshalled: &btcjson.RescanCmd{
|
||||
BeginBlock: "123",
|
||||
Addresses: []string{"1Address"},
|
||||
OutPoints: []btcjson.OutPoint{{Hash: "0000000000000000000000000000000000000000000000000000000000000123", Index: 0}},
|
||||
EndBlock: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rescan optional",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("rescan", "123", `["1Address"]`, `[{"hash":"123","index":0}]`, "456")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
addrs := []string{"1Address"}
|
||||
ops := []btcjson.OutPoint{{Hash: "123", Index: 0}}
|
||||
return btcjson.NewRescanCmd("123", addrs, ops, btcjson.String("456"))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"rescan","params":["123",["1Address"],[{"hash":"123","index":0}],"456"],"id":1}`,
|
||||
unmarshalled: &btcjson.RescanCmd{
|
||||
BeginBlock: "123",
|
||||
Addresses: []string{"1Address"},
|
||||
OutPoints: []btcjson.OutPoint{{Hash: "123", Index: 0}},
|
||||
EndBlock: btcjson.String("456"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "loadtxfilter",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("loadtxfilter", false, `["1Address"]`, `[{"hash":"0000000000000000000000000000000000000000000000000000000000000123","index":0}]`)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
addrs := []string{"1Address"}
|
||||
ops := []btcjson.OutPoint{{
|
||||
Hash: "0000000000000000000000000000000000000000000000000000000000000123",
|
||||
Index: 0,
|
||||
}}
|
||||
return btcjson.NewLoadTxFilterCmd(false, addrs, ops)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"loadtxfilter","params":[false,["1Address"],[{"hash":"0000000000000000000000000000000000000000000000000000000000000123","index":0}]],"id":1}`,
|
||||
unmarshalled: &btcjson.LoadTxFilterCmd{
|
||||
Reload: false,
|
||||
Addresses: []string{"1Address"},
|
||||
OutPoints: []btcjson.OutPoint{{Hash: "0000000000000000000000000000000000000000000000000000000000000123", Index: 0}},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rescanblocks",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("rescanblocks", `["0000000000000000000000000000000000000000000000000000000000000123"]`)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
blockhashes := []string{"0000000000000000000000000000000000000000000000000000000000000123"}
|
||||
return btcjson.NewRescanBlocksCmd(blockhashes)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"rescanblocks","params":[["0000000000000000000000000000000000000000000000000000000000000123"]],"id":1}`,
|
||||
unmarshalled: &btcjson.RescanBlocksCmd{
|
||||
BlockHashes: []string{"0000000000000000000000000000000000000000000000000000000000000123"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Marshal the command as created by the new static command
|
||||
// creation function.
|
||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.marshalled)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the command is created without error via the generic
|
||||
// new command creation function.
|
||||
cmd, err := test.newCmd()
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||
i, test.name, err)
|
||||
}
|
||||
|
||||
// Marshal the command as created by the generic new command
|
||||
// creation function.
|
||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.marshalled)
|
||||
continue
|
||||
}
|
||||
|
||||
var request btcjson.Request
|
||||
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||
"unmarshalling JSON-RPC request: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||
if err != nil {
|
||||
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||
"- got %s, want %s", i, test.name,
|
||||
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+304
@@ -0,0 +1,304 @@
|
||||
// Copyright (c) 2014-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2017 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// NOTE: This file is intended to house the RPC websocket notifications that are
|
||||
// supported by a chain server.
|
||||
|
||||
package btcjson
|
||||
|
||||
const (
|
||||
// BlockConnectedNtfnMethod is the legacy, deprecated method used for
|
||||
// notifications from the chain server that a block has been connected.
|
||||
//
|
||||
// NOTE: Deprecated. Use FilteredBlockConnectedNtfnMethod instead.
|
||||
BlockConnectedNtfnMethod = "blockconnected"
|
||||
|
||||
// BlockDisconnectedNtfnMethod is the legacy, deprecated method used for
|
||||
// notifications from the chain server that a block has been
|
||||
// disconnected.
|
||||
//
|
||||
// NOTE: Deprecated. Use FilteredBlockDisconnectedNtfnMethod instead.
|
||||
BlockDisconnectedNtfnMethod = "blockdisconnected"
|
||||
|
||||
// FilteredBlockConnectedNtfnMethod is the new method used for
|
||||
// notifications from the chain server that a block has been connected.
|
||||
FilteredBlockConnectedNtfnMethod = "filteredblockconnected"
|
||||
|
||||
// FilteredBlockDisconnectedNtfnMethod is the new method used for
|
||||
// notifications from the chain server that a block has been
|
||||
// disconnected.
|
||||
FilteredBlockDisconnectedNtfnMethod = "filteredblockdisconnected"
|
||||
|
||||
// RecvTxNtfnMethod is the legacy, deprecated method used for
|
||||
// notifications from the chain server that a transaction which pays to
|
||||
// a registered address has been processed.
|
||||
//
|
||||
// NOTE: Deprecated. Use RelevantTxAcceptedNtfnMethod and
|
||||
// FilteredBlockConnectedNtfnMethod instead.
|
||||
RecvTxNtfnMethod = "recvtx"
|
||||
|
||||
// RedeemingTxNtfnMethod is the legacy, deprecated method used for
|
||||
// notifications from the chain server that a transaction which spends a
|
||||
// registered outpoint has been processed.
|
||||
//
|
||||
// NOTE: Deprecated. Use RelevantTxAcceptedNtfnMethod and
|
||||
// FilteredBlockConnectedNtfnMethod instead.
|
||||
RedeemingTxNtfnMethod = "redeemingtx"
|
||||
|
||||
// RescanFinishedNtfnMethod is the legacy, deprecated method used for
|
||||
// notifications from the chain server that a legacy, deprecated rescan
|
||||
// operation has finished.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with rescanblocks command.
|
||||
RescanFinishedNtfnMethod = "rescanfinished"
|
||||
|
||||
// RescanProgressNtfnMethod is the legacy, deprecated method used for
|
||||
// notifications from the chain server that a legacy, deprecated rescan
|
||||
// operation this is underway has made progress.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with rescanblocks command.
|
||||
RescanProgressNtfnMethod = "rescanprogress"
|
||||
|
||||
// TxAcceptedNtfnMethod is the method used for notifications from the
|
||||
// chain server that a transaction has been accepted into the mempool.
|
||||
TxAcceptedNtfnMethod = "txaccepted"
|
||||
|
||||
// TxAcceptedVerboseNtfnMethod is the method used for notifications from
|
||||
// the chain server that a transaction has been accepted into the
|
||||
// mempool. This differs from TxAcceptedNtfnMethod in that it provides
|
||||
// more details in the notification.
|
||||
TxAcceptedVerboseNtfnMethod = "txacceptedverbose"
|
||||
|
||||
// RelevantTxAcceptedNtfnMethod is the new method used for notifications
|
||||
// from the chain server that inform a client that a transaction that
|
||||
// matches the loaded filter was accepted by the mempool.
|
||||
RelevantTxAcceptedNtfnMethod = "relevanttxaccepted"
|
||||
)
|
||||
|
||||
// BlockConnectedNtfn defines the blockconnected JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use FilteredBlockConnectedNtfn instead.
|
||||
type BlockConnectedNtfn struct {
|
||||
Hash string
|
||||
Height int32
|
||||
Time int64
|
||||
}
|
||||
|
||||
// NewBlockConnectedNtfn returns a new instance which can be used to issue a
|
||||
// blockconnected JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewFilteredBlockConnectedNtfn instead.
|
||||
func NewBlockConnectedNtfn(hash string, height int32, time int64) *BlockConnectedNtfn {
|
||||
return &BlockConnectedNtfn{
|
||||
Hash: hash,
|
||||
Height: height,
|
||||
Time: time,
|
||||
}
|
||||
}
|
||||
|
||||
// BlockDisconnectedNtfn defines the blockdisconnected JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use FilteredBlockDisconnectedNtfn instead.
|
||||
type BlockDisconnectedNtfn struct {
|
||||
Hash string
|
||||
Height int32
|
||||
Time int64
|
||||
}
|
||||
|
||||
// NewBlockDisconnectedNtfn returns a new instance which can be used to issue a
|
||||
// blockdisconnected JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewFilteredBlockDisconnectedNtfn instead.
|
||||
func NewBlockDisconnectedNtfn(hash string, height int32, time int64) *BlockDisconnectedNtfn {
|
||||
return &BlockDisconnectedNtfn{
|
||||
Hash: hash,
|
||||
Height: height,
|
||||
Time: time,
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredBlockConnectedNtfn defines the filteredblockconnected JSON-RPC
|
||||
// notification.
|
||||
type FilteredBlockConnectedNtfn struct {
|
||||
Height int32
|
||||
Header string
|
||||
SubscribedTxs []string
|
||||
}
|
||||
|
||||
// NewFilteredBlockConnectedNtfn returns a new instance which can be used to
|
||||
// issue a filteredblockconnected JSON-RPC notification.
|
||||
func NewFilteredBlockConnectedNtfn(height int32, header string, subscribedTxs []string) *FilteredBlockConnectedNtfn {
|
||||
return &FilteredBlockConnectedNtfn{
|
||||
Height: height,
|
||||
Header: header,
|
||||
SubscribedTxs: subscribedTxs,
|
||||
}
|
||||
}
|
||||
|
||||
// FilteredBlockDisconnectedNtfn defines the filteredblockdisconnected JSON-RPC
|
||||
// notification.
|
||||
type FilteredBlockDisconnectedNtfn struct {
|
||||
Height int32
|
||||
Header string
|
||||
}
|
||||
|
||||
// NewFilteredBlockDisconnectedNtfn returns a new instance which can be used to
|
||||
// issue a filteredblockdisconnected JSON-RPC notification.
|
||||
func NewFilteredBlockDisconnectedNtfn(height int32, header string) *FilteredBlockDisconnectedNtfn {
|
||||
return &FilteredBlockDisconnectedNtfn{
|
||||
Height: height,
|
||||
Header: header,
|
||||
}
|
||||
}
|
||||
|
||||
// BlockDetails describes details of a tx in a block.
|
||||
type BlockDetails struct {
|
||||
Height int32 `json:"height"`
|
||||
Hash string `json:"hash"`
|
||||
Index int `json:"index"`
|
||||
Time int64 `json:"time"`
|
||||
}
|
||||
|
||||
// RecvTxNtfn defines the recvtx JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use RelevantTxAcceptedNtfn and FilteredBlockConnectedNtfn
|
||||
// instead.
|
||||
type RecvTxNtfn struct {
|
||||
HexTx string
|
||||
Block *BlockDetails
|
||||
}
|
||||
|
||||
// NewRecvTxNtfn returns a new instance which can be used to issue a recvtx
|
||||
// JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewRelevantTxAcceptedNtfn and
|
||||
// NewFilteredBlockConnectedNtfn instead.
|
||||
func NewRecvTxNtfn(hexTx string, block *BlockDetails) *RecvTxNtfn {
|
||||
return &RecvTxNtfn{
|
||||
HexTx: hexTx,
|
||||
Block: block,
|
||||
}
|
||||
}
|
||||
|
||||
// RedeemingTxNtfn defines the redeemingtx JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use RelevantTxAcceptedNtfn and FilteredBlockConnectedNtfn
|
||||
// instead.
|
||||
type RedeemingTxNtfn struct {
|
||||
HexTx string
|
||||
Block *BlockDetails
|
||||
}
|
||||
|
||||
// NewRedeemingTxNtfn returns a new instance which can be used to issue a
|
||||
// redeemingtx JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Use NewRelevantTxAcceptedNtfn and
|
||||
// NewFilteredBlockConnectedNtfn instead.
|
||||
func NewRedeemingTxNtfn(hexTx string, block *BlockDetails) *RedeemingTxNtfn {
|
||||
return &RedeemingTxNtfn{
|
||||
HexTx: hexTx,
|
||||
Block: block,
|
||||
}
|
||||
}
|
||||
|
||||
// RescanFinishedNtfn defines the rescanfinished JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with rescanblocks command.
|
||||
type RescanFinishedNtfn struct {
|
||||
Hash string
|
||||
Height int32
|
||||
Time int64
|
||||
}
|
||||
|
||||
// NewRescanFinishedNtfn returns a new instance which can be used to issue a
|
||||
// rescanfinished JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with rescanblocks command.
|
||||
func NewRescanFinishedNtfn(hash string, height int32, time int64) *RescanFinishedNtfn {
|
||||
return &RescanFinishedNtfn{
|
||||
Hash: hash,
|
||||
Height: height,
|
||||
Time: time,
|
||||
}
|
||||
}
|
||||
|
||||
// RescanProgressNtfn defines the rescanprogress JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with rescanblocks command.
|
||||
type RescanProgressNtfn struct {
|
||||
Hash string
|
||||
Height int32
|
||||
Time int64
|
||||
}
|
||||
|
||||
// NewRescanProgressNtfn returns a new instance which can be used to issue a
|
||||
// rescanprogress JSON-RPC notification.
|
||||
//
|
||||
// NOTE: Deprecated. Not used with rescanblocks command.
|
||||
func NewRescanProgressNtfn(hash string, height int32, time int64) *RescanProgressNtfn {
|
||||
return &RescanProgressNtfn{
|
||||
Hash: hash,
|
||||
Height: height,
|
||||
Time: time,
|
||||
}
|
||||
}
|
||||
|
||||
// TxAcceptedNtfn defines the txaccepted JSON-RPC notification.
|
||||
type TxAcceptedNtfn struct {
|
||||
TxID string
|
||||
Amount float64
|
||||
}
|
||||
|
||||
// NewTxAcceptedNtfn returns a new instance which can be used to issue a
|
||||
// txaccepted JSON-RPC notification.
|
||||
func NewTxAcceptedNtfn(txHash string, amount float64) *TxAcceptedNtfn {
|
||||
return &TxAcceptedNtfn{
|
||||
TxID: txHash,
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
// TxAcceptedVerboseNtfn defines the txacceptedverbose JSON-RPC notification.
|
||||
type TxAcceptedVerboseNtfn struct {
|
||||
RawTx TxRawResult
|
||||
}
|
||||
|
||||
// NewTxAcceptedVerboseNtfn returns a new instance which can be used to issue a
|
||||
// txacceptedverbose JSON-RPC notification.
|
||||
func NewTxAcceptedVerboseNtfn(rawTx TxRawResult) *TxAcceptedVerboseNtfn {
|
||||
return &TxAcceptedVerboseNtfn{
|
||||
RawTx: rawTx,
|
||||
}
|
||||
}
|
||||
|
||||
// RelevantTxAcceptedNtfn defines the parameters to the relevanttxaccepted
|
||||
// JSON-RPC notification.
|
||||
type RelevantTxAcceptedNtfn struct {
|
||||
Transaction string `json:"transaction"`
|
||||
}
|
||||
|
||||
// NewRelevantTxAcceptedNtfn returns a new instance which can be used to issue a
|
||||
// relevantxaccepted JSON-RPC notification.
|
||||
func NewRelevantTxAcceptedNtfn(txHex string) *RelevantTxAcceptedNtfn {
|
||||
return &RelevantTxAcceptedNtfn{Transaction: txHex}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// The commands in this file are only usable by websockets and are
|
||||
// notifications.
|
||||
flags := UFWebsocketOnly | UFNotification
|
||||
|
||||
MustRegisterCmd(BlockConnectedNtfnMethod, (*BlockConnectedNtfn)(nil), flags)
|
||||
MustRegisterCmd(BlockDisconnectedNtfnMethod, (*BlockDisconnectedNtfn)(nil), flags)
|
||||
MustRegisterCmd(FilteredBlockConnectedNtfnMethod, (*FilteredBlockConnectedNtfn)(nil), flags)
|
||||
MustRegisterCmd(FilteredBlockDisconnectedNtfnMethod, (*FilteredBlockDisconnectedNtfn)(nil), flags)
|
||||
MustRegisterCmd(RecvTxNtfnMethod, (*RecvTxNtfn)(nil), flags)
|
||||
MustRegisterCmd(RedeemingTxNtfnMethod, (*RedeemingTxNtfn)(nil), flags)
|
||||
MustRegisterCmd(RescanFinishedNtfnMethod, (*RescanFinishedNtfn)(nil), flags)
|
||||
MustRegisterCmd(RescanProgressNtfnMethod, (*RescanProgressNtfn)(nil), flags)
|
||||
MustRegisterCmd(TxAcceptedNtfnMethod, (*TxAcceptedNtfn)(nil), flags)
|
||||
MustRegisterCmd(TxAcceptedVerboseNtfnMethod, (*TxAcceptedVerboseNtfn)(nil), flags)
|
||||
MustRegisterCmd(RelevantTxAcceptedNtfnMethod, (*RelevantTxAcceptedNtfn)(nil), flags)
|
||||
}
|
||||
+296
@@ -0,0 +1,296 @@
|
||||
// Copyright (c) 2014-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2017 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestChainSvrWsNtfns tests all of the chain server websocket-specific
|
||||
// notifications marshal and unmarshal into valid results include handling of
|
||||
// optional fields being omitted in the marshalled command, while optional
|
||||
// fields with defaults have the default assigned on unmarshalled commands.
|
||||
func TestChainSvrWsNtfns(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
newNtfn func() (interface{}, error)
|
||||
staticNtfn func() interface{}
|
||||
marshalled string
|
||||
unmarshalled interface{}
|
||||
}{
|
||||
{
|
||||
name: "blockconnected",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("blockconnected", "123", 100000, 123456789)
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
return btcjson.NewBlockConnectedNtfn("123", 100000, 123456789)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"blockconnected","params":["123",100000,123456789],"id":null}`,
|
||||
unmarshalled: &btcjson.BlockConnectedNtfn{
|
||||
Hash: "123",
|
||||
Height: 100000,
|
||||
Time: 123456789,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "blockdisconnected",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("blockdisconnected", "123", 100000, 123456789)
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
return btcjson.NewBlockDisconnectedNtfn("123", 100000, 123456789)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"blockdisconnected","params":["123",100000,123456789],"id":null}`,
|
||||
unmarshalled: &btcjson.BlockDisconnectedNtfn{
|
||||
Hash: "123",
|
||||
Height: 100000,
|
||||
Time: 123456789,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filteredblockconnected",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("filteredblockconnected", 100000, "header", []string{"tx0", "tx1"})
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
return btcjson.NewFilteredBlockConnectedNtfn(100000, "header", []string{"tx0", "tx1"})
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"filteredblockconnected","params":[100000,"header",["tx0","tx1"]],"id":null}`,
|
||||
unmarshalled: &btcjson.FilteredBlockConnectedNtfn{
|
||||
Height: 100000,
|
||||
Header: "header",
|
||||
SubscribedTxs: []string{"tx0", "tx1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "filteredblockdisconnected",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("filteredblockdisconnected", 100000, "header")
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
return btcjson.NewFilteredBlockDisconnectedNtfn(100000, "header")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"filteredblockdisconnected","params":[100000,"header"],"id":null}`,
|
||||
unmarshalled: &btcjson.FilteredBlockDisconnectedNtfn{
|
||||
Height: 100000,
|
||||
Header: "header",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "recvtx",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("recvtx", "001122", `{"height":100000,"hash":"123","index":0,"time":12345678}`)
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
blockDetails := btcjson.BlockDetails{
|
||||
Height: 100000,
|
||||
Hash: "123",
|
||||
Index: 0,
|
||||
Time: 12345678,
|
||||
}
|
||||
return btcjson.NewRecvTxNtfn("001122", &blockDetails)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"recvtx","params":["001122",{"height":100000,"hash":"123","index":0,"time":12345678}],"id":null}`,
|
||||
unmarshalled: &btcjson.RecvTxNtfn{
|
||||
HexTx: "001122",
|
||||
Block: &btcjson.BlockDetails{
|
||||
Height: 100000,
|
||||
Hash: "123",
|
||||
Index: 0,
|
||||
Time: 12345678,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "redeemingtx",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("redeemingtx", "001122", `{"height":100000,"hash":"123","index":0,"time":12345678}`)
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
blockDetails := btcjson.BlockDetails{
|
||||
Height: 100000,
|
||||
Hash: "123",
|
||||
Index: 0,
|
||||
Time: 12345678,
|
||||
}
|
||||
return btcjson.NewRedeemingTxNtfn("001122", &blockDetails)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"redeemingtx","params":["001122",{"height":100000,"hash":"123","index":0,"time":12345678}],"id":null}`,
|
||||
unmarshalled: &btcjson.RedeemingTxNtfn{
|
||||
HexTx: "001122",
|
||||
Block: &btcjson.BlockDetails{
|
||||
Height: 100000,
|
||||
Hash: "123",
|
||||
Index: 0,
|
||||
Time: 12345678,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rescanfinished",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("rescanfinished", "123", 100000, 12345678)
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
return btcjson.NewRescanFinishedNtfn("123", 100000, 12345678)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"rescanfinished","params":["123",100000,12345678],"id":null}`,
|
||||
unmarshalled: &btcjson.RescanFinishedNtfn{
|
||||
Hash: "123",
|
||||
Height: 100000,
|
||||
Time: 12345678,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "rescanprogress",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("rescanprogress", "123", 100000, 12345678)
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
return btcjson.NewRescanProgressNtfn("123", 100000, 12345678)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"rescanprogress","params":["123",100000,12345678],"id":null}`,
|
||||
unmarshalled: &btcjson.RescanProgressNtfn{
|
||||
Hash: "123",
|
||||
Height: 100000,
|
||||
Time: 12345678,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "txaccepted",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("txaccepted", "123", 1.5)
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
return btcjson.NewTxAcceptedNtfn("123", 1.5)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"txaccepted","params":["123",1.5],"id":null}`,
|
||||
unmarshalled: &btcjson.TxAcceptedNtfn{
|
||||
TxID: "123",
|
||||
Amount: 1.5,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "txacceptedverbose",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("txacceptedverbose", `{"hex":"001122","txid":"123","version":1,"locktime":4294967295,"vin":null,"vout":null,"confirmations":0}`)
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
txResult := btcjson.TxRawResult{
|
||||
Hex: "001122",
|
||||
Txid: "123",
|
||||
Version: 1,
|
||||
LockTime: 4294967295,
|
||||
Vin: nil,
|
||||
Vout: nil,
|
||||
Confirmations: 0,
|
||||
}
|
||||
return btcjson.NewTxAcceptedVerboseNtfn(txResult)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"txacceptedverbose","params":[{"hex":"001122","txid":"123","version":1,"locktime":4294967295,"vin":null,"vout":null}],"id":null}`,
|
||||
unmarshalled: &btcjson.TxAcceptedVerboseNtfn{
|
||||
RawTx: btcjson.TxRawResult{
|
||||
Hex: "001122",
|
||||
Txid: "123",
|
||||
Version: 1,
|
||||
LockTime: 4294967295,
|
||||
Vin: nil,
|
||||
Vout: nil,
|
||||
Confirmations: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "relevanttxaccepted",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("relevanttxaccepted", "001122")
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
return btcjson.NewRelevantTxAcceptedNtfn("001122")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"relevanttxaccepted","params":["001122"],"id":null}`,
|
||||
unmarshalled: &btcjson.RelevantTxAcceptedNtfn{
|
||||
Transaction: "001122",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Marshal the notification as created by the new static
|
||||
// creation function. The ID is nil for notifications.
|
||||
marshalled, err := btcjson.MarshalCmd(nil, test.staticNtfn())
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.marshalled)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the notification is created without error via the
|
||||
// generic new notification creation function.
|
||||
cmd, err := test.newNtfn()
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||
i, test.name, err)
|
||||
}
|
||||
|
||||
// Marshal the notification as created by the generic new
|
||||
// notification creation function. The ID is nil for
|
||||
// notifications.
|
||||
marshalled, err = btcjson.MarshalCmd(nil, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.marshalled)
|
||||
continue
|
||||
}
|
||||
|
||||
var request btcjson.Request
|
||||
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||
"unmarshalling JSON-RPC request: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||
if err != nil {
|
||||
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||
"- got %s, want %s", i, test.name,
|
||||
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) 2015-2017 The btcsuite developers
|
||||
// Copyright (c) 2015-2017 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
// SessionResult models the data from the session command.
|
||||
type SessionResult struct {
|
||||
SessionID uint64 `json:"sessionid"`
|
||||
}
|
||||
|
||||
// RescannedBlock contains the hash and all discovered transactions of a single
|
||||
// rescanned block.
|
||||
//
|
||||
// NOTE: This is a btcsuite extension ported from
|
||||
// github.com/decred/dcrd/dcrjson.
|
||||
type RescannedBlock struct {
|
||||
Hash string `json:"hash"`
|
||||
Transactions []string `json:"transactions"`
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2017 The btcsuite developers
|
||||
// Copyright (c) 2017 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestChainSvrWsResults ensures any results that have custom marshalling
|
||||
// work as inteded.
|
||||
func TestChainSvrWsResults(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
result interface{}
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "RescannedBlock",
|
||||
result: &btcjson.RescannedBlock{
|
||||
Hash: "blockhash",
|
||||
Transactions: []string{"serializedtx"},
|
||||
},
|
||||
expected: `{"hash":"blockhash","transactions":["serializedtx"]}`,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
marshalled, err := json.Marshal(test.result)
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
if string(marshalled) != test.expected {
|
||||
t.Errorf("Test #%d (%s) unexpected marhsalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// CmdMethod returns the method for the passed command. The provided command
|
||||
// type must be a registered type. All commands provided by this package are
|
||||
// registered by default.
|
||||
func CmdMethod(cmd interface{}) (string, 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 "", makeError(ErrUnregisteredMethod, str)
|
||||
}
|
||||
|
||||
return method, nil
|
||||
}
|
||||
|
||||
// MethodUsageFlags returns the usage flags for the passed command method. The
|
||||
// provided method must be associated with a registered type. All commands
|
||||
// provided by this package are registered by default.
|
||||
func MethodUsageFlags(method string) (UsageFlag, error) {
|
||||
// Look up details about the provided method and error out if not
|
||||
// registered.
|
||||
registerLock.RLock()
|
||||
info, ok := methodToInfo[method]
|
||||
registerLock.RUnlock()
|
||||
if !ok {
|
||||
str := fmt.Sprintf("%q is not registered", method)
|
||||
return 0, makeError(ErrUnregisteredMethod, str)
|
||||
}
|
||||
|
||||
return info.flags, nil
|
||||
}
|
||||
|
||||
// subStructUsage returns a string for use in the one-line usage for the given
|
||||
// sub struct. Note that this is specifically for fields which consist of
|
||||
// structs (or an array/slice of structs) as opposed to the top-level command
|
||||
// struct.
|
||||
//
|
||||
// Any fields that include a jsonrpcusage struct tag will use that instead of
|
||||
// being automatically generated.
|
||||
func subStructUsage(structType reflect.Type) string {
|
||||
numFields := structType.NumField()
|
||||
fieldUsages := make([]string, 0, numFields)
|
||||
for i := 0; i < structType.NumField(); i++ {
|
||||
rtf := structType.Field(i)
|
||||
|
||||
// When the field has a jsonrpcusage struct tag specified use
|
||||
// that instead of automatically generating it.
|
||||
if tag := rtf.Tag.Get("jsonrpcusage"); tag != "" {
|
||||
fieldUsages = append(fieldUsages, tag)
|
||||
continue
|
||||
}
|
||||
|
||||
// Create the name/value entry for the field while considering
|
||||
// the type of the field. Not all possible types are covered
|
||||
// here and when one of the types not specifically covered is
|
||||
// encountered, the field name is simply reused for the value.
|
||||
fieldName := strings.ToLower(rtf.Name)
|
||||
fieldValue := fieldName
|
||||
fieldKind := rtf.Type.Kind()
|
||||
switch {
|
||||
case isNumeric(fieldKind):
|
||||
if fieldKind == reflect.Float32 || fieldKind == reflect.Float64 {
|
||||
fieldValue = "n.nnn"
|
||||
} else {
|
||||
fieldValue = "n"
|
||||
}
|
||||
case fieldKind == reflect.String:
|
||||
fieldValue = `"value"`
|
||||
|
||||
case fieldKind == reflect.Struct:
|
||||
fieldValue = subStructUsage(rtf.Type)
|
||||
|
||||
case fieldKind == reflect.Array || fieldKind == reflect.Slice:
|
||||
fieldValue = subArrayUsage(rtf.Type, fieldName)
|
||||
}
|
||||
|
||||
usage := fmt.Sprintf("%q:%s", fieldName, fieldValue)
|
||||
fieldUsages = append(fieldUsages, usage)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("{%s}", strings.Join(fieldUsages, ","))
|
||||
}
|
||||
|
||||
// subArrayUsage returns a string for use in the one-line usage for the given
|
||||
// array or slice. It also contains logic to convert plural field names to
|
||||
// singular so the generated usage string reads better.
|
||||
func subArrayUsage(arrayType reflect.Type, fieldName string) string {
|
||||
// Convert plural field names to singular. Only works for English.
|
||||
singularFieldName := fieldName
|
||||
if strings.HasSuffix(fieldName, "ies") {
|
||||
singularFieldName = strings.TrimSuffix(fieldName, "ies")
|
||||
singularFieldName = singularFieldName + "y"
|
||||
} else if strings.HasSuffix(fieldName, "es") {
|
||||
singularFieldName = strings.TrimSuffix(fieldName, "es")
|
||||
} else if strings.HasSuffix(fieldName, "s") {
|
||||
singularFieldName = strings.TrimSuffix(fieldName, "s")
|
||||
}
|
||||
|
||||
elemType := arrayType.Elem()
|
||||
switch elemType.Kind() {
|
||||
case reflect.String:
|
||||
return fmt.Sprintf("[%q,...]", singularFieldName)
|
||||
|
||||
case reflect.Struct:
|
||||
return fmt.Sprintf("[%s,...]", subStructUsage(elemType))
|
||||
}
|
||||
|
||||
// Fall back to simply showing the field name in array syntax.
|
||||
return fmt.Sprintf(`[%s,...]`, singularFieldName)
|
||||
}
|
||||
|
||||
// fieldUsage returns a string for use in the one-line usage for the struct
|
||||
// field of a command.
|
||||
//
|
||||
// Any fields that include a jsonrpcusage struct tag will use that instead of
|
||||
// being automatically generated.
|
||||
func fieldUsage(structField reflect.StructField, defaultVal *reflect.Value) string {
|
||||
// When the field has a jsonrpcusage struct tag specified use that
|
||||
// instead of automatically generating it.
|
||||
if tag := structField.Tag.Get("jsonrpcusage"); tag != "" {
|
||||
return tag
|
||||
}
|
||||
|
||||
// Indirect the pointer if needed.
|
||||
fieldType := structField.Type
|
||||
if fieldType.Kind() == reflect.Ptr {
|
||||
fieldType = fieldType.Elem()
|
||||
}
|
||||
|
||||
// When there is a default value, it must also be a pointer due to the
|
||||
// rules enforced by RegisterCmd.
|
||||
if defaultVal != nil {
|
||||
indirect := defaultVal.Elem()
|
||||
defaultVal = &indirect
|
||||
}
|
||||
|
||||
// Handle certain types uniquely to provide nicer usage.
|
||||
fieldName := strings.ToLower(structField.Name)
|
||||
switch fieldType.Kind() {
|
||||
case reflect.String:
|
||||
if defaultVal != nil {
|
||||
return fmt.Sprintf("%s=%q", fieldName,
|
||||
defaultVal.Interface())
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%q", fieldName)
|
||||
|
||||
case reflect.Array, reflect.Slice:
|
||||
return subArrayUsage(fieldType, fieldName)
|
||||
|
||||
case reflect.Struct:
|
||||
return subStructUsage(fieldType)
|
||||
}
|
||||
|
||||
// Simply return the field name when none of the above special cases
|
||||
// apply.
|
||||
if defaultVal != nil {
|
||||
return fmt.Sprintf("%s=%v", fieldName, defaultVal.Interface())
|
||||
}
|
||||
return fieldName
|
||||
}
|
||||
|
||||
// methodUsageText returns a one-line usage string for the provided command and
|
||||
// method info. This is the main work horse for the exported MethodUsageText
|
||||
// function.
|
||||
func methodUsageText(rtp reflect.Type, defaults map[int]reflect.Value, method string) string {
|
||||
// Generate the individual usage for each field in the command. Several
|
||||
// simplifying assumptions are made here because the RegisterCmd
|
||||
// function has already rigorously enforced the layout.
|
||||
rt := rtp.Elem()
|
||||
numFields := rt.NumField()
|
||||
reqFieldUsages := make([]string, 0, numFields)
|
||||
optFieldUsages := make([]string, 0, numFields)
|
||||
for i := 0; i < numFields; i++ {
|
||||
rtf := rt.Field(i)
|
||||
var isOptional bool
|
||||
if kind := rtf.Type.Kind(); kind == reflect.Ptr {
|
||||
isOptional = true
|
||||
}
|
||||
|
||||
var defaultVal *reflect.Value
|
||||
if defVal, ok := defaults[i]; ok {
|
||||
defaultVal = &defVal
|
||||
}
|
||||
|
||||
// Add human-readable usage to the appropriate slice that is
|
||||
// later used to generate the one-line usage.
|
||||
usage := fieldUsage(rtf, defaultVal)
|
||||
if isOptional {
|
||||
optFieldUsages = append(optFieldUsages, usage)
|
||||
} else {
|
||||
reqFieldUsages = append(reqFieldUsages, usage)
|
||||
}
|
||||
}
|
||||
|
||||
// Generate and return the one-line usage string.
|
||||
usageStr := method
|
||||
if len(reqFieldUsages) > 0 {
|
||||
usageStr += " " + strings.Join(reqFieldUsages, " ")
|
||||
}
|
||||
if len(optFieldUsages) > 0 {
|
||||
usageStr += fmt.Sprintf(" (%s)", strings.Join(optFieldUsages, " "))
|
||||
}
|
||||
return usageStr
|
||||
}
|
||||
|
||||
// MethodUsageText returns a one-line usage string for the provided method. The
|
||||
// provided method must be associated with a registered type. All commands
|
||||
// provided by this package are registered by default.
|
||||
func MethodUsageText(method string) (string, error) {
|
||||
// Look up details about the provided method and error out if not
|
||||
// registered.
|
||||
registerLock.RLock()
|
||||
rtp, ok := methodToConcreteType[method]
|
||||
info := methodToInfo[method]
|
||||
registerLock.RUnlock()
|
||||
if !ok {
|
||||
str := fmt.Sprintf("%q is not registered", method)
|
||||
return "", makeError(ErrUnregisteredMethod, str)
|
||||
}
|
||||
|
||||
// When the usage for this method has already been generated, simply
|
||||
// return it.
|
||||
if info.usage != "" {
|
||||
return info.usage, nil
|
||||
}
|
||||
|
||||
// Generate and store the usage string for future calls and return it.
|
||||
usage := methodUsageText(rtp, info.defaults, method)
|
||||
registerLock.Lock()
|
||||
info.usage = usage
|
||||
methodToInfo[method] = info
|
||||
registerLock.Unlock()
|
||||
return usage, nil
|
||||
}
|
||||
+430
@@ -0,0 +1,430 @@
|
||||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestCmdMethod tests the CmdMethod function to ensure it retunrs the expected
|
||||
// methods and errors.
|
||||
func TestCmdMethod(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cmd interface{}
|
||||
method string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "unregistered type",
|
||||
cmd: (*int)(nil),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "nil pointer of registered type",
|
||||
cmd: (*btcjson.GetBlockCmd)(nil),
|
||||
method: "getblock",
|
||||
},
|
||||
{
|
||||
name: "nil instance of registered type",
|
||||
cmd: &btcjson.GetBlockCountCmd{},
|
||||
method: "getblockcount",
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
method, err := btcjson.CmdMethod(test.cmd)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.(btcjson.Error).ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code "+
|
||||
"- got %v (%v), want %v", i, test.name,
|
||||
gotErrorCode, err,
|
||||
test.err.(btcjson.Error).ErrorCode)
|
||||
continue
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure method matches the expected value.
|
||||
if method != test.method {
|
||||
t.Errorf("Test #%d (%s) mismatched method - got %v, "+
|
||||
"want %v", i, test.name, method, test.method)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMethodUsageFlags tests the MethodUsage function ensure it returns the
|
||||
// expected flags and errors.
|
||||
func TestMethodUsageFlags(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
err error
|
||||
flags btcjson.UsageFlag
|
||||
}{
|
||||
{
|
||||
name: "unregistered type",
|
||||
method: "bogusmethod",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "getblock",
|
||||
method: "getblock",
|
||||
flags: 0,
|
||||
},
|
||||
{
|
||||
name: "walletpassphrase",
|
||||
method: "walletpassphrase",
|
||||
flags: btcjson.UFWalletOnly,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
flags, err := btcjson.MethodUsageFlags(test.method)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.(btcjson.Error).ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code "+
|
||||
"- got %v (%v), want %v", i, test.name,
|
||||
gotErrorCode, err,
|
||||
test.err.(btcjson.Error).ErrorCode)
|
||||
continue
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure flags match the expected value.
|
||||
if flags != test.flags {
|
||||
t.Errorf("Test #%d (%s) mismatched flags - got %v, "+
|
||||
"want %v", i, test.name, flags, test.flags)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMethodUsageText tests the MethodUsageText function ensure it returns the
|
||||
// expected text.
|
||||
func TestMethodUsageText(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
err error
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "unregistered type",
|
||||
method: "bogusmethod",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "getblockcount",
|
||||
method: "getblockcount",
|
||||
expected: "getblockcount",
|
||||
},
|
||||
{
|
||||
name: "getblock",
|
||||
method: "getblock",
|
||||
expected: `getblock "hash" (verbose=true verbosetx=false)`,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
usage, err := btcjson.MethodUsageText(test.method)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.(btcjson.Error).ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code "+
|
||||
"- got %v (%v), want %v", i, test.name,
|
||||
gotErrorCode, err,
|
||||
test.err.(btcjson.Error).ErrorCode)
|
||||
continue
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure usage matches the expected value.
|
||||
if usage != test.expected {
|
||||
t.Errorf("Test #%d (%s) mismatched usage - got %v, "+
|
||||
"want %v", i, test.name, usage, test.expected)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the usage again to excerise caching.
|
||||
usage, err = btcjson.MethodUsageText(test.method)
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure usage still matches the expected value.
|
||||
if usage != test.expected {
|
||||
t.Errorf("Test #%d (%s) mismatched usage - got %v, "+
|
||||
"want %v", i, test.name, usage, test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFieldUsage tests the internal fieldUsage function ensure it returns the
|
||||
// expected text.
|
||||
func TestFieldUsage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
field reflect.StructField
|
||||
defValue *reflect.Value
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "jsonrpcusage tag override",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Test int `jsonrpcusage:"testvalue"`
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: "testvalue",
|
||||
},
|
||||
{
|
||||
name: "generic interface",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Test interface{}
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `test`,
|
||||
},
|
||||
{
|
||||
name: "string without default value",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Test string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `"test"`,
|
||||
},
|
||||
{
|
||||
name: "string with default value",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Test string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: func() *reflect.Value {
|
||||
value := "default"
|
||||
rv := reflect.ValueOf(&value)
|
||||
return &rv
|
||||
}(),
|
||||
expected: `test="default"`,
|
||||
},
|
||||
{
|
||||
name: "array of strings",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Test []string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `["test",...]`,
|
||||
},
|
||||
{
|
||||
name: "array of strings with plural field name 1",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Keys []string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `["key",...]`,
|
||||
},
|
||||
{
|
||||
name: "array of strings with plural field name 2",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Addresses []string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `["address",...]`,
|
||||
},
|
||||
{
|
||||
name: "array of strings with plural field name 3",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Capabilities []string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `["capability",...]`,
|
||||
},
|
||||
{
|
||||
name: "array of structs",
|
||||
field: func() reflect.StructField {
|
||||
type s2 struct {
|
||||
Txid string
|
||||
}
|
||||
type s struct {
|
||||
Capabilities []s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `[{"txid":"value"},...]`,
|
||||
},
|
||||
{
|
||||
name: "array of ints",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Test []int
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `[test,...]`,
|
||||
},
|
||||
{
|
||||
name: "sub struct with jsonrpcusage tag override",
|
||||
field: func() reflect.StructField {
|
||||
type s2 struct {
|
||||
Test string `jsonrpcusage:"testusage"`
|
||||
}
|
||||
type s struct {
|
||||
Test s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `{testusage}`,
|
||||
},
|
||||
{
|
||||
name: "sub struct with string",
|
||||
field: func() reflect.StructField {
|
||||
type s2 struct {
|
||||
Txid string
|
||||
}
|
||||
type s struct {
|
||||
Test s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `{"txid":"value"}`,
|
||||
},
|
||||
{
|
||||
name: "sub struct with int",
|
||||
field: func() reflect.StructField {
|
||||
type s2 struct {
|
||||
Vout int
|
||||
}
|
||||
type s struct {
|
||||
Test s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `{"vout":n}`,
|
||||
},
|
||||
{
|
||||
name: "sub struct with float",
|
||||
field: func() reflect.StructField {
|
||||
type s2 struct {
|
||||
Amount float64
|
||||
}
|
||||
type s struct {
|
||||
Test s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `{"amount":n.nnn}`,
|
||||
},
|
||||
{
|
||||
name: "sub struct with sub struct",
|
||||
field: func() reflect.StructField {
|
||||
type s3 struct {
|
||||
Amount float64
|
||||
}
|
||||
type s2 struct {
|
||||
Template s3
|
||||
}
|
||||
type s struct {
|
||||
Test s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `{"template":{"amount":n.nnn}}`,
|
||||
},
|
||||
{
|
||||
name: "sub struct with slice",
|
||||
field: func() reflect.StructField {
|
||||
type s2 struct {
|
||||
Capabilities []string
|
||||
}
|
||||
type s struct {
|
||||
Test s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `{"capabilities":["capability",...]}`,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Ensure usage matches the expected value.
|
||||
usage := btcjson.TstFieldUsage(test.field, test.defValue)
|
||||
if usage != test.expected {
|
||||
t.Errorf("Test #%d (%s) mismatched usage - got %v, "+
|
||||
"want %v", i, test.name, usage, test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+550
@@ -0,0 +1,550 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// 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"
|
||||
"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 := "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
|
||||
}
|
||||
+519
@@ -0,0 +1,519 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestAssignField tests the assignField function handles supported combinations
|
||||
// properly.
|
||||
func TestAssignField(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dest interface{}
|
||||
src interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
name: "same types",
|
||||
dest: int8(0),
|
||||
src: int8(100),
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "same types - more source pointers",
|
||||
dest: int8(0),
|
||||
src: func() interface{} {
|
||||
i := int8(100)
|
||||
return &i
|
||||
}(),
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "same types - more dest pointers",
|
||||
dest: func() interface{} {
|
||||
i := int8(0)
|
||||
return &i
|
||||
}(),
|
||||
src: int8(100),
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - more source pointers",
|
||||
dest: int16(0),
|
||||
src: func() interface{} {
|
||||
i := int8(100)
|
||||
return &i
|
||||
}(),
|
||||
expected: int16(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - both pointers",
|
||||
dest: func() interface{} {
|
||||
i := int8(0)
|
||||
return &i
|
||||
}(),
|
||||
src: func() interface{} {
|
||||
i := int16(100)
|
||||
return &i
|
||||
}(),
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - int16 -> int8",
|
||||
dest: int8(0),
|
||||
src: int16(100),
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - int16 -> uint8",
|
||||
dest: uint8(0),
|
||||
src: int16(100),
|
||||
expected: uint8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - uint16 -> int8",
|
||||
dest: int8(0),
|
||||
src: uint16(100),
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - uint16 -> uint8",
|
||||
dest: uint8(0),
|
||||
src: uint16(100),
|
||||
expected: uint8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - float32 -> float64",
|
||||
dest: float64(0),
|
||||
src: float32(1.5),
|
||||
expected: float64(1.5),
|
||||
},
|
||||
{
|
||||
name: "convertible types - float64 -> float32",
|
||||
dest: float32(0),
|
||||
src: float64(1.5),
|
||||
expected: float32(1.5),
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> bool",
|
||||
dest: false,
|
||||
src: "true",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> int8",
|
||||
dest: int8(0),
|
||||
src: "100",
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> uint8",
|
||||
dest: uint8(0),
|
||||
src: "100",
|
||||
expected: uint8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> float32",
|
||||
dest: float32(0),
|
||||
src: "1.5",
|
||||
expected: float32(1.5),
|
||||
},
|
||||
{
|
||||
name: "convertible types - typecase string -> string",
|
||||
dest: "",
|
||||
src: func() interface{} {
|
||||
type foo string
|
||||
return foo("foo")
|
||||
}(),
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> array",
|
||||
dest: [2]string{},
|
||||
src: `["test","test2"]`,
|
||||
expected: [2]string{"test", "test2"},
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> slice",
|
||||
dest: []string{},
|
||||
src: `["test","test2"]`,
|
||||
expected: []string{"test", "test2"},
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> struct",
|
||||
dest: struct{ A int }{},
|
||||
src: `{"A":100}`,
|
||||
expected: struct{ A int }{100},
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> map",
|
||||
dest: map[string]float64{},
|
||||
src: `{"1Address":1.5}`,
|
||||
expected: map[string]float64{"1Address": 1.5},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
dst := reflect.New(reflect.TypeOf(test.dest)).Elem()
|
||||
src := reflect.ValueOf(test.src)
|
||||
err := btcjson.TstAssignField(1, "testField", dst, src)
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Inidirect through to the base types to ensure their values
|
||||
// are the same.
|
||||
for dst.Kind() == reflect.Ptr {
|
||||
dst = dst.Elem()
|
||||
}
|
||||
if !reflect.DeepEqual(dst.Interface(), test.expected) {
|
||||
t.Errorf("Test #%d (%s) unexpected value - got %v, "+
|
||||
"want %v", i, test.name, dst.Interface(),
|
||||
test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAssignFieldErrors tests the assignField function error paths.
|
||||
func TestAssignFieldErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dest interface{}
|
||||
src interface{}
|
||||
err btcjson.Error
|
||||
}{
|
||||
{
|
||||
name: "general incompatible int -> string",
|
||||
dest: string(0),
|
||||
src: int(0),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow source int -> dest int",
|
||||
dest: int8(0),
|
||||
src: int(128),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow source int -> dest uint",
|
||||
dest: uint8(0),
|
||||
src: int(256),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "int -> float",
|
||||
dest: float32(0),
|
||||
src: int(256),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow source uint64 -> dest int64",
|
||||
dest: int64(0),
|
||||
src: uint64(1 << 63),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow source uint -> dest int",
|
||||
dest: int8(0),
|
||||
src: uint(128),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow source uint -> dest uint",
|
||||
dest: uint8(0),
|
||||
src: uint(256),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "uint -> float",
|
||||
dest: float32(0),
|
||||
src: uint(256),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "float -> int",
|
||||
dest: int(0),
|
||||
src: float32(1.0),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow float64 -> float32",
|
||||
dest: float32(0),
|
||||
src: float64(math.MaxFloat64),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> bool",
|
||||
dest: true,
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> int",
|
||||
dest: int8(0),
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow string -> int",
|
||||
dest: int8(0),
|
||||
src: "128",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> uint",
|
||||
dest: uint8(0),
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow string -> uint",
|
||||
dest: uint8(0),
|
||||
src: "256",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> float",
|
||||
dest: float32(0),
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow string -> float",
|
||||
dest: float32(0),
|
||||
src: "1.7976931348623157e+308",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> array",
|
||||
dest: [3]int{},
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> slice",
|
||||
dest: []int{},
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> struct",
|
||||
dest: struct{ A int }{},
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> map",
|
||||
dest: map[string]int{},
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
dst := reflect.New(reflect.TypeOf(test.dest)).Elem()
|
||||
src := reflect.ValueOf(test.src)
|
||||
err := btcjson.TstAssignField(1, "testField", dst, src)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||
err, test.err.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewCmdErrors ensures the error paths of NewCmd behave as expected.
|
||||
func TestNewCmdErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
args []interface{}
|
||||
err btcjson.Error
|
||||
}{
|
||||
{
|
||||
name: "unregistered command",
|
||||
method: "boguscommand",
|
||||
args: []interface{}{},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "too few parameters to command with required + optional",
|
||||
method: "getblock",
|
||||
args: []interface{}{},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrNumParams},
|
||||
},
|
||||
{
|
||||
name: "too many parameters to command with no optional",
|
||||
method: "getblockcount",
|
||||
args: []interface{}{"123"},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrNumParams},
|
||||
},
|
||||
{
|
||||
name: "incorrect parameter type",
|
||||
method: "getblock",
|
||||
args: []interface{}{1},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
_, err := btcjson.NewCmd(test.method, test.args...)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%[2]v), "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||
err, test.err.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMarshalCmdErrors tests the error paths of the MarshalCmd function.
|
||||
func TestMarshalCmdErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
id interface{}
|
||||
cmd interface{}
|
||||
err btcjson.Error
|
||||
}{
|
||||
{
|
||||
name: "unregistered type",
|
||||
id: 1,
|
||||
cmd: (*int)(nil),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "nil instance of registered type",
|
||||
id: 1,
|
||||
cmd: (*btcjson.GetBlockCmd)(nil),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "nil instance of registered type",
|
||||
id: []int{0, 1},
|
||||
cmd: &btcjson.GetBlockCountCmd{},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
_, err := btcjson.MarshalCmd(test.id, test.cmd)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%[2]v), "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||
err, test.err.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnmarshalCmdErrors tests the error paths of the UnmarshalCmd function.
|
||||
func TestUnmarshalCmdErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
request btcjson.Request
|
||||
err btcjson.Error
|
||||
}{
|
||||
{
|
||||
name: "unregistered type",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: "1.0",
|
||||
Method: "bogusmethod",
|
||||
Params: nil,
|
||||
ID: nil,
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "incorrect number of params",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: "1.0",
|
||||
Method: "getblockcount",
|
||||
Params: []json.RawMessage{[]byte(`"bogusparam"`)},
|
||||
ID: nil,
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrNumParams},
|
||||
},
|
||||
{
|
||||
name: "invalid type for a parameter",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: "1.0",
|
||||
Method: "getblock",
|
||||
Params: []json.RawMessage{[]byte("1")},
|
||||
ID: nil,
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid JSON for a parameter",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: "1.0",
|
||||
Method: "getblock",
|
||||
Params: []json.RawMessage{[]byte(`"1`)},
|
||||
ID: nil,
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
_, err := btcjson.UnmarshalCmd(&test.request)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%[2]v), "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||
err, test.err.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
Package btcjson provides primitives for working with the bitcoin JSON-RPC API.
|
||||
|
||||
Overview
|
||||
|
||||
When communicating via the JSON-RPC protocol, all of the commands need to be
|
||||
marshalled to and from the the wire in the appropriate format. This package
|
||||
provides data structures and primitives to ease this process.
|
||||
|
||||
In addition, it also provides some additional features such as custom command
|
||||
registration, command categorization, and reflection-based help generation.
|
||||
|
||||
JSON-RPC Protocol Overview
|
||||
|
||||
This information is not necessary in order to use this package, but it does
|
||||
provide some intuition into what the marshalling and unmarshalling that is
|
||||
discussed below is doing under the hood.
|
||||
|
||||
As defined by the JSON-RPC spec, there are effectively two forms of messages on
|
||||
the wire:
|
||||
|
||||
- Request Objects
|
||||
{"jsonrpc":"1.0","id":"SOMEID","method":"SOMEMETHOD","params":[SOMEPARAMS]}
|
||||
NOTE: Notifications are the same format except the id field is null.
|
||||
|
||||
- Response Objects
|
||||
{"result":SOMETHING,"error":null,"id":"SOMEID"}
|
||||
{"result":null,"error":{"code":SOMEINT,"message":SOMESTRING},"id":"SOMEID"}
|
||||
|
||||
For requests, the params field can vary in what it contains depending on the
|
||||
method (a.k.a. command) being sent. Each parameter can be as simple as an int
|
||||
or a complex structure containing many nested fields. The id field is used to
|
||||
identify a request and will be included in the associated response.
|
||||
|
||||
When working with asynchronous transports, such as websockets, spontaneous
|
||||
notifications are also possible. As indicated, they are the same as a request
|
||||
object, except they have the id field set to null. Therefore, servers will
|
||||
ignore requests with the id field set to null, while clients can choose to
|
||||
consume or ignore them.
|
||||
|
||||
Unfortunately, the original Bitcoin JSON-RPC API (and hence anything compatible
|
||||
with it) doesn't always follow the spec and will sometimes return an error
|
||||
string in the result field with a null error for certain commands. However,
|
||||
for the most part, the error field will be set as described on failure.
|
||||
|
||||
Marshalling and Unmarshalling
|
||||
|
||||
Based upon the discussion above, it should be easy to see how the types of this
|
||||
package map into the required parts of the protocol
|
||||
|
||||
- Request Objects (type Request)
|
||||
- Commands (type <Foo>Cmd)
|
||||
- Notifications (type <Foo>Ntfn)
|
||||
- Response Objects (type Response)
|
||||
- Result (type <Foo>Result)
|
||||
|
||||
To simplify the marshalling of the requests and responses, the MarshalCmd and
|
||||
MarshalResponse functions are provided. They return the raw bytes ready to be
|
||||
sent across the wire.
|
||||
|
||||
Unmarshalling a received Request object is a two step process:
|
||||
1) Unmarshal the raw bytes into a Request struct instance via json.Unmarshal
|
||||
2) Use UnmarshalCmd on the Result field of the unmarshalled Request to create
|
||||
a concrete command or notification instance with all struct fields set
|
||||
accordingly
|
||||
|
||||
This approach is used since it provides the caller with access to the additional
|
||||
fields in the request that are not part of the command such as the ID.
|
||||
|
||||
Unmarshalling a received Response object is also a two step process:
|
||||
1) Unmarhsal the raw bytes into a Response struct instance via json.Unmarshal
|
||||
2) Depending on the ID, unmarshal the Result field of the unmarshalled
|
||||
Response to create a concrete type instance
|
||||
|
||||
As above, this approach is used since it provides the caller with access to the
|
||||
fields in the response such as the ID and Error.
|
||||
|
||||
Command Creation
|
||||
|
||||
This package provides two approaches for creating a new command. This first,
|
||||
and preferred, method is to use one of the New<Foo>Cmd functions. This allows
|
||||
static compile-time checking to help ensure the parameters stay in sync with
|
||||
the struct definitions.
|
||||
|
||||
The second approach is the NewCmd function which takes a method (command) name
|
||||
and variable arguments. The function includes full checking to ensure the
|
||||
parameters are accurate according to provided method, however these checks are,
|
||||
obviously, run-time which means any mistakes won't be found until the code is
|
||||
actually executed. However, it is quite useful for user-supplied commands
|
||||
that are intentionally dynamic.
|
||||
|
||||
Custom Command Registration
|
||||
|
||||
The command handling of this package is built around the concept of registered
|
||||
commands. This is true for the wide variety of commands already provided by the
|
||||
package, but it also means caller can easily provide custom commands with all
|
||||
of the same functionality as the built-in commands. Use the RegisterCmd
|
||||
function for this purpose.
|
||||
|
||||
A list of all registered methods can be obtained with the RegisteredCmdMethods
|
||||
function.
|
||||
|
||||
Command Inspection
|
||||
|
||||
All registered commands are registered with flags that identify information such
|
||||
as whether the command applies to a chain server, wallet server, or is a
|
||||
notification along with the method name to use. These flags can be obtained
|
||||
with the MethodUsageFlags flags, and the method can be obtained with the
|
||||
CmdMethod function.
|
||||
|
||||
Help Generation
|
||||
|
||||
To facilitate providing consistent help to users of the RPC server, this package
|
||||
exposes the GenerateHelp and function which uses reflection on registered
|
||||
commands or notifications, as well as the provided expected result types, to
|
||||
generate the final help text.
|
||||
|
||||
In addition, the MethodUsageText function is provided to generate consistent
|
||||
one-line usage for registered commands and notifications using reflection.
|
||||
|
||||
Errors
|
||||
|
||||
There are 2 distinct type of errors supported by this package:
|
||||
|
||||
- General errors related to marshalling or unmarshalling or improper use of
|
||||
the package (type Error)
|
||||
- RPC errors which are intended to be returned across the wire as a part of
|
||||
the JSON-RPC response (type RPCError)
|
||||
|
||||
The first category of errors (type Error) typically indicates a programmer error
|
||||
and can be avoided by properly using the API. Errors of this type will be
|
||||
returned from the various functions available in this package. They identify
|
||||
issues such as unsupported field types, attempts to register malformed commands,
|
||||
and attempting to create a new command with an improper number of parameters.
|
||||
The specific reason for the error can be detected by type asserting it to a
|
||||
*btcjson.Error and accessing the ErrorCode field.
|
||||
|
||||
The second category of errors (type RPCError), on the other hand, are useful for
|
||||
returning errors to RPC clients. Consequently, they are used in the previously
|
||||
described Response type.
|
||||
*/
|
||||
package btcjson
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// ErrorCode identifies a kind of error. These error codes are NOT used for
|
||||
// JSON-RPC response errors.
|
||||
type ErrorCode int
|
||||
|
||||
// These constants are used to identify a specific RuleError.
|
||||
const (
|
||||
// ErrDuplicateMethod indicates a command with the specified method
|
||||
// already exists.
|
||||
ErrDuplicateMethod ErrorCode = iota
|
||||
|
||||
// ErrInvalidUsageFlags indicates one or more unrecognized flag bits
|
||||
// were specified.
|
||||
ErrInvalidUsageFlags
|
||||
|
||||
// ErrInvalidType indicates a type was passed that is not the required
|
||||
// type.
|
||||
ErrInvalidType
|
||||
|
||||
// ErrEmbeddedType indicates the provided command struct contains an
|
||||
// embedded type which is not not supported.
|
||||
ErrEmbeddedType
|
||||
|
||||
// ErrUnexportedField indiciates the provided command struct contains an
|
||||
// unexported field which is not supported.
|
||||
ErrUnexportedField
|
||||
|
||||
// ErrUnsupportedFieldType indicates the type of a field in the provided
|
||||
// command struct is not one of the supported types.
|
||||
ErrUnsupportedFieldType
|
||||
|
||||
// ErrNonOptionalField indicates a non-optional field was specified
|
||||
// after an optional field.
|
||||
ErrNonOptionalField
|
||||
|
||||
// ErrNonOptionalDefault indicates a 'jsonrpcdefault' struct tag was
|
||||
// specified for a non-optional field.
|
||||
ErrNonOptionalDefault
|
||||
|
||||
// ErrMismatchedDefault indicates a 'jsonrpcdefault' struct tag contains
|
||||
// a value that doesn't match the type of the field.
|
||||
ErrMismatchedDefault
|
||||
|
||||
// ErrUnregisteredMethod indicates a method was specified that has not
|
||||
// been registered.
|
||||
ErrUnregisteredMethod
|
||||
|
||||
// ErrMissingDescription indicates a description required to generate
|
||||
// help is missing.
|
||||
ErrMissingDescription
|
||||
|
||||
// ErrNumParams inidcates the number of params supplied do not
|
||||
// match the requirements of the associated command.
|
||||
ErrNumParams
|
||||
|
||||
// numErrorCodes is the maximum error code number used in tests.
|
||||
numErrorCodes
|
||||
)
|
||||
|
||||
// Map of ErrorCode values back to their constant names for pretty printing.
|
||||
var errorCodeStrings = map[ErrorCode]string{
|
||||
ErrDuplicateMethod: "ErrDuplicateMethod",
|
||||
ErrInvalidUsageFlags: "ErrInvalidUsageFlags",
|
||||
ErrInvalidType: "ErrInvalidType",
|
||||
ErrEmbeddedType: "ErrEmbeddedType",
|
||||
ErrUnexportedField: "ErrUnexportedField",
|
||||
ErrUnsupportedFieldType: "ErrUnsupportedFieldType",
|
||||
ErrNonOptionalField: "ErrNonOptionalField",
|
||||
ErrNonOptionalDefault: "ErrNonOptionalDefault",
|
||||
ErrMismatchedDefault: "ErrMismatchedDefault",
|
||||
ErrUnregisteredMethod: "ErrUnregisteredMethod",
|
||||
ErrMissingDescription: "ErrMissingDescription",
|
||||
ErrNumParams: "ErrNumParams",
|
||||
}
|
||||
|
||||
// String returns the ErrorCode as a human-readable name.
|
||||
func (e ErrorCode) String() string {
|
||||
if s := errorCodeStrings[e]; s != "" {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("Unknown ErrorCode (%d)", int(e))
|
||||
}
|
||||
|
||||
// Error identifies a general error. This differs from an RPCError in that this
|
||||
// error typically is used more by the consumers of the package as opposed to
|
||||
// RPCErrors which are intended to be returned to the client across the wire via
|
||||
// a JSON-RPC Response. The caller can use type assertions to determine the
|
||||
// specific error and access the ErrorCode field.
|
||||
type Error struct {
|
||||
ErrorCode ErrorCode // Describes the kind of error
|
||||
Description string // Human readable description of the issue
|
||||
}
|
||||
|
||||
// Error satisfies the error interface and prints human-readable errors.
|
||||
func (e Error) Error() string {
|
||||
return e.Description
|
||||
}
|
||||
|
||||
// makeError creates an Error given a set of arguments.
|
||||
func makeError(c ErrorCode, desc string) Error {
|
||||
return Error{ErrorCode: c, Description: desc}
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestErrorCodeStringer tests the stringized output for the ErrorCode type.
|
||||
func TestErrorCodeStringer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in btcjson.ErrorCode
|
||||
want string
|
||||
}{
|
||||
{btcjson.ErrDuplicateMethod, "ErrDuplicateMethod"},
|
||||
{btcjson.ErrInvalidUsageFlags, "ErrInvalidUsageFlags"},
|
||||
{btcjson.ErrInvalidType, "ErrInvalidType"},
|
||||
{btcjson.ErrEmbeddedType, "ErrEmbeddedType"},
|
||||
{btcjson.ErrUnexportedField, "ErrUnexportedField"},
|
||||
{btcjson.ErrUnsupportedFieldType, "ErrUnsupportedFieldType"},
|
||||
{btcjson.ErrNonOptionalField, "ErrNonOptionalField"},
|
||||
{btcjson.ErrNonOptionalDefault, "ErrNonOptionalDefault"},
|
||||
{btcjson.ErrMismatchedDefault, "ErrMismatchedDefault"},
|
||||
{btcjson.ErrUnregisteredMethod, "ErrUnregisteredMethod"},
|
||||
{btcjson.ErrNumParams, "ErrNumParams"},
|
||||
{btcjson.ErrMissingDescription, "ErrMissingDescription"},
|
||||
{0xffff, "Unknown ErrorCode (65535)"},
|
||||
}
|
||||
|
||||
// Detect additional error codes that don't have the stringer added.
|
||||
if len(tests)-1 != int(btcjson.TstNumErrorCodes) {
|
||||
t.Errorf("It appears an error code was added without adding an " +
|
||||
"associated stringer test")
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
result := test.in.String()
|
||||
if result != test.want {
|
||||
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||
test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestError tests the error output for the Error type.
|
||||
func TestError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in btcjson.Error
|
||||
want string
|
||||
}{
|
||||
{
|
||||
btcjson.Error{Description: "some error"},
|
||||
"some error",
|
||||
},
|
||||
{
|
||||
btcjson.Error{Description: "human-readable error"},
|
||||
"human-readable error",
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
result := test.in.Error()
|
||||
if result != test.want {
|
||||
t.Errorf("Error #%d\n got: %s want: %s", i, result,
|
||||
test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// This example demonstrates how to create and marshal a command into a JSON-RPC
|
||||
// request.
|
||||
func ExampleMarshalCmd() {
|
||||
// Create a new getblock command. Notice the nil parameter indicates
|
||||
// to use the default parameter for that fields. This is a common
|
||||
// pattern used in all of the New<Foo>Cmd functions in this package for
|
||||
// optional fields. Also, notice the call to btcjson.Bool which is a
|
||||
// convenience function for creating a pointer out of a primitive for
|
||||
// optional parameters.
|
||||
blockHash := "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
|
||||
gbCmd := btcjson.NewGetBlockCmd(blockHash, btcjson.Bool(false), nil)
|
||||
|
||||
// Marshal the command to the format suitable for sending to the RPC
|
||||
// server. Typically the client would increment the id here which is
|
||||
// request so the response can be identified.
|
||||
id := 1
|
||||
marshalledBytes, err := btcjson.MarshalCmd(id, gbCmd)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Display the marshalled command. Ordinarily this would be sent across
|
||||
// the wire to the RPC server, but for this example, just display it.
|
||||
fmt.Printf("%s\n", marshalledBytes)
|
||||
|
||||
// Output:
|
||||
// {"jsonrpc":"1.0","method":"getblock","params":["000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",false],"id":1}
|
||||
}
|
||||
|
||||
// This example demonstrates how to unmarshal a JSON-RPC request and then
|
||||
// unmarshal the concrete request into a concrete command.
|
||||
func ExampleUnmarshalCmd() {
|
||||
// Ordinarily this would be read from the wire, but for this example,
|
||||
// it is hard coded here for clarity.
|
||||
data := []byte(`{"jsonrpc":"1.0","method":"getblock","params":["000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",false],"id":1}`)
|
||||
|
||||
// Unmarshal the raw bytes from the wire into a JSON-RPC request.
|
||||
var request btcjson.Request
|
||||
if err := json.Unmarshal(data, &request); err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Typically there isn't any need to examine the request fields directly
|
||||
// like this as the caller already knows what response to expect based
|
||||
// on the command it sent. However, this is done here to demonstrate
|
||||
// why the unmarshal process is two steps.
|
||||
if request.ID == nil {
|
||||
fmt.Println("Unexpected notification")
|
||||
return
|
||||
}
|
||||
if request.Method != "getblock" {
|
||||
fmt.Println("Unexpected method")
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal the request into a concrete command.
|
||||
cmd, err := btcjson.UnmarshalCmd(&request)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Type assert the command to the appropriate type.
|
||||
gbCmd, ok := cmd.(*btcjson.GetBlockCmd)
|
||||
if !ok {
|
||||
fmt.Printf("Incorrect command type: %T\n", cmd)
|
||||
return
|
||||
}
|
||||
|
||||
// Display the fields in the concrete command.
|
||||
fmt.Println("Hash:", gbCmd.Hash)
|
||||
fmt.Println("Verbose:", *gbCmd.Verbose)
|
||||
fmt.Println("VerboseTx:", *gbCmd.VerboseTx)
|
||||
|
||||
// Output:
|
||||
// Hash: 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
|
||||
// Verbose: false
|
||||
// VerboseTx: false
|
||||
}
|
||||
|
||||
// This example demonstrates how to marshal a JSON-RPC response.
|
||||
func ExampleMarshalResponse() {
|
||||
// Marshal a new JSON-RPC response. For example, this is a response
|
||||
// to a getblockheight request.
|
||||
marshalledBytes, err := btcjson.MarshalResponse(1, 350001, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Display the marshalled response. Ordinarily this would be sent
|
||||
// across the wire to the RPC client, but for this example, just display
|
||||
// it.
|
||||
fmt.Printf("%s\n", marshalledBytes)
|
||||
|
||||
// Output:
|
||||
// {"result":350001,"error":null,"id":1}
|
||||
}
|
||||
|
||||
// This example demonstrates how to unmarshal a JSON-RPC response and then
|
||||
// unmarshal the result field in the response to a concrete type.
|
||||
func Example_unmarshalResponse() {
|
||||
// Ordinarily this would be read from the wire, but for this example,
|
||||
// it is hard coded here for clarity. This is an example response to a
|
||||
// getblockheight request.
|
||||
data := []byte(`{"result":350001,"error":null,"id":1}`)
|
||||
|
||||
// Unmarshal the raw bytes from the wire into a JSON-RPC response.
|
||||
var response btcjson.Response
|
||||
if err := json.Unmarshal(data, &response); err != nil {
|
||||
fmt.Println("Malformed JSON-RPC response:", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Check the response for an error from the server. For example, the
|
||||
// server might return an error if an invalid/unknown block hash is
|
||||
// requested.
|
||||
if response.Error != nil {
|
||||
fmt.Println(response.Error)
|
||||
return
|
||||
}
|
||||
|
||||
// Unmarshal the result into the expected type for the response.
|
||||
var blockHeight int32
|
||||
if err := json.Unmarshal(response.Result, &blockHeight); err != nil {
|
||||
fmt.Printf("Unexpected result type: %T\n", response.Result)
|
||||
return
|
||||
}
|
||||
fmt.Println("Block height:", blockHeight)
|
||||
|
||||
// Output:
|
||||
// Block height: 350001
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
// TstHighestUsageFlagBit makes the internal highestUsageFlagBit parameter
|
||||
// available to the test package.
|
||||
var TstHighestUsageFlagBit = highestUsageFlagBit
|
||||
|
||||
// TstNumErrorCodes makes the internal numErrorCodes parameter available to the
|
||||
// test package.
|
||||
var TstNumErrorCodes = numErrorCodes
|
||||
|
||||
// TstAssignField makes the internal assignField function available to the test
|
||||
// package.
|
||||
var TstAssignField = assignField
|
||||
|
||||
// TstFieldUsage makes the internal fieldUsage function available to the test
|
||||
// package.
|
||||
var TstFieldUsage = fieldUsage
|
||||
|
||||
// TstReflectTypeToJSONType makes the internal reflectTypeToJSONType function
|
||||
// available to the test package.
|
||||
var TstReflectTypeToJSONType = reflectTypeToJSONType
|
||||
|
||||
// TstResultStructHelp makes the internal resultStructHelp function available to
|
||||
// the test package.
|
||||
var TstResultStructHelp = resultStructHelp
|
||||
|
||||
// TstReflectTypeToJSONExample makes the internal reflectTypeToJSONExample
|
||||
// function available to the test package.
|
||||
var TstReflectTypeToJSONExample = reflectTypeToJSONExample
|
||||
|
||||
// TstResultTypeHelp makes the internal resultTypeHelp function available to the
|
||||
// test package.
|
||||
var TstResultTypeHelp = resultTypeHelp
|
||||
|
||||
// TstArgHelp makes the internal argHelp function available to the test package.
|
||||
var TstArgHelp = argHelp
|
||||
|
||||
// TestMethodHelp makes the internal methodHelp function available to the test
|
||||
// package.
|
||||
var TestMethodHelp = methodHelp
|
||||
|
||||
// TstIsValidResultType makes the internal isValidResultType function available
|
||||
// to the test package.
|
||||
var TstIsValidResultType = isValidResultType
|
||||
+560
@@ -0,0 +1,560 @@
|
||||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
)
|
||||
|
||||
// baseHelpDescs house the various help labels, types, and example values used
|
||||
// when generating help. The per-command synopsis, field descriptions,
|
||||
// conditions, and result descriptions are to be provided by the caller.
|
||||
var baseHelpDescs = map[string]string{
|
||||
// Misc help labels and output.
|
||||
"help-arguments": "Arguments",
|
||||
"help-arguments-none": "None",
|
||||
"help-result": "Result",
|
||||
"help-result-nothing": "Nothing",
|
||||
"help-default": "default",
|
||||
"help-optional": "optional",
|
||||
"help-required": "required",
|
||||
|
||||
// JSON types.
|
||||
"json-type-numeric": "numeric",
|
||||
"json-type-string": "string",
|
||||
"json-type-bool": "boolean",
|
||||
"json-type-array": "array of ",
|
||||
"json-type-object": "object",
|
||||
"json-type-value": "value",
|
||||
|
||||
// JSON examples.
|
||||
"json-example-string": "value",
|
||||
"json-example-bool": "true|false",
|
||||
"json-example-map-data": "data",
|
||||
"json-example-unknown": "unknown",
|
||||
}
|
||||
|
||||
// descLookupFunc is a function which is used to lookup a description given
|
||||
// a key.
|
||||
type descLookupFunc func(string) string
|
||||
|
||||
// reflectTypeToJSONType returns a string that represents the JSON type
|
||||
// associated with the provided Go type.
|
||||
func reflectTypeToJSONType(xT descLookupFunc, rt reflect.Type) string {
|
||||
kind := rt.Kind()
|
||||
if isNumeric(kind) {
|
||||
return xT("json-type-numeric")
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.String:
|
||||
return xT("json-type-string")
|
||||
|
||||
case reflect.Bool:
|
||||
return xT("json-type-bool")
|
||||
|
||||
case reflect.Array, reflect.Slice:
|
||||
return xT("json-type-array") + reflectTypeToJSONType(xT,
|
||||
rt.Elem())
|
||||
|
||||
case reflect.Struct:
|
||||
return xT("json-type-object")
|
||||
|
||||
case reflect.Map:
|
||||
return xT("json-type-object")
|
||||
}
|
||||
|
||||
return xT("json-type-value")
|
||||
}
|
||||
|
||||
// resultStructHelp returns a slice of strings containing the result help output
|
||||
// for a struct. Each line makes use of tabs to separate the relevant pieces so
|
||||
// a tabwriter can be used later to line everything up. The descriptions are
|
||||
// pulled from the active help descriptions map based on the lowercase version
|
||||
// of the provided reflect type and json name (or the lowercase version of the
|
||||
// field name if no json tag was specified).
|
||||
func resultStructHelp(xT descLookupFunc, rt reflect.Type, indentLevel int) []string {
|
||||
indent := strings.Repeat(" ", indentLevel)
|
||||
typeName := strings.ToLower(rt.Name())
|
||||
|
||||
// Generate the help for each of the fields in the result struct.
|
||||
numField := rt.NumField()
|
||||
results := make([]string, 0, numField)
|
||||
for i := 0; i < numField; i++ {
|
||||
rtf := rt.Field(i)
|
||||
|
||||
// The field name to display is the json name when it's
|
||||
// available, otherwise use the lowercase field name.
|
||||
var fieldName string
|
||||
if tag := rtf.Tag.Get("json"); tag != "" {
|
||||
fieldName = strings.Split(tag, ",")[0]
|
||||
} else {
|
||||
fieldName = strings.ToLower(rtf.Name)
|
||||
}
|
||||
|
||||
// Deference pointer if needed.
|
||||
rtfType := rtf.Type
|
||||
if rtfType.Kind() == reflect.Ptr {
|
||||
rtfType = rtf.Type.Elem()
|
||||
}
|
||||
|
||||
// Generate the JSON example for the result type of this struct
|
||||
// field. When it is a complex type, examine the type and
|
||||
// adjust the opening bracket and brace combination accordingly.
|
||||
fieldType := reflectTypeToJSONType(xT, rtfType)
|
||||
fieldDescKey := typeName + "-" + fieldName
|
||||
fieldExamples, isComplex := reflectTypeToJSONExample(xT,
|
||||
rtfType, indentLevel, fieldDescKey)
|
||||
if isComplex {
|
||||
var brace string
|
||||
kind := rtfType.Kind()
|
||||
if kind == reflect.Array || kind == reflect.Slice {
|
||||
brace = "[{"
|
||||
} else {
|
||||
brace = "{"
|
||||
}
|
||||
result := fmt.Sprintf("%s\"%s\": %s\t(%s)\t%s", indent,
|
||||
fieldName, brace, fieldType, xT(fieldDescKey))
|
||||
results = append(results, result)
|
||||
results = append(results, fieldExamples...)
|
||||
} else {
|
||||
result := fmt.Sprintf("%s\"%s\": %s,\t(%s)\t%s", indent,
|
||||
fieldName, fieldExamples[0], fieldType,
|
||||
xT(fieldDescKey))
|
||||
results = append(results, result)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// reflectTypeToJSONExample generates example usage in the format used by the
|
||||
// help output. It handles arrays, slices and structs recursively. The output
|
||||
// is returned as a slice of lines so the final help can be nicely aligned via
|
||||
// a tab writer. A bool is also returned which specifies whether or not the
|
||||
// type results in a complex JSON object since they need to be handled
|
||||
// differently.
|
||||
func reflectTypeToJSONExample(xT descLookupFunc, rt reflect.Type, indentLevel int, fieldDescKey string) ([]string, bool) {
|
||||
// Indirect pointer if needed.
|
||||
if rt.Kind() == reflect.Ptr {
|
||||
rt = rt.Elem()
|
||||
}
|
||||
kind := rt.Kind()
|
||||
if isNumeric(kind) {
|
||||
if kind == reflect.Float32 || kind == reflect.Float64 {
|
||||
return []string{"n.nnn"}, false
|
||||
}
|
||||
|
||||
return []string{"n"}, false
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.String:
|
||||
return []string{`"` + xT("json-example-string") + `"`}, false
|
||||
|
||||
case reflect.Bool:
|
||||
return []string{xT("json-example-bool")}, false
|
||||
|
||||
case reflect.Struct:
|
||||
indent := strings.Repeat(" ", indentLevel)
|
||||
results := resultStructHelp(xT, rt, indentLevel+1)
|
||||
|
||||
// An opening brace is needed for the first indent level. For
|
||||
// all others, it will be included as a part of the previous
|
||||
// field.
|
||||
if indentLevel == 0 {
|
||||
newResults := make([]string, len(results)+1)
|
||||
newResults[0] = "{"
|
||||
copy(newResults[1:], results)
|
||||
results = newResults
|
||||
}
|
||||
|
||||
// The closing brace has a comma after it except for the first
|
||||
// indent level. The final tabs are necessary so the tab writer
|
||||
// lines things up properly.
|
||||
closingBrace := indent + "}"
|
||||
if indentLevel > 0 {
|
||||
closingBrace += ","
|
||||
}
|
||||
results = append(results, closingBrace+"\t\t")
|
||||
return results, true
|
||||
|
||||
case reflect.Array, reflect.Slice:
|
||||
results, isComplex := reflectTypeToJSONExample(xT, rt.Elem(),
|
||||
indentLevel, fieldDescKey)
|
||||
|
||||
// When the result is complex, it is because this is an array of
|
||||
// objects.
|
||||
if isComplex {
|
||||
// When this is at indent level zero, there is no
|
||||
// previous field to house the opening array bracket, so
|
||||
// replace the opening object brace with the array
|
||||
// syntax. Also, replace the final closing object brace
|
||||
// with the variadiac array closing syntax.
|
||||
indent := strings.Repeat(" ", indentLevel)
|
||||
if indentLevel == 0 {
|
||||
results[0] = indent + "[{"
|
||||
results[len(results)-1] = indent + "},...]"
|
||||
return results, true
|
||||
}
|
||||
|
||||
// At this point, the indent level is greater than 0, so
|
||||
// the opening array bracket and object brace are
|
||||
// already a part of the previous field. However, the
|
||||
// closing entry is a simple object brace, so replace it
|
||||
// with the variadiac array closing syntax. The final
|
||||
// tabs are necessary so the tab writer lines things up
|
||||
// properly.
|
||||
results[len(results)-1] = indent + "},...],\t\t"
|
||||
return results, true
|
||||
}
|
||||
|
||||
// It's an array of primitives, so return the formatted text
|
||||
// accordingly.
|
||||
return []string{fmt.Sprintf("[%s,...]", results[0])}, false
|
||||
|
||||
case reflect.Map:
|
||||
indent := strings.Repeat(" ", indentLevel)
|
||||
results := make([]string, 0, 3)
|
||||
|
||||
// An opening brace is needed for the first indent level. For
|
||||
// all others, it will be included as a part of the previous
|
||||
// field.
|
||||
if indentLevel == 0 {
|
||||
results = append(results, indent+"{")
|
||||
}
|
||||
|
||||
// Maps are a bit special in that they need to have the key,
|
||||
// value, and description of the object entry specifically
|
||||
// called out.
|
||||
innerIndent := strings.Repeat(" ", indentLevel+1)
|
||||
result := fmt.Sprintf("%s%q: %s, (%s) %s", innerIndent,
|
||||
xT(fieldDescKey+"--key"), xT(fieldDescKey+"--value"),
|
||||
reflectTypeToJSONType(xT, rt), xT(fieldDescKey+"--desc"))
|
||||
results = append(results, result)
|
||||
results = append(results, innerIndent+"...")
|
||||
|
||||
results = append(results, indent+"}")
|
||||
return results, true
|
||||
}
|
||||
|
||||
return []string{xT("json-example-unknown")}, false
|
||||
}
|
||||
|
||||
// resultTypeHelp generates and returns formatted help for the provided result
|
||||
// type.
|
||||
func resultTypeHelp(xT descLookupFunc, rt reflect.Type, fieldDescKey string) string {
|
||||
// Generate the JSON example for the result type.
|
||||
results, isComplex := reflectTypeToJSONExample(xT, rt, 0, fieldDescKey)
|
||||
|
||||
// When this is a primitive type, add the associated JSON type and
|
||||
// result description into the final string, format it accordingly,
|
||||
// and return it.
|
||||
if !isComplex {
|
||||
return fmt.Sprintf("%s (%s) %s", results[0],
|
||||
reflectTypeToJSONType(xT, rt), xT(fieldDescKey))
|
||||
}
|
||||
|
||||
// At this point, this is a complex type that already has the JSON types
|
||||
// and descriptions in the results. Thus, use a tab writer to nicely
|
||||
// align the help text.
|
||||
var formatted bytes.Buffer
|
||||
w := new(tabwriter.Writer)
|
||||
w.Init(&formatted, 0, 4, 1, ' ', 0)
|
||||
for i, text := range results {
|
||||
if i == len(results)-1 {
|
||||
fmt.Fprintf(w, text)
|
||||
} else {
|
||||
fmt.Fprintln(w, text)
|
||||
}
|
||||
}
|
||||
w.Flush()
|
||||
return formatted.String()
|
||||
}
|
||||
|
||||
// argTypeHelp returns the type of provided command argument as a string in the
|
||||
// format used by the help output. In particular, it includes the JSON type
|
||||
// (boolean, numeric, string, array, object) along with optional and the default
|
||||
// value if applicable.
|
||||
func argTypeHelp(xT descLookupFunc, structField reflect.StructField, defaultVal *reflect.Value) string {
|
||||
// Indirect the pointer if needed and track if it's an optional field.
|
||||
fieldType := structField.Type
|
||||
var isOptional bool
|
||||
if fieldType.Kind() == reflect.Ptr {
|
||||
fieldType = fieldType.Elem()
|
||||
isOptional = true
|
||||
}
|
||||
|
||||
// When there is a default value, it must also be a pointer due to the
|
||||
// rules enforced by RegisterCmd.
|
||||
if defaultVal != nil {
|
||||
indirect := defaultVal.Elem()
|
||||
defaultVal = &indirect
|
||||
}
|
||||
|
||||
// Convert the field type to a JSON type.
|
||||
details := make([]string, 0, 3)
|
||||
details = append(details, reflectTypeToJSONType(xT, fieldType))
|
||||
|
||||
// Add optional and default value to the details if needed.
|
||||
if isOptional {
|
||||
details = append(details, xT("help-optional"))
|
||||
|
||||
// Add the default value if there is one. This is only checked
|
||||
// when the field is optional since a non-optional field can't
|
||||
// have a default value.
|
||||
if defaultVal != nil {
|
||||
val := defaultVal.Interface()
|
||||
if defaultVal.Kind() == reflect.String {
|
||||
val = fmt.Sprintf(`"%s"`, val)
|
||||
}
|
||||
str := fmt.Sprintf("%s=%v", xT("help-default"), val)
|
||||
details = append(details, str)
|
||||
}
|
||||
} else {
|
||||
details = append(details, xT("help-required"))
|
||||
}
|
||||
|
||||
return strings.Join(details, ", ")
|
||||
}
|
||||
|
||||
// argHelp generates and returns formatted help for the provided command.
|
||||
func argHelp(xT descLookupFunc, rtp reflect.Type, defaults map[int]reflect.Value, method string) string {
|
||||
// Return now if the command has no arguments.
|
||||
rt := rtp.Elem()
|
||||
numFields := rt.NumField()
|
||||
if numFields == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Generate the help for each argument in the command. Several
|
||||
// simplifying assumptions are made here because the RegisterCmd
|
||||
// function has already rigorously enforced the layout.
|
||||
args := make([]string, 0, numFields)
|
||||
for i := 0; i < numFields; i++ {
|
||||
rtf := rt.Field(i)
|
||||
var defaultVal *reflect.Value
|
||||
if defVal, ok := defaults[i]; ok {
|
||||
defaultVal = &defVal
|
||||
}
|
||||
|
||||
fieldName := strings.ToLower(rtf.Name)
|
||||
helpText := fmt.Sprintf("%d.\t%s\t(%s)\t%s", i+1, fieldName,
|
||||
argTypeHelp(xT, rtf, defaultVal),
|
||||
xT(method+"-"+fieldName))
|
||||
args = append(args, helpText)
|
||||
|
||||
// For types which require a JSON object, or an array of JSON
|
||||
// objects, generate the full syntax for the argument.
|
||||
fieldType := rtf.Type
|
||||
if fieldType.Kind() == reflect.Ptr {
|
||||
fieldType = fieldType.Elem()
|
||||
}
|
||||
kind := fieldType.Kind()
|
||||
switch kind {
|
||||
case reflect.Struct:
|
||||
fieldDescKey := fmt.Sprintf("%s-%s", method, fieldName)
|
||||
resultText := resultTypeHelp(xT, fieldType, fieldDescKey)
|
||||
args = append(args, resultText)
|
||||
|
||||
case reflect.Map:
|
||||
fieldDescKey := fmt.Sprintf("%s-%s", method, fieldName)
|
||||
resultText := resultTypeHelp(xT, fieldType, fieldDescKey)
|
||||
args = append(args, resultText)
|
||||
|
||||
case reflect.Array, reflect.Slice:
|
||||
fieldDescKey := fmt.Sprintf("%s-%s", method, fieldName)
|
||||
if rtf.Type.Elem().Kind() == reflect.Struct {
|
||||
resultText := resultTypeHelp(xT, fieldType,
|
||||
fieldDescKey)
|
||||
args = append(args, resultText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add argument names, types, and descriptions if there are any. Use a
|
||||
// tab writer to nicely align the help text.
|
||||
var formatted bytes.Buffer
|
||||
w := new(tabwriter.Writer)
|
||||
w.Init(&formatted, 0, 4, 1, ' ', 0)
|
||||
for _, text := range args {
|
||||
fmt.Fprintln(w, text)
|
||||
}
|
||||
w.Flush()
|
||||
return formatted.String()
|
||||
}
|
||||
|
||||
// methodHelp generates and returns the help output for the provided command
|
||||
// and method info. This is the main work horse for the exported MethodHelp
|
||||
// function.
|
||||
func methodHelp(xT descLookupFunc, rtp reflect.Type, defaults map[int]reflect.Value, method string, resultTypes []interface{}) string {
|
||||
// Start off with the method usage and help synopsis.
|
||||
help := fmt.Sprintf("%s\n\n%s\n", methodUsageText(rtp, defaults, method),
|
||||
xT(method+"--synopsis"))
|
||||
|
||||
// Generate the help for each argument in the command.
|
||||
if argText := argHelp(xT, rtp, defaults, method); argText != "" {
|
||||
help += fmt.Sprintf("\n%s:\n%s", xT("help-arguments"),
|
||||
argText)
|
||||
} else {
|
||||
help += fmt.Sprintf("\n%s:\n%s\n", xT("help-arguments"),
|
||||
xT("help-arguments-none"))
|
||||
}
|
||||
|
||||
// Generate the help text for each result type.
|
||||
resultTexts := make([]string, 0, len(resultTypes))
|
||||
for i := range resultTypes {
|
||||
rtp := reflect.TypeOf(resultTypes[i])
|
||||
fieldDescKey := fmt.Sprintf("%s--result%d", method, i)
|
||||
if resultTypes[i] == nil {
|
||||
resultText := xT("help-result-nothing")
|
||||
resultTexts = append(resultTexts, resultText)
|
||||
continue
|
||||
}
|
||||
|
||||
resultText := resultTypeHelp(xT, rtp.Elem(), fieldDescKey)
|
||||
resultTexts = append(resultTexts, resultText)
|
||||
}
|
||||
|
||||
// Add result types and descriptions. When there is more than one
|
||||
// result type, also add the condition which triggers it.
|
||||
if len(resultTexts) > 1 {
|
||||
for i, resultText := range resultTexts {
|
||||
condKey := fmt.Sprintf("%s--condition%d", method, i)
|
||||
help += fmt.Sprintf("\n%s (%s):\n%s\n",
|
||||
xT("help-result"), xT(condKey), resultText)
|
||||
}
|
||||
} else if len(resultTexts) > 0 {
|
||||
help += fmt.Sprintf("\n%s:\n%s\n", xT("help-result"),
|
||||
resultTexts[0])
|
||||
} else {
|
||||
help += fmt.Sprintf("\n%s:\n%s\n", xT("help-result"),
|
||||
xT("help-result-nothing"))
|
||||
}
|
||||
return help
|
||||
}
|
||||
|
||||
// isValidResultType returns whether the passed reflect kind is one of the
|
||||
// acceptable types for results.
|
||||
func isValidResultType(kind reflect.Kind) bool {
|
||||
if isNumeric(kind) {
|
||||
return true
|
||||
}
|
||||
|
||||
switch kind {
|
||||
case reflect.String, reflect.Struct, reflect.Array, reflect.Slice,
|
||||
reflect.Bool, reflect.Map:
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// GenerateHelp generates and returns help output for the provided method and
|
||||
// result types given a map to provide the appropriate keys for the method
|
||||
// synopsis, field descriptions, conditions, and result descriptions. The
|
||||
// method must be associated with a registered type. All commands provided by
|
||||
// this package are registered by default.
|
||||
//
|
||||
// The resultTypes must be pointer-to-types which represent the specific types
|
||||
// of values the command returns. For example, if the command only returns a
|
||||
// boolean value, there should only be a single entry of (*bool)(nil). Note
|
||||
// that each type must be a single pointer to the type. Therefore, it is
|
||||
// recommended to simply pass a nil pointer cast to the appropriate type as
|
||||
// previously shown.
|
||||
//
|
||||
// The provided descriptions map must contain all of the keys or an error will
|
||||
// be returned which includes the missing key, or the final missing key when
|
||||
// there is more than one key missing. The generated help in the case of such
|
||||
// an error will use the key in place of the description.
|
||||
//
|
||||
// The following outlines the required keys:
|
||||
// "<method>--synopsis" Synopsis for the command
|
||||
// "<method>-<lowerfieldname>" Description for each command argument
|
||||
// "<typename>-<lowerfieldname>" Description for each object field
|
||||
// "<method>--condition<#>" Description for each result condition
|
||||
// "<method>--result<#>" Description for each primitive result num
|
||||
//
|
||||
// Notice that the "special" keys synopsis, condition<#>, and result<#> are
|
||||
// preceded by a double dash to ensure they don't conflict with field names.
|
||||
//
|
||||
// The condition keys are only required when there is more than on result type,
|
||||
// and the result key for a given result type is only required if it's not an
|
||||
// object.
|
||||
//
|
||||
// For example, consider the 'help' command itself. There are two possible
|
||||
// returns depending on the provided parameters. So, the help would be
|
||||
// generated by calling the function as follows:
|
||||
// GenerateHelp("help", descs, (*string)(nil), (*string)(nil)).
|
||||
//
|
||||
// The following keys would then be required in the provided descriptions map:
|
||||
//
|
||||
// "help--synopsis": "Returns a list of all commands or help for ...."
|
||||
// "help-command": "The command to retrieve help for",
|
||||
// "help--condition0": "no command provided"
|
||||
// "help--condition1": "command specified"
|
||||
// "help--result0": "List of commands"
|
||||
// "help--result1": "Help for specified command"
|
||||
func GenerateHelp(method string, descs map[string]string, resultTypes ...interface{}) (string, error) {
|
||||
// Look up details about the provided method and error out if not
|
||||
// registered.
|
||||
registerLock.RLock()
|
||||
rtp, ok := methodToConcreteType[method]
|
||||
info := methodToInfo[method]
|
||||
registerLock.RUnlock()
|
||||
if !ok {
|
||||
str := fmt.Sprintf("%q is not registered", method)
|
||||
return "", makeError(ErrUnregisteredMethod, str)
|
||||
}
|
||||
|
||||
// Validate each result type is a pointer to a supported type (or nil).
|
||||
for i, resultType := range resultTypes {
|
||||
if resultType == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
rtp := reflect.TypeOf(resultType)
|
||||
if rtp.Kind() != reflect.Ptr {
|
||||
str := fmt.Sprintf("result #%d (%v) is not a pointer",
|
||||
i, rtp.Kind())
|
||||
return "", makeError(ErrInvalidType, str)
|
||||
}
|
||||
|
||||
elemKind := rtp.Elem().Kind()
|
||||
if !isValidResultType(elemKind) {
|
||||
str := fmt.Sprintf("result #%d (%v) is not an allowed "+
|
||||
"type", i, elemKind)
|
||||
return "", makeError(ErrInvalidType, str)
|
||||
}
|
||||
}
|
||||
|
||||
// Create a closure for the description lookup function which falls back
|
||||
// to the base help descriptions map for unrecognized keys and tracks
|
||||
// and missing keys.
|
||||
var missingKey string
|
||||
xT := func(key string) string {
|
||||
if desc, ok := descs[key]; ok {
|
||||
return desc
|
||||
}
|
||||
if desc, ok := baseHelpDescs[key]; ok {
|
||||
return desc
|
||||
}
|
||||
|
||||
missingKey = key
|
||||
return key
|
||||
}
|
||||
|
||||
// Generate and return the help for the method.
|
||||
help := methodHelp(xT, rtp, info.defaults, method, resultTypes)
|
||||
if missingKey != "" {
|
||||
return help, makeError(ErrMissingDescription, missingKey)
|
||||
}
|
||||
return help, nil
|
||||
}
|
||||
+737
@@ -0,0 +1,737 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestHelpReflectInternals ensures the various help functions which deal with
|
||||
// reflect types work as expected for various Go types.
|
||||
func TestHelpReflectInternals(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
reflectType reflect.Type
|
||||
indentLevel int
|
||||
key string
|
||||
examples []string
|
||||
isComplex bool
|
||||
help string
|
||||
isInvalid bool
|
||||
}{
|
||||
{
|
||||
name: "int",
|
||||
reflectType: reflect.TypeOf(int(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "*int",
|
||||
reflectType: reflect.TypeOf((*int)(nil)),
|
||||
key: "json-type-value",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-value) fdk",
|
||||
isInvalid: true,
|
||||
},
|
||||
{
|
||||
name: "int8",
|
||||
reflectType: reflect.TypeOf(int8(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "int16",
|
||||
reflectType: reflect.TypeOf(int16(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "int32",
|
||||
reflectType: reflect.TypeOf(int32(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "int64",
|
||||
reflectType: reflect.TypeOf(int64(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "uint",
|
||||
reflectType: reflect.TypeOf(uint(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "uint8",
|
||||
reflectType: reflect.TypeOf(uint8(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "uint16",
|
||||
reflectType: reflect.TypeOf(uint16(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "uint32",
|
||||
reflectType: reflect.TypeOf(uint32(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "uint64",
|
||||
reflectType: reflect.TypeOf(uint64(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "float32",
|
||||
reflectType: reflect.TypeOf(float32(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n.nnn"},
|
||||
help: "n.nnn (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "float64",
|
||||
reflectType: reflect.TypeOf(float64(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n.nnn"},
|
||||
help: "n.nnn (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "string",
|
||||
reflectType: reflect.TypeOf(""),
|
||||
key: "json-type-string",
|
||||
examples: []string{`"json-example-string"`},
|
||||
help: "\"json-example-string\" (json-type-string) fdk",
|
||||
},
|
||||
{
|
||||
name: "bool",
|
||||
reflectType: reflect.TypeOf(true),
|
||||
key: "json-type-bool",
|
||||
examples: []string{"json-example-bool"},
|
||||
help: "json-example-bool (json-type-bool) fdk",
|
||||
},
|
||||
{
|
||||
name: "array of int",
|
||||
reflectType: reflect.TypeOf([1]int{0}),
|
||||
key: "json-type-arrayjson-type-numeric",
|
||||
examples: []string{"[n,...]"},
|
||||
help: "[n,...] (json-type-arrayjson-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "slice of int",
|
||||
reflectType: reflect.TypeOf([]int{0}),
|
||||
key: "json-type-arrayjson-type-numeric",
|
||||
examples: []string{"[n,...]"},
|
||||
help: "[n,...] (json-type-arrayjson-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "struct",
|
||||
reflectType: reflect.TypeOf(struct{}{}),
|
||||
key: "json-type-object",
|
||||
examples: []string{"{", "}\t\t"},
|
||||
isComplex: true,
|
||||
help: "{\n} ",
|
||||
},
|
||||
{
|
||||
name: "struct indent level 1",
|
||||
reflectType: reflect.TypeOf(struct{ field int }{}),
|
||||
indentLevel: 1,
|
||||
key: "json-type-object",
|
||||
examples: []string{
|
||||
" \"field\": n,\t(json-type-numeric)\t-field",
|
||||
" },\t\t",
|
||||
},
|
||||
help: "{\n" +
|
||||
" \"field\": n, (json-type-numeric) -field\n" +
|
||||
"} ",
|
||||
isComplex: true,
|
||||
},
|
||||
{
|
||||
name: "array of struct indent level 0",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
field int
|
||||
}
|
||||
return reflect.TypeOf([]s{})
|
||||
}(),
|
||||
key: "json-type-arrayjson-type-object",
|
||||
examples: []string{
|
||||
"[{",
|
||||
" \"field\": n,\t(json-type-numeric)\ts-field",
|
||||
"},...]",
|
||||
},
|
||||
help: "[{\n" +
|
||||
" \"field\": n, (json-type-numeric) s-field\n" +
|
||||
"},...]",
|
||||
isComplex: true,
|
||||
},
|
||||
{
|
||||
name: "array of struct indent level 1",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
field int
|
||||
}
|
||||
return reflect.TypeOf([]s{})
|
||||
}(),
|
||||
indentLevel: 1,
|
||||
key: "json-type-arrayjson-type-object",
|
||||
examples: []string{
|
||||
" \"field\": n,\t(json-type-numeric)\ts-field",
|
||||
" },...],\t\t",
|
||||
},
|
||||
help: "[{\n" +
|
||||
" \"field\": n, (json-type-numeric) s-field\n" +
|
||||
"},...]",
|
||||
isComplex: true,
|
||||
},
|
||||
{
|
||||
name: "map",
|
||||
reflectType: reflect.TypeOf(map[string]string{}),
|
||||
key: "json-type-object",
|
||||
examples: []string{"{",
|
||||
" \"fdk--key\": fdk--value, (json-type-object) fdk--desc",
|
||||
" ...", "}",
|
||||
},
|
||||
help: "{\n" +
|
||||
" \"fdk--key\": fdk--value, (json-type-object) fdk--desc\n" +
|
||||
" ...\n" +
|
||||
"}",
|
||||
isComplex: true,
|
||||
},
|
||||
{
|
||||
name: "complex",
|
||||
reflectType: reflect.TypeOf(complex64(0)),
|
||||
key: "json-type-value",
|
||||
examples: []string{"json-example-unknown"},
|
||||
help: "json-example-unknown (json-type-value) fdk",
|
||||
isInvalid: true,
|
||||
},
|
||||
}
|
||||
|
||||
xT := func(key string) string {
|
||||
return key
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Ensure the description key is the expected value.
|
||||
key := btcjson.TstReflectTypeToJSONType(xT, test.reflectType)
|
||||
if key != test.key {
|
||||
t.Errorf("Test #%d (%s) unexpected key - got: %v, "+
|
||||
"want: %v", i, test.name, key, test.key)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the generated example is as expected.
|
||||
examples, isComplex := btcjson.TstReflectTypeToJSONExample(xT,
|
||||
test.reflectType, test.indentLevel, "fdk")
|
||||
if isComplex != test.isComplex {
|
||||
t.Errorf("Test #%d (%s) unexpected isComplex - got: %v, "+
|
||||
"want: %v", i, test.name, isComplex,
|
||||
test.isComplex)
|
||||
continue
|
||||
}
|
||||
if len(examples) != len(test.examples) {
|
||||
t.Errorf("Test #%d (%s) unexpected result length - "+
|
||||
"got: %v, want: %v", i, test.name, len(examples),
|
||||
len(test.examples))
|
||||
continue
|
||||
}
|
||||
for j, example := range examples {
|
||||
if example != test.examples[j] {
|
||||
t.Errorf("Test #%d (%s) example #%d unexpected "+
|
||||
"example - got: %v, want: %v", i,
|
||||
test.name, j, example, test.examples[j])
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the generated result type help is as expected.
|
||||
helpText := btcjson.TstResultTypeHelp(xT, test.reflectType, "fdk")
|
||||
if helpText != test.help {
|
||||
t.Errorf("Test #%d (%s) unexpected result help - "+
|
||||
"got: %v, want: %v", i, test.name, helpText,
|
||||
test.help)
|
||||
continue
|
||||
}
|
||||
|
||||
isValid := btcjson.TstIsValidResultType(test.reflectType.Kind())
|
||||
if isValid != !test.isInvalid {
|
||||
t.Errorf("Test #%d (%s) unexpected result type validity "+
|
||||
"- got: %v", i, test.name, isValid)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestResultStructHelp ensures the expected help text format is returned for
|
||||
// various Go struct types.
|
||||
func TestResultStructHelp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
reflectType reflect.Type
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "empty struct",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct{}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "struct with primitive field",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
field int
|
||||
}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: []string{
|
||||
"\"field\": n,\t(json-type-numeric)\ts-field",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "struct with primitive field and json tag",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Field int `json:"f"`
|
||||
}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: []string{
|
||||
"\"f\": n,\t(json-type-numeric)\ts-f",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "struct with array of primitive field",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
field []int
|
||||
}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: []string{
|
||||
"\"field\": [n,...],\t(json-type-arrayjson-type-numeric)\ts-field",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "struct with sub-struct field",
|
||||
reflectType: func() reflect.Type {
|
||||
type s2 struct {
|
||||
subField int
|
||||
}
|
||||
type s struct {
|
||||
field s2
|
||||
}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: []string{
|
||||
"\"field\": {\t(json-type-object)\ts-field",
|
||||
"{",
|
||||
" \"subfield\": n,\t(json-type-numeric)\ts2-subfield",
|
||||
"}\t\t",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "struct with sub-struct field pointer",
|
||||
reflectType: func() reflect.Type {
|
||||
type s2 struct {
|
||||
subField int
|
||||
}
|
||||
type s struct {
|
||||
field *s2
|
||||
}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: []string{
|
||||
"\"field\": {\t(json-type-object)\ts-field",
|
||||
"{",
|
||||
" \"subfield\": n,\t(json-type-numeric)\ts2-subfield",
|
||||
"}\t\t",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "struct with array of structs field",
|
||||
reflectType: func() reflect.Type {
|
||||
type s2 struct {
|
||||
subField int
|
||||
}
|
||||
type s struct {
|
||||
field []s2
|
||||
}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: []string{
|
||||
"\"field\": [{\t(json-type-arrayjson-type-object)\ts-field",
|
||||
"[{",
|
||||
" \"subfield\": n,\t(json-type-numeric)\ts2-subfield",
|
||||
"},...]",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
xT := func(key string) string {
|
||||
return key
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
results := btcjson.TstResultStructHelp(xT, test.reflectType, 0)
|
||||
if len(results) != len(test.expected) {
|
||||
t.Errorf("Test #%d (%s) unexpected result length - "+
|
||||
"got: %v, want: %v", i, test.name, len(results),
|
||||
len(test.expected))
|
||||
continue
|
||||
}
|
||||
for j, result := range results {
|
||||
if result != test.expected[j] {
|
||||
t.Errorf("Test #%d (%s) result #%d unexpected "+
|
||||
"result - got: %v, want: %v", i,
|
||||
test.name, j, result, test.expected[j])
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestHelpArgInternals ensures the various help functions which deal with
|
||||
// arguments work as expected for various argument types.
|
||||
func TestHelpArgInternals(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
reflectType reflect.Type
|
||||
defaults map[int]reflect.Value
|
||||
help string
|
||||
}{
|
||||
{
|
||||
name: "command with no args",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct{}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "",
|
||||
},
|
||||
{
|
||||
name: "command with one required arg",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Field int
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "1. field (json-type-numeric, help-required) test-field\n",
|
||||
},
|
||||
{
|
||||
name: "command with one optional arg, no default",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Optional *int
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "1. optional (json-type-numeric, help-optional) test-optional\n",
|
||||
},
|
||||
{
|
||||
name: "command with one optional arg with default",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Optional *string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: func() map[int]reflect.Value {
|
||||
defVal := "test"
|
||||
return map[int]reflect.Value{
|
||||
0: reflect.ValueOf(&defVal),
|
||||
}
|
||||
}(),
|
||||
help: "1. optional (json-type-string, help-optional, help-default=\"test\") test-optional\n",
|
||||
},
|
||||
{
|
||||
name: "command with struct field",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s2 struct {
|
||||
F int8
|
||||
}
|
||||
type s struct {
|
||||
Field s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "1. field (json-type-object, help-required) test-field\n" +
|
||||
"{\n" +
|
||||
" \"f\": n, (json-type-numeric) s2-f\n" +
|
||||
"} \n",
|
||||
},
|
||||
{
|
||||
name: "command with map field",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Field map[string]float64
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "1. field (json-type-object, help-required) test-field\n" +
|
||||
"{\n" +
|
||||
" \"test-field--key\": test-field--value, (json-type-object) test-field--desc\n" +
|
||||
" ...\n" +
|
||||
"}\n",
|
||||
},
|
||||
{
|
||||
name: "command with slice of primitives field",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Field []int64
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "1. field (json-type-arrayjson-type-numeric, help-required) test-field\n",
|
||||
},
|
||||
{
|
||||
name: "command with slice of structs field",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s2 struct {
|
||||
F int64
|
||||
}
|
||||
type s struct {
|
||||
Field []s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "1. field (json-type-arrayjson-type-object, help-required) test-field\n" +
|
||||
"[{\n" +
|
||||
" \"f\": n, (json-type-numeric) s2-f\n" +
|
||||
"},...]\n",
|
||||
},
|
||||
}
|
||||
|
||||
xT := func(key string) string {
|
||||
return key
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
help := btcjson.TstArgHelp(xT, test.reflectType, test.defaults,
|
||||
test.method)
|
||||
if help != test.help {
|
||||
t.Errorf("Test #%d (%s) unexpected help - got:\n%v\n"+
|
||||
"want:\n%v", i, test.name, help, test.help)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMethodHelp ensures the method help function works as expected for various
|
||||
// command structs.
|
||||
func TestMethodHelp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
reflectType reflect.Type
|
||||
defaults map[int]reflect.Value
|
||||
resultTypes []interface{}
|
||||
help string
|
||||
}{
|
||||
{
|
||||
name: "command with no args or results",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct{}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
help: "test\n\ntest--synopsis\n\n" +
|
||||
"help-arguments:\nhelp-arguments-none\n\n" +
|
||||
"help-result:\nhelp-result-nothing\n",
|
||||
},
|
||||
{
|
||||
name: "command with no args and one primitive result",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct{}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
resultTypes: []interface{}{(*int64)(nil)},
|
||||
help: "test\n\ntest--synopsis\n\n" +
|
||||
"help-arguments:\nhelp-arguments-none\n\n" +
|
||||
"help-result:\nn (json-type-numeric) test--result0\n",
|
||||
},
|
||||
{
|
||||
name: "command with no args and two results",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct{}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
resultTypes: []interface{}{(*int64)(nil), nil},
|
||||
help: "test\n\ntest--synopsis\n\n" +
|
||||
"help-arguments:\nhelp-arguments-none\n\n" +
|
||||
"help-result (test--condition0):\nn (json-type-numeric) test--result0\n\n" +
|
||||
"help-result (test--condition1):\nhelp-result-nothing\n",
|
||||
},
|
||||
{
|
||||
name: "command with primitive arg and no results",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Field bool
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
help: "test field\n\ntest--synopsis\n\n" +
|
||||
"help-arguments:\n1. field (json-type-bool, help-required) test-field\n\n" +
|
||||
"help-result:\nhelp-result-nothing\n",
|
||||
},
|
||||
{
|
||||
name: "command with primitive optional and no results",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Field *bool
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
help: "test (field)\n\ntest--synopsis\n\n" +
|
||||
"help-arguments:\n1. field (json-type-bool, help-optional) test-field\n\n" +
|
||||
"help-result:\nhelp-result-nothing\n",
|
||||
},
|
||||
}
|
||||
|
||||
xT := func(key string) string {
|
||||
return key
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
help := btcjson.TestMethodHelp(xT, test.reflectType,
|
||||
test.defaults, test.method, test.resultTypes)
|
||||
if help != test.help {
|
||||
t.Errorf("Test #%d (%s) unexpected help - got:\n%v\n"+
|
||||
"want:\n%v", i, test.name, help, test.help)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateHelpErrors ensures the GenerateHelp function returns the expected
|
||||
// errors.
|
||||
func TestGenerateHelpErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
resultTypes []interface{}
|
||||
err btcjson.Error
|
||||
}{
|
||||
{
|
||||
name: "unregistered command",
|
||||
method: "boguscommand",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "non-pointer result type",
|
||||
method: "help",
|
||||
resultTypes: []interface{}{0},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid result type",
|
||||
method: "help",
|
||||
resultTypes: []interface{}{(*complex64)(nil)},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "missing description",
|
||||
method: "help",
|
||||
resultTypes: []interface{}{(*string)(nil), nil},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrMissingDescription},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
_, err := btcjson.GenerateHelp(test.method, nil,
|
||||
test.resultTypes...)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%[2]v), "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||
err, test.err.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateHelp performs a very basic test to ensure GenerateHelp is working
|
||||
// as expected. The internal are testd much more thoroughly in other tests, so
|
||||
// there is no need to add more tests here.
|
||||
func TestGenerateHelp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
descs := map[string]string{
|
||||
"help--synopsis": "test",
|
||||
"help-command": "test",
|
||||
}
|
||||
help, err := btcjson.GenerateHelp("help", descs)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateHelp: unexpected error: %v", err)
|
||||
}
|
||||
wantHelp := "help (\"command\")\n\n" +
|
||||
"test\n\nArguments:\n1. command (string, optional) test\n\n" +
|
||||
"Result:\nNothing\n"
|
||||
if help != wantHelp {
|
||||
t.Fatalf("GenerateHelp: unexpected help - got\n%v\nwant\n%v",
|
||||
help, wantHelp)
|
||||
}
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
// Bool is a helper routine that allocates a new bool value to store v and
|
||||
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||
func Bool(v bool) *bool {
|
||||
p := new(bool)
|
||||
*p = v
|
||||
return p
|
||||
}
|
||||
|
||||
// Int is a helper routine that allocates a new int value to store v and
|
||||
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||
func Int(v int) *int {
|
||||
p := new(int)
|
||||
*p = v
|
||||
return p
|
||||
}
|
||||
|
||||
// Uint is a helper routine that allocates a new uint value to store v and
|
||||
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||
func Uint(v uint) *uint {
|
||||
p := new(uint)
|
||||
*p = v
|
||||
return p
|
||||
}
|
||||
|
||||
// Int32 is a helper routine that allocates a new int32 value to store v and
|
||||
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||
func Int32(v int32) *int32 {
|
||||
p := new(int32)
|
||||
*p = v
|
||||
return p
|
||||
}
|
||||
|
||||
// Uint32 is a helper routine that allocates a new uint32 value to store v and
|
||||
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||
func Uint32(v uint32) *uint32 {
|
||||
p := new(uint32)
|
||||
*p = v
|
||||
return p
|
||||
}
|
||||
|
||||
// Int64 is a helper routine that allocates a new int64 value to store v and
|
||||
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||
func Int64(v int64) *int64 {
|
||||
p := new(int64)
|
||||
*p = v
|
||||
return p
|
||||
}
|
||||
|
||||
// Uint64 is a helper routine that allocates a new uint64 value to store v and
|
||||
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||
func Uint64(v uint64) *uint64 {
|
||||
p := new(uint64)
|
||||
*p = v
|
||||
return p
|
||||
}
|
||||
|
||||
// Float64 is a helper routine that allocates a new float64 value to store v and
|
||||
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||
func Float64(v float64) *float64 {
|
||||
p := new(float64)
|
||||
*p = v
|
||||
return p
|
||||
}
|
||||
|
||||
// String is a helper routine that allocates a new string value to store v and
|
||||
// returns a pointer to it. This is useful when assigning optional parameters.
|
||||
func String(v string) *string {
|
||||
p := new(string)
|
||||
*p = v
|
||||
return p
|
||||
}
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestHelpers tests the various helper functions which create pointers to
|
||||
// primitive types.
|
||||
func TestHelpers(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
f func() interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
name: "bool",
|
||||
f: func() interface{} {
|
||||
return btcjson.Bool(true)
|
||||
},
|
||||
expected: func() interface{} {
|
||||
val := true
|
||||
return &val
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "int",
|
||||
f: func() interface{} {
|
||||
return btcjson.Int(5)
|
||||
},
|
||||
expected: func() interface{} {
|
||||
val := int(5)
|
||||
return &val
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "uint",
|
||||
f: func() interface{} {
|
||||
return btcjson.Uint(5)
|
||||
},
|
||||
expected: func() interface{} {
|
||||
val := uint(5)
|
||||
return &val
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "int32",
|
||||
f: func() interface{} {
|
||||
return btcjson.Int32(5)
|
||||
},
|
||||
expected: func() interface{} {
|
||||
val := int32(5)
|
||||
return &val
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "uint32",
|
||||
f: func() interface{} {
|
||||
return btcjson.Uint32(5)
|
||||
},
|
||||
expected: func() interface{} {
|
||||
val := uint32(5)
|
||||
return &val
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "int64",
|
||||
f: func() interface{} {
|
||||
return btcjson.Int64(5)
|
||||
},
|
||||
expected: func() interface{} {
|
||||
val := int64(5)
|
||||
return &val
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "uint64",
|
||||
f: func() interface{} {
|
||||
return btcjson.Uint64(5)
|
||||
},
|
||||
expected: func() interface{} {
|
||||
val := uint64(5)
|
||||
return &val
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "string",
|
||||
f: func() interface{} {
|
||||
return btcjson.String("abc")
|
||||
},
|
||||
expected: func() interface{} {
|
||||
val := "abc"
|
||||
return &val
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
result := test.f()
|
||||
if !reflect.DeepEqual(result, test.expected) {
|
||||
t.Errorf("Test #%d (%s) unexpected value - got %v, "+
|
||||
"want %v", i, test.name, result, test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// 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"
|
||||
)
|
||||
|
||||
// RPCErrorCode represents an error code to be used as a part of an RPCError
|
||||
// which is in turn used in a JSON-RPC Response object.
|
||||
//
|
||||
// A specific type is used to help ensure the wrong errors aren't used.
|
||||
type RPCErrorCode int
|
||||
|
||||
// RPCError represents an error that is used as a part of a JSON-RPC Response
|
||||
// object.
|
||||
type RPCError struct {
|
||||
Code RPCErrorCode `json:"code,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// Guarantee RPCError satisifies the builtin error interface.
|
||||
var _, _ error = RPCError{}, (*RPCError)(nil)
|
||||
|
||||
// Error returns a string describing the RPC error. This satisifies the
|
||||
// builtin error interface.
|
||||
func (e RPCError) Error() string {
|
||||
return fmt.Sprintf("%d: %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// NewRPCError constructs and returns a new JSON-RPC error that is suitable
|
||||
// for use in a JSON-RPC Response object.
|
||||
func NewRPCError(code RPCErrorCode, message string) *RPCError {
|
||||
return &RPCError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// IsValidIDType checks that the ID field (which can go in any of the JSON-RPC
|
||||
// requests, responses, or notifications) is valid. JSON-RPC 1.0 allows any
|
||||
// valid JSON type. JSON-RPC 2.0 (which bitcoind follows for some parts) only
|
||||
// allows string, number, or null, so this function restricts the allowed types
|
||||
// to that list. This function is only provided in case the caller is manually
|
||||
// marshalling for some reason. The functions which accept an ID in this
|
||||
// package already call this function to ensure the provided id is valid.
|
||||
func IsValidIDType(id interface{}) bool {
|
||||
switch id.(type) {
|
||||
case int, int8, int16, int32, int64,
|
||||
uint, uint8, uint16, uint32, uint64,
|
||||
float32, float64,
|
||||
string,
|
||||
nil:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Request is a type for raw JSON-RPC 1.0 requests. The Method field identifies
|
||||
// the specific command type which in turns leads to different parameters.
|
||||
// Callers typically will not use this directly since this package provides a
|
||||
// statically typed command infrastructure which handles creation of these
|
||||
// requests, however this struct it being exported in case the caller wants to
|
||||
// construct raw requests for some reason.
|
||||
type Request struct {
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
Method string `json:"method"`
|
||||
Params []json.RawMessage `json:"params"`
|
||||
ID interface{} `json:"id"`
|
||||
}
|
||||
|
||||
// NewRequest returns a new JSON-RPC 1.0 request object given the provided id,
|
||||
// method, and parameters. The parameters are marshalled into a json.RawMessage
|
||||
// for the Params field of the returned request object. This function is only
|
||||
// provided in case the caller wants to construct raw requests for some reason.
|
||||
//
|
||||
// Typically callers will instead want to create a registered concrete command
|
||||
// type with the NewCmd or New<Foo>Cmd functions and call the MarshalCmd
|
||||
// function with that command to generate the marshalled JSON-RPC request.
|
||||
func NewRequest(id interface{}, method string, params []interface{}) (*Request, error) {
|
||||
if !IsValidIDType(id) {
|
||||
str := fmt.Sprintf("the id of type '%T' is invalid", id)
|
||||
return nil, makeError(ErrInvalidType, str)
|
||||
}
|
||||
|
||||
rawParams := make([]json.RawMessage, 0, len(params))
|
||||
for _, param := range params {
|
||||
marshalledParam, err := json.Marshal(param)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rawMessage := json.RawMessage(marshalledParam)
|
||||
rawParams = append(rawParams, rawMessage)
|
||||
}
|
||||
|
||||
return &Request{
|
||||
Jsonrpc: "1.0",
|
||||
ID: id,
|
||||
Method: method,
|
||||
Params: rawParams,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Response is the general form of a JSON-RPC response. The type of the Result
|
||||
// field varies from one command to the next, so it is implemented as an
|
||||
// interface. The ID field has to be a pointer for Go to put a null in it when
|
||||
// empty.
|
||||
type Response struct {
|
||||
Result json.RawMessage `json:"result"`
|
||||
Error *RPCError `json:"error"`
|
||||
ID *interface{} `json:"id"`
|
||||
}
|
||||
|
||||
// NewResponse returns a new JSON-RPC response object given the provided id,
|
||||
// marshalled result, and RPC error. This function is only provided in case the
|
||||
// caller wants to construct raw responses for some reason.
|
||||
//
|
||||
// Typically callers will instead want to create the fully marshalled JSON-RPC
|
||||
// response to send over the wire with the MarshalResponse function.
|
||||
func NewResponse(id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Response, error) {
|
||||
if !IsValidIDType(id) {
|
||||
str := fmt.Sprintf("the id of type '%T' is invalid", id)
|
||||
return nil, makeError(ErrInvalidType, str)
|
||||
}
|
||||
|
||||
pid := &id
|
||||
return &Response{
|
||||
Result: marshalledResult,
|
||||
Error: rpcErr,
|
||||
ID: pid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarshalResponse marshals the passed id, result, and RPCError to a JSON-RPC
|
||||
// response byte slice that is suitable for transmission to a JSON-RPC client.
|
||||
func MarshalResponse(id interface{}, result interface{}, rpcErr *RPCError) ([]byte, error) {
|
||||
marshalledResult, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err := NewResponse(id, marshalledResult, rpcErr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(&response)
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestIsValidIDType ensures the IsValidIDType function behaves as expected.
|
||||
func TestIsValidIDType(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
id interface{}
|
||||
isValid bool
|
||||
}{
|
||||
{"int", int(1), true},
|
||||
{"int8", int8(1), true},
|
||||
{"int16", int16(1), true},
|
||||
{"int32", int32(1), true},
|
||||
{"int64", int64(1), true},
|
||||
{"uint", uint(1), true},
|
||||
{"uint8", uint8(1), true},
|
||||
{"uint16", uint16(1), true},
|
||||
{"uint32", uint32(1), true},
|
||||
{"uint64", uint64(1), true},
|
||||
{"string", "1", true},
|
||||
{"nil", nil, true},
|
||||
{"float32", float32(1), true},
|
||||
{"float64", float64(1), true},
|
||||
{"bool", true, false},
|
||||
{"chan int", make(chan int), false},
|
||||
{"complex64", complex64(1), false},
|
||||
{"complex128", complex128(1), false},
|
||||
{"func", func() {}, false},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
if btcjson.IsValidIDType(test.id) != test.isValid {
|
||||
t.Errorf("Test #%d (%s) valid mismatch - got %v, "+
|
||||
"want %v", i, test.name, !test.isValid,
|
||||
test.isValid)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMarshalResponse ensures the MarshalResponse function works as expected.
|
||||
func TestMarshalResponse(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testID := 1
|
||||
tests := []struct {
|
||||
name string
|
||||
result interface{}
|
||||
jsonErr *btcjson.RPCError
|
||||
expected []byte
|
||||
}{
|
||||
{
|
||||
name: "ordinary bool result with no error",
|
||||
result: true,
|
||||
jsonErr: nil,
|
||||
expected: []byte(`{"result":true,"error":null,"id":1}`),
|
||||
},
|
||||
{
|
||||
name: "result with error",
|
||||
result: nil,
|
||||
jsonErr: func() *btcjson.RPCError {
|
||||
return btcjson.NewRPCError(btcjson.ErrRPCBlockNotFound, "123 not found")
|
||||
}(),
|
||||
expected: []byte(`{"result":null,"error":{"code":-5,"message":"123 not found"},"id":1}`),
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
_, _ = i, test
|
||||
marshalled, err := btcjson.MarshalResponse(testID, test.result, test.jsonErr)
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(marshalled, test.expected) {
|
||||
t.Errorf("Test #%d (%s) mismatched result - got %s, "+
|
||||
"want %s", i, test.name, marshalled,
|
||||
test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMiscErrors tests a few error conditions not covered elsewhere.
|
||||
func TestMiscErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Force an error in NewRequest by giving it a parameter type that is
|
||||
// not supported.
|
||||
_, err := btcjson.NewRequest(nil, "test", []interface{}{make(chan int)})
|
||||
if err == nil {
|
||||
t.Error("NewRequest: did not receive error")
|
||||
return
|
||||
}
|
||||
|
||||
// Force an error in MarshalResponse by giving it an id type that is not
|
||||
// supported.
|
||||
wantErr := btcjson.Error{ErrorCode: btcjson.ErrInvalidType}
|
||||
_, err = btcjson.MarshalResponse(make(chan int), nil, nil)
|
||||
if jerr, ok := err.(btcjson.Error); !ok || jerr.ErrorCode != wantErr.ErrorCode {
|
||||
t.Errorf("MarshalResult: did not receive expected error - got "+
|
||||
"%v (%[1]T), want %v (%[2]T)", err, wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
// Force an error in MarshalResponse by giving it a result type that
|
||||
// can't be marshalled.
|
||||
_, err = btcjson.MarshalResponse(1, make(chan int), nil)
|
||||
if _, ok := err.(*json.UnsupportedTypeError); !ok {
|
||||
wantErr := &json.UnsupportedTypeError{}
|
||||
t.Errorf("MarshalResult: did not receive expected error - got "+
|
||||
"%v (%[1]T), want %T", err, wantErr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// TestRPCError tests the error output for the RPCError type.
|
||||
func TestRPCError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in *btcjson.RPCError
|
||||
want string
|
||||
}{
|
||||
{
|
||||
btcjson.ErrRPCInvalidRequest,
|
||||
"-32600: Invalid request",
|
||||
},
|
||||
{
|
||||
btcjson.ErrRPCMethodNotFound,
|
||||
"-32601: Method not found",
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
result := test.in.Error()
|
||||
if result != test.want {
|
||||
t.Errorf("Error #%d\n got: %s want: %s", i, result,
|
||||
test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
// Standard JSON-RPC 2.0 errors.
|
||||
var (
|
||||
ErrRPCInvalidRequest = &RPCError{
|
||||
Code: -32600,
|
||||
Message: "Invalid request",
|
||||
}
|
||||
ErrRPCMethodNotFound = &RPCError{
|
||||
Code: -32601,
|
||||
Message: "Method not found",
|
||||
}
|
||||
ErrRPCInvalidParams = &RPCError{
|
||||
Code: -32602,
|
||||
Message: "Invalid parameters",
|
||||
}
|
||||
ErrRPCInternal = &RPCError{
|
||||
Code: -32603,
|
||||
Message: "Internal error",
|
||||
}
|
||||
ErrRPCParse = &RPCError{
|
||||
Code: -32700,
|
||||
Message: "Parse error",
|
||||
}
|
||||
)
|
||||
|
||||
// General application defined JSON errors.
|
||||
const (
|
||||
ErrRPCMisc RPCErrorCode = -1
|
||||
ErrRPCForbiddenBySafeMode RPCErrorCode = -2
|
||||
ErrRPCType RPCErrorCode = -3
|
||||
ErrRPCInvalidAddressOrKey RPCErrorCode = -5
|
||||
ErrRPCOutOfMemory RPCErrorCode = -7
|
||||
ErrRPCInvalidParameter RPCErrorCode = -8
|
||||
ErrRPCDatabase RPCErrorCode = -20
|
||||
ErrRPCDeserialization RPCErrorCode = -22
|
||||
ErrRPCVerify RPCErrorCode = -25
|
||||
)
|
||||
|
||||
// Peer-to-peer client errors.
|
||||
const (
|
||||
ErrRPCClientNotConnected RPCErrorCode = -9
|
||||
ErrRPCClientInInitialDownload RPCErrorCode = -10
|
||||
ErrRPCClientNodeNotAdded RPCErrorCode = -24
|
||||
)
|
||||
|
||||
// Wallet JSON errors
|
||||
const (
|
||||
ErrRPCWallet RPCErrorCode = -4
|
||||
ErrRPCWalletInsufficientFunds RPCErrorCode = -6
|
||||
ErrRPCWalletInvalidAccountName RPCErrorCode = -11
|
||||
ErrRPCWalletKeypoolRanOut RPCErrorCode = -12
|
||||
ErrRPCWalletUnlockNeeded RPCErrorCode = -13
|
||||
ErrRPCWalletPassphraseIncorrect RPCErrorCode = -14
|
||||
ErrRPCWalletWrongEncState RPCErrorCode = -15
|
||||
ErrRPCWalletEncryptionFailed RPCErrorCode = -16
|
||||
ErrRPCWalletAlreadyUnlocked RPCErrorCode = -17
|
||||
)
|
||||
|
||||
// Specific Errors related to commands. These are the ones a user of the RPC
|
||||
// server are most likely to see. Generally, the codes should match one of the
|
||||
// more general errors above.
|
||||
const (
|
||||
ErrRPCBlockNotFound RPCErrorCode = -5
|
||||
ErrRPCBlockCount RPCErrorCode = -5
|
||||
ErrRPCBestBlockHash RPCErrorCode = -5
|
||||
ErrRPCDifficulty RPCErrorCode = -5
|
||||
ErrRPCOutOfRange RPCErrorCode = -1
|
||||
ErrRPCNoTxInfo RPCErrorCode = -5
|
||||
ErrRPCNoNewestBlockInfo RPCErrorCode = -5
|
||||
ErrRPCInvalidTxVout RPCErrorCode = -5
|
||||
ErrRPCRawTxString RPCErrorCode = -32602
|
||||
ErrRPCDecodeHexString RPCErrorCode = -22
|
||||
)
|
||||
|
||||
// Errors that are specific to btcd.
|
||||
const (
|
||||
ErrRPCNoWallet RPCErrorCode = -1
|
||||
ErrRPCUnimplemented RPCErrorCode = -1
|
||||
)
|
||||
+292
@@ -0,0 +1,292 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// 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:
|
||||
//
|
||||
// - 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)
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
|
||||
sort.Sort(sort.StringSlice(methods))
|
||||
return methods
|
||||
}
|
||||
+263
@@ -0,0 +1,263 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestUsageFlagStringer tests the stringized output for the UsageFlag type.
|
||||
func TestUsageFlagStringer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in btcjson.UsageFlag
|
||||
want string
|
||||
}{
|
||||
{0, "0x0"},
|
||||
{btcjson.UFWalletOnly, "UFWalletOnly"},
|
||||
{btcjson.UFWebsocketOnly, "UFWebsocketOnly"},
|
||||
{btcjson.UFNotification, "UFNotification"},
|
||||
{btcjson.UFWalletOnly | btcjson.UFWebsocketOnly,
|
||||
"UFWalletOnly|UFWebsocketOnly"},
|
||||
{btcjson.UFWalletOnly | btcjson.UFWebsocketOnly | (1 << 31),
|
||||
"UFWalletOnly|UFWebsocketOnly|0x80000000"},
|
||||
}
|
||||
|
||||
// Detect additional usage flags that don't have the stringer added.
|
||||
numUsageFlags := 0
|
||||
highestUsageFlagBit := btcjson.TstHighestUsageFlagBit
|
||||
for highestUsageFlagBit > 1 {
|
||||
numUsageFlags++
|
||||
highestUsageFlagBit >>= 1
|
||||
}
|
||||
if len(tests)-3 != numUsageFlags {
|
||||
t.Errorf("It appears a usage flag was added without adding " +
|
||||
"an associated stringer test")
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
result := test.in.String()
|
||||
if result != test.want {
|
||||
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||
test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterCmdErrors ensures the RegisterCmd function returns the expected
|
||||
// error when provided with invalid types.
|
||||
func TestRegisterCmdErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
cmdFunc func() interface{}
|
||||
flags btcjson.UsageFlag
|
||||
err btcjson.Error
|
||||
}{
|
||||
{
|
||||
name: "duplicate method",
|
||||
method: "getblock",
|
||||
cmdFunc: func() interface{} {
|
||||
return struct{}{}
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrDuplicateMethod},
|
||||
},
|
||||
{
|
||||
name: "invalid usage flags",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
return 0
|
||||
},
|
||||
flags: btcjson.TstHighestUsageFlagBit,
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidUsageFlags},
|
||||
},
|
||||
{
|
||||
name: "invalid type",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
return 0
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid type 2",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
return &[]string{}
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "embedded field",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ int }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrEmbeddedType},
|
||||
},
|
||||
{
|
||||
name: "unexported field",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ a int }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnexportedField},
|
||||
},
|
||||
{
|
||||
name: "unsupported field type 1",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ A **int }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||
},
|
||||
{
|
||||
name: "unsupported field type 2",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ A chan int }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||
},
|
||||
{
|
||||
name: "unsupported field type 3",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ A complex64 }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||
},
|
||||
{
|
||||
name: "unsupported field type 4",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ A complex128 }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||
},
|
||||
{
|
||||
name: "unsupported field type 5",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ A func() }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||
},
|
||||
{
|
||||
name: "unsupported field type 6",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ A interface{} }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||
},
|
||||
{
|
||||
name: "required after optional",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct {
|
||||
A *int
|
||||
B int
|
||||
}
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrNonOptionalField},
|
||||
},
|
||||
{
|
||||
name: "non-optional with default",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct {
|
||||
A int `jsonrpcdefault:"1"`
|
||||
}
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrNonOptionalDefault},
|
||||
},
|
||||
{
|
||||
name: "mismatched default",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct {
|
||||
A *int `jsonrpcdefault:"1.7"`
|
||||
}
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrMismatchedDefault},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
err := btcjson.RegisterCmd(test.method, test.cmdFunc(),
|
||||
test.flags)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T, "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||
"%v, want %v", i, test.name, gotErrorCode,
|
||||
test.err.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMustRegisterCmdPanic ensures the MustRegisterCmd function panics when
|
||||
// used to register an invalid type.
|
||||
func TestMustRegisterCmdPanic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Setup a defer to catch the expected panic to ensure it actually
|
||||
// paniced.
|
||||
defer func() {
|
||||
if err := recover(); err == nil {
|
||||
t.Error("MustRegisterCmd did not panic as expected")
|
||||
}
|
||||
}()
|
||||
|
||||
// Intentionally try to register an invalid type to force a panic.
|
||||
btcjson.MustRegisterCmd("panicme", 0, 0)
|
||||
}
|
||||
|
||||
// TestRegisteredCmdMethods tests the RegisteredCmdMethods function ensure it
|
||||
// works as expected.
|
||||
func TestRegisteredCmdMethods(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Ensure the registerd methods are returned.
|
||||
methods := btcjson.RegisteredCmdMethods()
|
||||
if len(methods) == 0 {
|
||||
t.Fatal("RegisteredCmdMethods: no methods")
|
||||
}
|
||||
|
||||
// Ensure the returned methods are sorted.
|
||||
sortedMethods := make([]string, len(methods))
|
||||
copy(sortedMethods, methods)
|
||||
sort.Sort(sort.StringSlice(sortedMethods))
|
||||
if !reflect.DeepEqual(sortedMethods, methods) {
|
||||
t.Fatal("RegisteredCmdMethods: methods are not sorted")
|
||||
}
|
||||
}
|
||||
+699
@@ -0,0 +1,699 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// NOTE: This file is intended to house the RPC commands that are supported by
|
||||
// a wallet server.
|
||||
|
||||
package btcjson
|
||||
|
||||
// AddMultisigAddressCmd defines the addmutisigaddress JSON-RPC command.
|
||||
type AddMultisigAddressCmd struct {
|
||||
NRequired int
|
||||
Keys []string
|
||||
Account *string
|
||||
}
|
||||
|
||||
// NewAddMultisigAddressCmd returns a new instance which can be used to issue a
|
||||
// addmultisigaddress JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewAddMultisigAddressCmd(nRequired int, keys []string, account *string) *AddMultisigAddressCmd {
|
||||
return &AddMultisigAddressCmd{
|
||||
NRequired: nRequired,
|
||||
Keys: keys,
|
||||
Account: account,
|
||||
}
|
||||
}
|
||||
|
||||
// AddWitnessAddressCmd defines the addwitnessaddress JSON-RPC command.
|
||||
type AddWitnessAddressCmd struct {
|
||||
Address string
|
||||
}
|
||||
|
||||
// NewAddWitnessAddressCmd returns a new instance which can be used to issue a
|
||||
// addwitnessaddress JSON-RPC command.
|
||||
func NewAddWitnessAddressCmd(address string) *AddWitnessAddressCmd {
|
||||
return &AddWitnessAddressCmd{
|
||||
Address: address,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateMultisigCmd defines the createmultisig JSON-RPC command.
|
||||
type CreateMultisigCmd struct {
|
||||
NRequired int
|
||||
Keys []string
|
||||
}
|
||||
|
||||
// NewCreateMultisigCmd returns a new instance which can be used to issue a
|
||||
// createmultisig JSON-RPC command.
|
||||
func NewCreateMultisigCmd(nRequired int, keys []string) *CreateMultisigCmd {
|
||||
return &CreateMultisigCmd{
|
||||
NRequired: nRequired,
|
||||
Keys: keys,
|
||||
}
|
||||
}
|
||||
|
||||
// DumpPrivKeyCmd defines the dumpprivkey JSON-RPC command.
|
||||
type DumpPrivKeyCmd struct {
|
||||
Address string
|
||||
}
|
||||
|
||||
// NewDumpPrivKeyCmd returns a new instance which can be used to issue a
|
||||
// dumpprivkey JSON-RPC command.
|
||||
func NewDumpPrivKeyCmd(address string) *DumpPrivKeyCmd {
|
||||
return &DumpPrivKeyCmd{
|
||||
Address: address,
|
||||
}
|
||||
}
|
||||
|
||||
// EncryptWalletCmd defines the encryptwallet JSON-RPC command.
|
||||
type EncryptWalletCmd struct {
|
||||
Passphrase string
|
||||
}
|
||||
|
||||
// NewEncryptWalletCmd returns a new instance which can be used to issue a
|
||||
// encryptwallet JSON-RPC command.
|
||||
func NewEncryptWalletCmd(passphrase string) *EncryptWalletCmd {
|
||||
return &EncryptWalletCmd{
|
||||
Passphrase: passphrase,
|
||||
}
|
||||
}
|
||||
|
||||
// EstimateFeeCmd defines the estimatefee JSON-RPC command.
|
||||
type EstimateFeeCmd struct {
|
||||
NumBlocks int64
|
||||
}
|
||||
|
||||
// NewEstimateFeeCmd returns a new instance which can be used to issue a
|
||||
// estimatefee JSON-RPC command.
|
||||
func NewEstimateFeeCmd(numBlocks int64) *EstimateFeeCmd {
|
||||
return &EstimateFeeCmd{
|
||||
NumBlocks: numBlocks,
|
||||
}
|
||||
}
|
||||
|
||||
// EstimatePriorityCmd defines the estimatepriority JSON-RPC command.
|
||||
type EstimatePriorityCmd struct {
|
||||
NumBlocks int64
|
||||
}
|
||||
|
||||
// NewEstimatePriorityCmd returns a new instance which can be used to issue a
|
||||
// estimatepriority JSON-RPC command.
|
||||
func NewEstimatePriorityCmd(numBlocks int64) *EstimatePriorityCmd {
|
||||
return &EstimatePriorityCmd{
|
||||
NumBlocks: numBlocks,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAccountCmd defines the getaccount JSON-RPC command.
|
||||
type GetAccountCmd struct {
|
||||
Address string
|
||||
}
|
||||
|
||||
// NewGetAccountCmd returns a new instance which can be used to issue a
|
||||
// getaccount JSON-RPC command.
|
||||
func NewGetAccountCmd(address string) *GetAccountCmd {
|
||||
return &GetAccountCmd{
|
||||
Address: address,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAccountAddressCmd defines the getaccountaddress JSON-RPC command.
|
||||
type GetAccountAddressCmd struct {
|
||||
Account string
|
||||
}
|
||||
|
||||
// NewGetAccountAddressCmd returns a new instance which can be used to issue a
|
||||
// getaccountaddress JSON-RPC command.
|
||||
func NewGetAccountAddressCmd(account string) *GetAccountAddressCmd {
|
||||
return &GetAccountAddressCmd{
|
||||
Account: account,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAddressesByAccountCmd defines the getaddressesbyaccount JSON-RPC command.
|
||||
type GetAddressesByAccountCmd struct {
|
||||
Account string
|
||||
}
|
||||
|
||||
// NewGetAddressesByAccountCmd returns a new instance which can be used to issue
|
||||
// a getaddressesbyaccount JSON-RPC command.
|
||||
func NewGetAddressesByAccountCmd(account string) *GetAddressesByAccountCmd {
|
||||
return &GetAddressesByAccountCmd{
|
||||
Account: account,
|
||||
}
|
||||
}
|
||||
|
||||
// GetBalanceCmd defines the getbalance JSON-RPC command.
|
||||
type GetBalanceCmd struct {
|
||||
Account *string
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
}
|
||||
|
||||
// NewGetBalanceCmd returns a new instance which can be used to issue a
|
||||
// getbalance JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetBalanceCmd(account *string, minConf *int) *GetBalanceCmd {
|
||||
return &GetBalanceCmd{
|
||||
Account: account,
|
||||
MinConf: minConf,
|
||||
}
|
||||
}
|
||||
|
||||
// GetNewAddressCmd defines the getnewaddress JSON-RPC command.
|
||||
type GetNewAddressCmd struct {
|
||||
Account *string
|
||||
}
|
||||
|
||||
// NewGetNewAddressCmd returns a new instance which can be used to issue a
|
||||
// getnewaddress JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetNewAddressCmd(account *string) *GetNewAddressCmd {
|
||||
return &GetNewAddressCmd{
|
||||
Account: account,
|
||||
}
|
||||
}
|
||||
|
||||
// GetRawChangeAddressCmd defines the getrawchangeaddress JSON-RPC command.
|
||||
type GetRawChangeAddressCmd struct {
|
||||
Account *string
|
||||
}
|
||||
|
||||
// NewGetRawChangeAddressCmd returns a new instance which can be used to issue a
|
||||
// getrawchangeaddress JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetRawChangeAddressCmd(account *string) *GetRawChangeAddressCmd {
|
||||
return &GetRawChangeAddressCmd{
|
||||
Account: account,
|
||||
}
|
||||
}
|
||||
|
||||
// GetReceivedByAccountCmd defines the getreceivedbyaccount JSON-RPC command.
|
||||
type GetReceivedByAccountCmd struct {
|
||||
Account string
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
}
|
||||
|
||||
// NewGetReceivedByAccountCmd returns a new instance which can be used to issue
|
||||
// a getreceivedbyaccount JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetReceivedByAccountCmd(account string, minConf *int) *GetReceivedByAccountCmd {
|
||||
return &GetReceivedByAccountCmd{
|
||||
Account: account,
|
||||
MinConf: minConf,
|
||||
}
|
||||
}
|
||||
|
||||
// GetReceivedByAddressCmd defines the getreceivedbyaddress JSON-RPC command.
|
||||
type GetReceivedByAddressCmd struct {
|
||||
Address string
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
}
|
||||
|
||||
// NewGetReceivedByAddressCmd returns a new instance which can be used to issue
|
||||
// a getreceivedbyaddress JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetReceivedByAddressCmd(address string, minConf *int) *GetReceivedByAddressCmd {
|
||||
return &GetReceivedByAddressCmd{
|
||||
Address: address,
|
||||
MinConf: minConf,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTransactionCmd defines the gettransaction JSON-RPC command.
|
||||
type GetTransactionCmd struct {
|
||||
Txid string
|
||||
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||
}
|
||||
|
||||
// NewGetTransactionCmd returns a new instance which can be used to issue a
|
||||
// gettransaction JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetTransactionCmd(txHash string, includeWatchOnly *bool) *GetTransactionCmd {
|
||||
return &GetTransactionCmd{
|
||||
Txid: txHash,
|
||||
IncludeWatchOnly: includeWatchOnly,
|
||||
}
|
||||
}
|
||||
|
||||
// GetWalletInfoCmd defines the getwalletinfo JSON-RPC command.
|
||||
type GetWalletInfoCmd struct{}
|
||||
|
||||
// NewGetWalletInfoCmd returns a new instance which can be used to issue a
|
||||
// getwalletinfo JSON-RPC command.
|
||||
func NewGetWalletInfoCmd() *GetWalletInfoCmd {
|
||||
return &GetWalletInfoCmd{}
|
||||
}
|
||||
|
||||
// ImportPrivKeyCmd defines the importprivkey JSON-RPC command.
|
||||
type ImportPrivKeyCmd struct {
|
||||
PrivKey string
|
||||
Label *string
|
||||
Rescan *bool `jsonrpcdefault:"true"`
|
||||
}
|
||||
|
||||
// NewImportPrivKeyCmd returns a new instance which can be used to issue a
|
||||
// importprivkey JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewImportPrivKeyCmd(privKey string, label *string, rescan *bool) *ImportPrivKeyCmd {
|
||||
return &ImportPrivKeyCmd{
|
||||
PrivKey: privKey,
|
||||
Label: label,
|
||||
Rescan: rescan,
|
||||
}
|
||||
}
|
||||
|
||||
// KeyPoolRefillCmd defines the keypoolrefill JSON-RPC command.
|
||||
type KeyPoolRefillCmd struct {
|
||||
NewSize *uint `jsonrpcdefault:"100"`
|
||||
}
|
||||
|
||||
// NewKeyPoolRefillCmd returns a new instance which can be used to issue a
|
||||
// keypoolrefill JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewKeyPoolRefillCmd(newSize *uint) *KeyPoolRefillCmd {
|
||||
return &KeyPoolRefillCmd{
|
||||
NewSize: newSize,
|
||||
}
|
||||
}
|
||||
|
||||
// ListAccountsCmd defines the listaccounts JSON-RPC command.
|
||||
type ListAccountsCmd struct {
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
}
|
||||
|
||||
// NewListAccountsCmd returns a new instance which can be used to issue a
|
||||
// listaccounts JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewListAccountsCmd(minConf *int) *ListAccountsCmd {
|
||||
return &ListAccountsCmd{
|
||||
MinConf: minConf,
|
||||
}
|
||||
}
|
||||
|
||||
// ListAddressGroupingsCmd defines the listaddressgroupings JSON-RPC command.
|
||||
type ListAddressGroupingsCmd struct{}
|
||||
|
||||
// NewListAddressGroupingsCmd returns a new instance which can be used to issue
|
||||
// a listaddressgroupoings JSON-RPC command.
|
||||
func NewListAddressGroupingsCmd() *ListAddressGroupingsCmd {
|
||||
return &ListAddressGroupingsCmd{}
|
||||
}
|
||||
|
||||
// ListLockUnspentCmd defines the listlockunspent JSON-RPC command.
|
||||
type ListLockUnspentCmd struct{}
|
||||
|
||||
// NewListLockUnspentCmd returns a new instance which can be used to issue a
|
||||
// listlockunspent JSON-RPC command.
|
||||
func NewListLockUnspentCmd() *ListLockUnspentCmd {
|
||||
return &ListLockUnspentCmd{}
|
||||
}
|
||||
|
||||
// ListReceivedByAccountCmd defines the listreceivedbyaccount JSON-RPC command.
|
||||
type ListReceivedByAccountCmd struct {
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
IncludeEmpty *bool `jsonrpcdefault:"false"`
|
||||
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||
}
|
||||
|
||||
// NewListReceivedByAccountCmd returns a new instance which can be used to issue
|
||||
// a listreceivedbyaccount JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewListReceivedByAccountCmd(minConf *int, includeEmpty, includeWatchOnly *bool) *ListReceivedByAccountCmd {
|
||||
return &ListReceivedByAccountCmd{
|
||||
MinConf: minConf,
|
||||
IncludeEmpty: includeEmpty,
|
||||
IncludeWatchOnly: includeWatchOnly,
|
||||
}
|
||||
}
|
||||
|
||||
// ListReceivedByAddressCmd defines the listreceivedbyaddress JSON-RPC command.
|
||||
type ListReceivedByAddressCmd struct {
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
IncludeEmpty *bool `jsonrpcdefault:"false"`
|
||||
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||
}
|
||||
|
||||
// NewListReceivedByAddressCmd returns a new instance which can be used to issue
|
||||
// a listreceivedbyaddress JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewListReceivedByAddressCmd(minConf *int, includeEmpty, includeWatchOnly *bool) *ListReceivedByAddressCmd {
|
||||
return &ListReceivedByAddressCmd{
|
||||
MinConf: minConf,
|
||||
IncludeEmpty: includeEmpty,
|
||||
IncludeWatchOnly: includeWatchOnly,
|
||||
}
|
||||
}
|
||||
|
||||
// ListSinceBlockCmd defines the listsinceblock JSON-RPC command.
|
||||
type ListSinceBlockCmd struct {
|
||||
BlockHash *string
|
||||
TargetConfirmations *int `jsonrpcdefault:"1"`
|
||||
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||
}
|
||||
|
||||
// NewListSinceBlockCmd returns a new instance which can be used to issue a
|
||||
// listsinceblock JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewListSinceBlockCmd(blockHash *string, targetConfirms *int, includeWatchOnly *bool) *ListSinceBlockCmd {
|
||||
return &ListSinceBlockCmd{
|
||||
BlockHash: blockHash,
|
||||
TargetConfirmations: targetConfirms,
|
||||
IncludeWatchOnly: includeWatchOnly,
|
||||
}
|
||||
}
|
||||
|
||||
// ListTransactionsCmd defines the listtransactions JSON-RPC command.
|
||||
type ListTransactionsCmd struct {
|
||||
Account *string
|
||||
Count *int `jsonrpcdefault:"10"`
|
||||
From *int `jsonrpcdefault:"0"`
|
||||
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||
}
|
||||
|
||||
// NewListTransactionsCmd returns a new instance which can be used to issue a
|
||||
// listtransactions JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewListTransactionsCmd(account *string, count, from *int, includeWatchOnly *bool) *ListTransactionsCmd {
|
||||
return &ListTransactionsCmd{
|
||||
Account: account,
|
||||
Count: count,
|
||||
From: from,
|
||||
IncludeWatchOnly: includeWatchOnly,
|
||||
}
|
||||
}
|
||||
|
||||
// ListUnspentCmd defines the listunspent JSON-RPC command.
|
||||
type ListUnspentCmd struct {
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
MaxConf *int `jsonrpcdefault:"9999999"`
|
||||
Addresses *[]string
|
||||
}
|
||||
|
||||
// NewListUnspentCmd returns a new instance which can be used to issue a
|
||||
// listunspent JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewListUnspentCmd(minConf, maxConf *int, addresses *[]string) *ListUnspentCmd {
|
||||
return &ListUnspentCmd{
|
||||
MinConf: minConf,
|
||||
MaxConf: maxConf,
|
||||
Addresses: addresses,
|
||||
}
|
||||
}
|
||||
|
||||
// LockUnspentCmd defines the lockunspent JSON-RPC command.
|
||||
type LockUnspentCmd struct {
|
||||
Unlock bool
|
||||
Transactions []TransactionInput
|
||||
}
|
||||
|
||||
// NewLockUnspentCmd returns a new instance which can be used to issue a
|
||||
// lockunspent JSON-RPC command.
|
||||
func NewLockUnspentCmd(unlock bool, transactions []TransactionInput) *LockUnspentCmd {
|
||||
return &LockUnspentCmd{
|
||||
Unlock: unlock,
|
||||
Transactions: transactions,
|
||||
}
|
||||
}
|
||||
|
||||
// MoveCmd defines the move JSON-RPC command.
|
||||
type MoveCmd struct {
|
||||
FromAccount string
|
||||
ToAccount string
|
||||
Amount float64 // In BTC
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
Comment *string
|
||||
}
|
||||
|
||||
// NewMoveCmd returns a new instance which can be used to issue a move JSON-RPC
|
||||
// command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewMoveCmd(fromAccount, toAccount string, amount float64, minConf *int, comment *string) *MoveCmd {
|
||||
return &MoveCmd{
|
||||
FromAccount: fromAccount,
|
||||
ToAccount: toAccount,
|
||||
Amount: amount,
|
||||
MinConf: minConf,
|
||||
Comment: comment,
|
||||
}
|
||||
}
|
||||
|
||||
// SendFromCmd defines the sendfrom JSON-RPC command.
|
||||
type SendFromCmd struct {
|
||||
FromAccount string
|
||||
ToAddress string
|
||||
Amount float64 // In BTC
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
Comment *string
|
||||
CommentTo *string
|
||||
}
|
||||
|
||||
// NewSendFromCmd returns a new instance which can be used to issue a sendfrom
|
||||
// JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewSendFromCmd(fromAccount, toAddress string, amount float64, minConf *int, comment, commentTo *string) *SendFromCmd {
|
||||
return &SendFromCmd{
|
||||
FromAccount: fromAccount,
|
||||
ToAddress: toAddress,
|
||||
Amount: amount,
|
||||
MinConf: minConf,
|
||||
Comment: comment,
|
||||
CommentTo: commentTo,
|
||||
}
|
||||
}
|
||||
|
||||
// SendManyCmd defines the sendmany JSON-RPC command.
|
||||
type SendManyCmd struct {
|
||||
FromAccount string
|
||||
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
Comment *string
|
||||
}
|
||||
|
||||
// NewSendManyCmd returns a new instance which can be used to issue a sendmany
|
||||
// JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewSendManyCmd(fromAccount string, amounts map[string]float64, minConf *int, comment *string) *SendManyCmd {
|
||||
return &SendManyCmd{
|
||||
FromAccount: fromAccount,
|
||||
Amounts: amounts,
|
||||
MinConf: minConf,
|
||||
Comment: comment,
|
||||
}
|
||||
}
|
||||
|
||||
// SendToAddressCmd defines the sendtoaddress JSON-RPC command.
|
||||
type SendToAddressCmd struct {
|
||||
Address string
|
||||
Amount float64
|
||||
Comment *string
|
||||
CommentTo *string
|
||||
}
|
||||
|
||||
// NewSendToAddressCmd returns a new instance which can be used to issue a
|
||||
// sendtoaddress JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewSendToAddressCmd(address string, amount float64, comment, commentTo *string) *SendToAddressCmd {
|
||||
return &SendToAddressCmd{
|
||||
Address: address,
|
||||
Amount: amount,
|
||||
Comment: comment,
|
||||
CommentTo: commentTo,
|
||||
}
|
||||
}
|
||||
|
||||
// SetAccountCmd defines the setaccount JSON-RPC command.
|
||||
type SetAccountCmd struct {
|
||||
Address string
|
||||
Account string
|
||||
}
|
||||
|
||||
// NewSetAccountCmd returns a new instance which can be used to issue a
|
||||
// setaccount JSON-RPC command.
|
||||
func NewSetAccountCmd(address, account string) *SetAccountCmd {
|
||||
return &SetAccountCmd{
|
||||
Address: address,
|
||||
Account: account,
|
||||
}
|
||||
}
|
||||
|
||||
// SetTxFeeCmd defines the settxfee JSON-RPC command.
|
||||
type SetTxFeeCmd struct {
|
||||
Amount float64 // In BTC
|
||||
}
|
||||
|
||||
// NewSetTxFeeCmd returns a new instance which can be used to issue a settxfee
|
||||
// JSON-RPC command.
|
||||
func NewSetTxFeeCmd(amount float64) *SetTxFeeCmd {
|
||||
return &SetTxFeeCmd{
|
||||
Amount: amount,
|
||||
}
|
||||
}
|
||||
|
||||
// SignMessageCmd defines the signmessage JSON-RPC command.
|
||||
type SignMessageCmd struct {
|
||||
Address string
|
||||
Message string
|
||||
}
|
||||
|
||||
// NewSignMessageCmd returns a new instance which can be used to issue a
|
||||
// signmessage JSON-RPC command.
|
||||
func NewSignMessageCmd(address, message string) *SignMessageCmd {
|
||||
return &SignMessageCmd{
|
||||
Address: address,
|
||||
Message: message,
|
||||
}
|
||||
}
|
||||
|
||||
// RawTxInput models the data needed for raw transaction input that is used in
|
||||
// the SignRawTransactionCmd struct.
|
||||
type RawTxInput struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
ScriptPubKey string `json:"scriptPubKey"`
|
||||
RedeemScript string `json:"redeemScript"`
|
||||
}
|
||||
|
||||
// SignRawTransactionCmd defines the signrawtransaction JSON-RPC command.
|
||||
type SignRawTransactionCmd struct {
|
||||
RawTx string
|
||||
Inputs *[]RawTxInput
|
||||
PrivKeys *[]string
|
||||
Flags *string `jsonrpcdefault:"\"ALL\""`
|
||||
}
|
||||
|
||||
// NewSignRawTransactionCmd returns a new instance which can be used to issue a
|
||||
// signrawtransaction JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewSignRawTransactionCmd(hexEncodedTx string, inputs *[]RawTxInput, privKeys *[]string, flags *string) *SignRawTransactionCmd {
|
||||
return &SignRawTransactionCmd{
|
||||
RawTx: hexEncodedTx,
|
||||
Inputs: inputs,
|
||||
PrivKeys: privKeys,
|
||||
Flags: flags,
|
||||
}
|
||||
}
|
||||
|
||||
// WalletLockCmd defines the walletlock JSON-RPC command.
|
||||
type WalletLockCmd struct{}
|
||||
|
||||
// NewWalletLockCmd returns a new instance which can be used to issue a
|
||||
// walletlock JSON-RPC command.
|
||||
func NewWalletLockCmd() *WalletLockCmd {
|
||||
return &WalletLockCmd{}
|
||||
}
|
||||
|
||||
// WalletPassphraseCmd defines the walletpassphrase JSON-RPC command.
|
||||
type WalletPassphraseCmd struct {
|
||||
Passphrase string
|
||||
Timeout int64
|
||||
}
|
||||
|
||||
// NewWalletPassphraseCmd returns a new instance which can be used to issue a
|
||||
// walletpassphrase JSON-RPC command.
|
||||
func NewWalletPassphraseCmd(passphrase string, timeout int64) *WalletPassphraseCmd {
|
||||
return &WalletPassphraseCmd{
|
||||
Passphrase: passphrase,
|
||||
Timeout: timeout,
|
||||
}
|
||||
}
|
||||
|
||||
// WalletPassphraseChangeCmd defines the walletpassphrase JSON-RPC command.
|
||||
type WalletPassphraseChangeCmd struct {
|
||||
OldPassphrase string
|
||||
NewPassphrase string
|
||||
}
|
||||
|
||||
// NewWalletPassphraseChangeCmd returns a new instance which can be used to
|
||||
// issue a walletpassphrasechange JSON-RPC command.
|
||||
func NewWalletPassphraseChangeCmd(oldPassphrase, newPassphrase string) *WalletPassphraseChangeCmd {
|
||||
return &WalletPassphraseChangeCmd{
|
||||
OldPassphrase: oldPassphrase,
|
||||
NewPassphrase: newPassphrase,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// The commands in this file are only usable with a wallet server.
|
||||
flags := UFWalletOnly
|
||||
|
||||
MustRegisterCmd("addmultisigaddress", (*AddMultisigAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("addwitnessaddress", (*AddWitnessAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("createmultisig", (*CreateMultisigCmd)(nil), flags)
|
||||
MustRegisterCmd("dumpprivkey", (*DumpPrivKeyCmd)(nil), flags)
|
||||
MustRegisterCmd("encryptwallet", (*EncryptWalletCmd)(nil), flags)
|
||||
MustRegisterCmd("estimatefee", (*EstimateFeeCmd)(nil), flags)
|
||||
MustRegisterCmd("estimatepriority", (*EstimatePriorityCmd)(nil), flags)
|
||||
MustRegisterCmd("getaccount", (*GetAccountCmd)(nil), flags)
|
||||
MustRegisterCmd("getaccountaddress", (*GetAccountAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("getaddressesbyaccount", (*GetAddressesByAccountCmd)(nil), flags)
|
||||
MustRegisterCmd("getbalance", (*GetBalanceCmd)(nil), flags)
|
||||
MustRegisterCmd("getnewaddress", (*GetNewAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("getrawchangeaddress", (*GetRawChangeAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("getreceivedbyaccount", (*GetReceivedByAccountCmd)(nil), flags)
|
||||
MustRegisterCmd("getreceivedbyaddress", (*GetReceivedByAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("gettransaction", (*GetTransactionCmd)(nil), flags)
|
||||
MustRegisterCmd("getwalletinfo", (*GetWalletInfoCmd)(nil), flags)
|
||||
MustRegisterCmd("importprivkey", (*ImportPrivKeyCmd)(nil), flags)
|
||||
MustRegisterCmd("keypoolrefill", (*KeyPoolRefillCmd)(nil), flags)
|
||||
MustRegisterCmd("listaccounts", (*ListAccountsCmd)(nil), flags)
|
||||
MustRegisterCmd("listaddressgroupings", (*ListAddressGroupingsCmd)(nil), flags)
|
||||
MustRegisterCmd("listlockunspent", (*ListLockUnspentCmd)(nil), flags)
|
||||
MustRegisterCmd("listreceivedbyaccount", (*ListReceivedByAccountCmd)(nil), flags)
|
||||
MustRegisterCmd("listreceivedbyaddress", (*ListReceivedByAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("listsinceblock", (*ListSinceBlockCmd)(nil), flags)
|
||||
MustRegisterCmd("listtransactions", (*ListTransactionsCmd)(nil), flags)
|
||||
MustRegisterCmd("listunspent", (*ListUnspentCmd)(nil), flags)
|
||||
MustRegisterCmd("lockunspent", (*LockUnspentCmd)(nil), flags)
|
||||
MustRegisterCmd("move", (*MoveCmd)(nil), flags)
|
||||
MustRegisterCmd("sendfrom", (*SendFromCmd)(nil), flags)
|
||||
MustRegisterCmd("sendmany", (*SendManyCmd)(nil), flags)
|
||||
MustRegisterCmd("sendtoaddress", (*SendToAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("setaccount", (*SetAccountCmd)(nil), flags)
|
||||
MustRegisterCmd("settxfee", (*SetTxFeeCmd)(nil), flags)
|
||||
MustRegisterCmd("signmessage", (*SignMessageCmd)(nil), flags)
|
||||
MustRegisterCmd("signrawtransaction", (*SignRawTransactionCmd)(nil), flags)
|
||||
MustRegisterCmd("walletlock", (*WalletLockCmd)(nil), flags)
|
||||
MustRegisterCmd("walletpassphrase", (*WalletPassphraseCmd)(nil), flags)
|
||||
MustRegisterCmd("walletpassphrasechange", (*WalletPassphraseChangeCmd)(nil), flags)
|
||||
}
|
||||
+1274
File diff suppressed because it is too large
Load Diff
+161
@@ -0,0 +1,161 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
// GetTransactionDetailsResult models the details data from the gettransaction command.
|
||||
//
|
||||
// This models the "short" version of the ListTransactionsResult type, which
|
||||
// excludes fields common to the transaction. These common fields are instead
|
||||
// part of the GetTransactionResult.
|
||||
type GetTransactionDetailsResult struct {
|
||||
Account string `json:"account"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Amount float64 `json:"amount"`
|
||||
Category string `json:"category"`
|
||||
InvolvesWatchOnly bool `json:"involveswatchonly,omitempty"`
|
||||
Fee *float64 `json:"fee,omitempty"`
|
||||
Vout uint32 `json:"vout"`
|
||||
}
|
||||
|
||||
// GetTransactionResult models the data from the gettransaction command.
|
||||
type GetTransactionResult struct {
|
||||
Amount float64 `json:"amount"`
|
||||
Fee float64 `json:"fee,omitempty"`
|
||||
Confirmations int64 `json:"confirmations"`
|
||||
BlockHash string `json:"blockhash"`
|
||||
BlockIndex int64 `json:"blockindex"`
|
||||
BlockTime int64 `json:"blocktime"`
|
||||
TxID string `json:"txid"`
|
||||
WalletConflicts []string `json:"walletconflicts"`
|
||||
Time int64 `json:"time"`
|
||||
TimeReceived int64 `json:"timereceived"`
|
||||
Details []GetTransactionDetailsResult `json:"details"`
|
||||
Hex string `json:"hex"`
|
||||
}
|
||||
|
||||
// InfoWalletResult models the data returned by the wallet server getinfo
|
||||
// command.
|
||||
type InfoWalletResult struct {
|
||||
Version int32 `json:"version"`
|
||||
ProtocolVersion int32 `json:"protocolversion"`
|
||||
WalletVersion int32 `json:"walletversion"`
|
||||
Balance float64 `json:"balance"`
|
||||
Blocks int32 `json:"blocks"`
|
||||
TimeOffset int64 `json:"timeoffset"`
|
||||
Connections int32 `json:"connections"`
|
||||
Proxy string `json:"proxy"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
TestNet bool `json:"testnet"`
|
||||
KeypoolOldest int64 `json:"keypoololdest"`
|
||||
KeypoolSize int32 `json:"keypoolsize"`
|
||||
UnlockedUntil int64 `json:"unlocked_until"`
|
||||
PaytxFee float64 `json:"paytxfee"`
|
||||
RelayFee float64 `json:"relayfee"`
|
||||
Errors string `json:"errors"`
|
||||
}
|
||||
|
||||
// ListTransactionsResult models the data from the listtransactions command.
|
||||
type ListTransactionsResult struct {
|
||||
Abandoned bool `json:"abandoned"`
|
||||
Account string `json:"account"`
|
||||
Address string `json:"address,omitempty"`
|
||||
Amount float64 `json:"amount"`
|
||||
BIP125Replaceable string `json:"bip125-replaceable,omitempty"`
|
||||
BlockHash string `json:"blockhash,omitempty"`
|
||||
BlockIndex *int64 `json:"blockindex,omitempty"`
|
||||
BlockTime int64 `json:"blocktime,omitempty"`
|
||||
Category string `json:"category"`
|
||||
Confirmations int64 `json:"confirmations"`
|
||||
Fee *float64 `json:"fee,omitempty"`
|
||||
Generated bool `json:"generated,omitempty"`
|
||||
InvolvesWatchOnly bool `json:"involveswatchonly,omitempty"`
|
||||
Time int64 `json:"time"`
|
||||
TimeReceived int64 `json:"timereceived"`
|
||||
Trusted bool `json:"trusted"`
|
||||
TxID string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
WalletConflicts []string `json:"walletconflicts"`
|
||||
Comment string `json:"comment,omitempty"`
|
||||
OtherAccount string `json:"otheraccount,omitempty"`
|
||||
}
|
||||
|
||||
// ListReceivedByAccountResult models the data from the listreceivedbyaccount
|
||||
// command.
|
||||
type ListReceivedByAccountResult struct {
|
||||
Account string `json:"account"`
|
||||
Amount float64 `json:"amount"`
|
||||
Confirmations uint64 `json:"confirmations"`
|
||||
}
|
||||
|
||||
// ListReceivedByAddressResult models the data from the listreceivedbyaddress
|
||||
// command.
|
||||
type ListReceivedByAddressResult struct {
|
||||
Account string `json:"account"`
|
||||
Address string `json:"address"`
|
||||
Amount float64 `json:"amount"`
|
||||
Confirmations uint64 `json:"confirmations"`
|
||||
TxIDs []string `json:"txids,omitempty"`
|
||||
InvolvesWatchonly bool `json:"involvesWatchonly,omitempty"`
|
||||
}
|
||||
|
||||
// ListSinceBlockResult models the data from the listsinceblock command.
|
||||
type ListSinceBlockResult struct {
|
||||
Transactions []ListTransactionsResult `json:"transactions"`
|
||||
LastBlock string `json:"lastblock"`
|
||||
}
|
||||
|
||||
// ListUnspentResult models a successful response from the listunspent request.
|
||||
type ListUnspentResult struct {
|
||||
TxID string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
Address string `json:"address"`
|
||||
Account string `json:"account"`
|
||||
ScriptPubKey string `json:"scriptPubKey"`
|
||||
RedeemScript string `json:"redeemScript,omitempty"`
|
||||
Amount float64 `json:"amount"`
|
||||
Confirmations int64 `json:"confirmations"`
|
||||
Spendable bool `json:"spendable"`
|
||||
}
|
||||
|
||||
// SignRawTransactionError models the data that contains script verification
|
||||
// errors from the signrawtransaction request.
|
||||
type SignRawTransactionError struct {
|
||||
TxID string `json:"txid"`
|
||||
Vout uint32 `json:"vout"`
|
||||
ScriptSig string `json:"scriptSig"`
|
||||
Sequence uint32 `json:"sequence"`
|
||||
Error string `json:"error"`
|
||||
}
|
||||
|
||||
// SignRawTransactionResult models the data from the signrawtransaction
|
||||
// command.
|
||||
type SignRawTransactionResult struct {
|
||||
Hex string `json:"hex"`
|
||||
Complete bool `json:"complete"`
|
||||
Errors []SignRawTransactionError `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
// ValidateAddressWalletResult models the data returned by the wallet server
|
||||
// validateaddress command.
|
||||
type ValidateAddressWalletResult struct {
|
||||
IsValid bool `json:"isvalid"`
|
||||
Address string `json:"address,omitempty"`
|
||||
IsMine bool `json:"ismine,omitempty"`
|
||||
IsWatchOnly bool `json:"iswatchonly,omitempty"`
|
||||
IsScript bool `json:"isscript,omitempty"`
|
||||
PubKey string `json:"pubkey,omitempty"`
|
||||
IsCompressed bool `json:"iscompressed,omitempty"`
|
||||
Account string `json:"account,omitempty"`
|
||||
Addresses []string `json:"addresses,omitempty"`
|
||||
Hex string `json:"hex,omitempty"`
|
||||
Script string `json:"script,omitempty"`
|
||||
SigsRequired int32 `json:"sigsrequired,omitempty"`
|
||||
}
|
||||
|
||||
// GetBestBlockResult models the data from the getbestblock command.
|
||||
type GetBestBlockResult struct {
|
||||
Hash string `json:"hash"`
|
||||
Height int32 `json:"height"`
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson
|
||||
|
||||
// NOTE: This file is intended to house the RPC commands that are supported by
|
||||
// a wallet server, but are only available via websockets.
|
||||
|
||||
// CreateEncryptedWalletCmd defines the createencryptedwallet JSON-RPC command.
|
||||
type CreateEncryptedWalletCmd struct {
|
||||
Passphrase string
|
||||
}
|
||||
|
||||
// NewCreateEncryptedWalletCmd returns a new instance which can be used to issue
|
||||
// a createencryptedwallet JSON-RPC command.
|
||||
func NewCreateEncryptedWalletCmd(passphrase string) *CreateEncryptedWalletCmd {
|
||||
return &CreateEncryptedWalletCmd{
|
||||
Passphrase: passphrase,
|
||||
}
|
||||
}
|
||||
|
||||
// ExportWatchingWalletCmd defines the exportwatchingwallet JSON-RPC command.
|
||||
type ExportWatchingWalletCmd struct {
|
||||
Account *string
|
||||
Download *bool `jsonrpcdefault:"false"`
|
||||
}
|
||||
|
||||
// NewExportWatchingWalletCmd returns a new instance which can be used to issue
|
||||
// a exportwatchingwallet JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewExportWatchingWalletCmd(account *string, download *bool) *ExportWatchingWalletCmd {
|
||||
return &ExportWatchingWalletCmd{
|
||||
Account: account,
|
||||
Download: download,
|
||||
}
|
||||
}
|
||||
|
||||
// GetUnconfirmedBalanceCmd defines the getunconfirmedbalance JSON-RPC command.
|
||||
type GetUnconfirmedBalanceCmd struct {
|
||||
Account *string
|
||||
}
|
||||
|
||||
// NewGetUnconfirmedBalanceCmd returns a new instance which can be used to issue
|
||||
// a getunconfirmedbalance JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetUnconfirmedBalanceCmd(account *string) *GetUnconfirmedBalanceCmd {
|
||||
return &GetUnconfirmedBalanceCmd{
|
||||
Account: account,
|
||||
}
|
||||
}
|
||||
|
||||
// ListAddressTransactionsCmd defines the listaddresstransactions JSON-RPC
|
||||
// command.
|
||||
type ListAddressTransactionsCmd struct {
|
||||
Addresses []string
|
||||
Account *string
|
||||
}
|
||||
|
||||
// NewListAddressTransactionsCmd returns a new instance which can be used to
|
||||
// issue a listaddresstransactions JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewListAddressTransactionsCmd(addresses []string, account *string) *ListAddressTransactionsCmd {
|
||||
return &ListAddressTransactionsCmd{
|
||||
Addresses: addresses,
|
||||
Account: account,
|
||||
}
|
||||
}
|
||||
|
||||
// ListAllTransactionsCmd defines the listalltransactions JSON-RPC command.
|
||||
type ListAllTransactionsCmd struct {
|
||||
Account *string
|
||||
}
|
||||
|
||||
// NewListAllTransactionsCmd returns a new instance which can be used to issue a
|
||||
// listalltransactions JSON-RPC command.
|
||||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewListAllTransactionsCmd(account *string) *ListAllTransactionsCmd {
|
||||
return &ListAllTransactionsCmd{
|
||||
Account: account,
|
||||
}
|
||||
}
|
||||
|
||||
// RecoverAddressesCmd defines the recoveraddresses JSON-RPC command.
|
||||
type RecoverAddressesCmd struct {
|
||||
Account string
|
||||
N int
|
||||
}
|
||||
|
||||
// NewRecoverAddressesCmd returns a new instance which can be used to issue a
|
||||
// recoveraddresses JSON-RPC command.
|
||||
func NewRecoverAddressesCmd(account string, n int) *RecoverAddressesCmd {
|
||||
return &RecoverAddressesCmd{
|
||||
Account: account,
|
||||
N: n,
|
||||
}
|
||||
}
|
||||
|
||||
// WalletIsLockedCmd defines the walletislocked JSON-RPC command.
|
||||
type WalletIsLockedCmd struct{}
|
||||
|
||||
// NewWalletIsLockedCmd returns a new instance which can be used to issue a
|
||||
// walletislocked JSON-RPC command.
|
||||
func NewWalletIsLockedCmd() *WalletIsLockedCmd {
|
||||
return &WalletIsLockedCmd{}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// The commands in this file are only usable with a wallet server via
|
||||
// websockets.
|
||||
flags := UFWalletOnly | UFWebsocketOnly
|
||||
|
||||
MustRegisterCmd("createencryptedwallet", (*CreateEncryptedWalletCmd)(nil), flags)
|
||||
MustRegisterCmd("exportwatchingwallet", (*ExportWatchingWalletCmd)(nil), flags)
|
||||
MustRegisterCmd("getunconfirmedbalance", (*GetUnconfirmedBalanceCmd)(nil), flags)
|
||||
MustRegisterCmd("listaddresstransactions", (*ListAddressTransactionsCmd)(nil), flags)
|
||||
MustRegisterCmd("listalltransactions", (*ListAllTransactionsCmd)(nil), flags)
|
||||
MustRegisterCmd("recoveraddresses", (*RecoverAddressesCmd)(nil), flags)
|
||||
MustRegisterCmd("walletislocked", (*WalletIsLockedCmd)(nil), flags)
|
||||
}
|
||||
+259
@@ -0,0 +1,259 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestWalletSvrWsCmds tests all of the wallet server websocket-specific
|
||||
// commands marshal and unmarshal into valid results include handling of
|
||||
// optional fields being omitted in the marshalled command, while optional
|
||||
// fields with defaults have the default assigned on unmarshalled commands.
|
||||
func TestWalletSvrWsCmds(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
testID := int(1)
|
||||
tests := []struct {
|
||||
name string
|
||||
newCmd func() (interface{}, error)
|
||||
staticCmd func() interface{}
|
||||
marshalled string
|
||||
unmarshalled interface{}
|
||||
}{
|
||||
{
|
||||
name: "createencryptedwallet",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("createencryptedwallet", "pass")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewCreateEncryptedWalletCmd("pass")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"createencryptedwallet","params":["pass"],"id":1}`,
|
||||
unmarshalled: &btcjson.CreateEncryptedWalletCmd{Passphrase: "pass"},
|
||||
},
|
||||
{
|
||||
name: "exportwatchingwallet",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("exportwatchingwallet")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewExportWatchingWalletCmd(nil, nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"exportwatchingwallet","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.ExportWatchingWalletCmd{
|
||||
Account: nil,
|
||||
Download: btcjson.Bool(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exportwatchingwallet optional1",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("exportwatchingwallet", "acct")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewExportWatchingWalletCmd(btcjson.String("acct"), nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"exportwatchingwallet","params":["acct"],"id":1}`,
|
||||
unmarshalled: &btcjson.ExportWatchingWalletCmd{
|
||||
Account: btcjson.String("acct"),
|
||||
Download: btcjson.Bool(false),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "exportwatchingwallet optional2",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("exportwatchingwallet", "acct", true)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewExportWatchingWalletCmd(btcjson.String("acct"),
|
||||
btcjson.Bool(true))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"exportwatchingwallet","params":["acct",true],"id":1}`,
|
||||
unmarshalled: &btcjson.ExportWatchingWalletCmd{
|
||||
Account: btcjson.String("acct"),
|
||||
Download: btcjson.Bool(true),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "getunconfirmedbalance",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("getunconfirmedbalance")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGetUnconfirmedBalanceCmd(nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getunconfirmedbalance","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.GetUnconfirmedBalanceCmd{
|
||||
Account: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "getunconfirmedbalance optional1",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("getunconfirmedbalance", "acct")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGetUnconfirmedBalanceCmd(btcjson.String("acct"))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getunconfirmedbalance","params":["acct"],"id":1}`,
|
||||
unmarshalled: &btcjson.GetUnconfirmedBalanceCmd{
|
||||
Account: btcjson.String("acct"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "listaddresstransactions",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("listaddresstransactions", `["1Address"]`)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewListAddressTransactionsCmd([]string{"1Address"}, nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["1Address"]],"id":1}`,
|
||||
unmarshalled: &btcjson.ListAddressTransactionsCmd{
|
||||
Addresses: []string{"1Address"},
|
||||
Account: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "listaddresstransactions optional1",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("listaddresstransactions", `["1Address"]`, "acct")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewListAddressTransactionsCmd([]string{"1Address"},
|
||||
btcjson.String("acct"))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["1Address"],"acct"],"id":1}`,
|
||||
unmarshalled: &btcjson.ListAddressTransactionsCmd{
|
||||
Addresses: []string{"1Address"},
|
||||
Account: btcjson.String("acct"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "listalltransactions",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("listalltransactions")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewListAllTransactionsCmd(nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"listalltransactions","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.ListAllTransactionsCmd{
|
||||
Account: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "listalltransactions optional",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("listalltransactions", "acct")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewListAllTransactionsCmd(btcjson.String("acct"))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"listalltransactions","params":["acct"],"id":1}`,
|
||||
unmarshalled: &btcjson.ListAllTransactionsCmd{
|
||||
Account: btcjson.String("acct"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "recoveraddresses",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("recoveraddresses", "acct", 10)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewRecoverAddressesCmd("acct", 10)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"recoveraddresses","params":["acct",10],"id":1}`,
|
||||
unmarshalled: &btcjson.RecoverAddressesCmd{
|
||||
Account: "acct",
|
||||
N: 10,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "walletislocked",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("walletislocked")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewWalletIsLockedCmd()
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"walletislocked","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.WalletIsLockedCmd{},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Marshal the command as created by the new static command
|
||||
// creation function.
|
||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.marshalled)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the command is created without error via the generic
|
||||
// new command creation function.
|
||||
cmd, err := test.newCmd()
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||
i, test.name, err)
|
||||
}
|
||||
|
||||
// Marshal the command as created by the generic new command
|
||||
// creation function.
|
||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.marshalled)
|
||||
continue
|
||||
}
|
||||
|
||||
var request btcjson.Request
|
||||
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||
"unmarshalling JSON-RPC request: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||
if err != nil {
|
||||
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||
"- got %s, want %s", i, test.name,
|
||||
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// NOTE: This file is intended to house the RPC websocket notifications that are
|
||||
// supported by a wallet server.
|
||||
|
||||
package btcjson
|
||||
|
||||
const (
|
||||
// AccountBalanceNtfnMethod is the method used for account balance
|
||||
// notifications.
|
||||
AccountBalanceNtfnMethod = "accountbalance"
|
||||
|
||||
// BtcdConnectedNtfnMethod is the method used for notifications when
|
||||
// a wallet server is connected to a chain server.
|
||||
BtcdConnectedNtfnMethod = "btcdconnected"
|
||||
|
||||
// WalletLockStateNtfnMethod is the method used to notify the lock state
|
||||
// of a wallet has changed.
|
||||
WalletLockStateNtfnMethod = "walletlockstate"
|
||||
|
||||
// NewTxNtfnMethod is the method used to notify that a wallet server has
|
||||
// added a new transaction to the transaciton store.
|
||||
NewTxNtfnMethod = "newtx"
|
||||
)
|
||||
|
||||
// AccountBalanceNtfn defines the accountbalance JSON-RPC notification.
|
||||
type AccountBalanceNtfn struct {
|
||||
Account string
|
||||
Balance float64 // In BTC
|
||||
Confirmed bool // Whether Balance is confirmed or unconfirmed.
|
||||
}
|
||||
|
||||
// NewAccountBalanceNtfn returns a new instance which can be used to issue an
|
||||
// accountbalance JSON-RPC notification.
|
||||
func NewAccountBalanceNtfn(account string, balance float64, confirmed bool) *AccountBalanceNtfn {
|
||||
return &AccountBalanceNtfn{
|
||||
Account: account,
|
||||
Balance: balance,
|
||||
Confirmed: confirmed,
|
||||
}
|
||||
}
|
||||
|
||||
// BtcdConnectedNtfn defines the btcdconnected JSON-RPC notification.
|
||||
type BtcdConnectedNtfn struct {
|
||||
Connected bool
|
||||
}
|
||||
|
||||
// NewBtcdConnectedNtfn returns a new instance which can be used to issue a
|
||||
// btcdconnected JSON-RPC notification.
|
||||
func NewBtcdConnectedNtfn(connected bool) *BtcdConnectedNtfn {
|
||||
return &BtcdConnectedNtfn{
|
||||
Connected: connected,
|
||||
}
|
||||
}
|
||||
|
||||
// WalletLockStateNtfn defines the walletlockstate JSON-RPC notification.
|
||||
type WalletLockStateNtfn struct {
|
||||
Locked bool
|
||||
}
|
||||
|
||||
// NewWalletLockStateNtfn returns a new instance which can be used to issue a
|
||||
// walletlockstate JSON-RPC notification.
|
||||
func NewWalletLockStateNtfn(locked bool) *WalletLockStateNtfn {
|
||||
return &WalletLockStateNtfn{
|
||||
Locked: locked,
|
||||
}
|
||||
}
|
||||
|
||||
// NewTxNtfn defines the newtx JSON-RPC notification.
|
||||
type NewTxNtfn struct {
|
||||
Account string
|
||||
Details ListTransactionsResult
|
||||
}
|
||||
|
||||
// NewNewTxNtfn returns a new instance which can be used to issue a newtx
|
||||
// JSON-RPC notification.
|
||||
func NewNewTxNtfn(account string, details ListTransactionsResult) *NewTxNtfn {
|
||||
return &NewTxNtfn{
|
||||
Account: account,
|
||||
Details: details,
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
// The commands in this file are only usable with a wallet server via
|
||||
// websockets and are notifications.
|
||||
flags := UFWalletOnly | UFWebsocketOnly | UFNotification
|
||||
|
||||
MustRegisterCmd(AccountBalanceNtfnMethod, (*AccountBalanceNtfn)(nil), flags)
|
||||
MustRegisterCmd(BtcdConnectedNtfnMethod, (*BtcdConnectedNtfn)(nil), flags)
|
||||
MustRegisterCmd(WalletLockStateNtfnMethod, (*WalletLockStateNtfn)(nil), flags)
|
||||
MustRegisterCmd(NewTxNtfnMethod, (*NewTxNtfn)(nil), flags)
|
||||
}
|
||||
+187
@@ -0,0 +1,187 @@
|
||||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
)
|
||||
|
||||
// TestWalletSvrWsNtfns tests all of the chain server websocket-specific
|
||||
// notifications marshal and unmarshal into valid results include handling of
|
||||
// optional fields being omitted in the marshalled command, while optional
|
||||
// fields with defaults have the default assigned on unmarshalled commands.
|
||||
func TestWalletSvrWsNtfns(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
newNtfn func() (interface{}, error)
|
||||
staticNtfn func() interface{}
|
||||
marshalled string
|
||||
unmarshalled interface{}
|
||||
}{
|
||||
{
|
||||
name: "accountbalance",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("accountbalance", "acct", 1.25, true)
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
return btcjson.NewAccountBalanceNtfn("acct", 1.25, true)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"accountbalance","params":["acct",1.25,true],"id":null}`,
|
||||
unmarshalled: &btcjson.AccountBalanceNtfn{
|
||||
Account: "acct",
|
||||
Balance: 1.25,
|
||||
Confirmed: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "btcdconnected",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("btcdconnected", true)
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
return btcjson.NewBtcdConnectedNtfn(true)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"btcdconnected","params":[true],"id":null}`,
|
||||
unmarshalled: &btcjson.BtcdConnectedNtfn{
|
||||
Connected: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "walletlockstate",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("walletlockstate", true)
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
return btcjson.NewWalletLockStateNtfn(true)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"walletlockstate","params":[true],"id":null}`,
|
||||
unmarshalled: &btcjson.WalletLockStateNtfn{
|
||||
Locked: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "newtx",
|
||||
newNtfn: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("newtx", "acct", `{"account":"acct","address":"1Address","category":"send","amount":1.5,"bip125-replaceable":"unknown","fee":0.0001,"confirmations":1,"trusted":true,"txid":"456","walletconflicts":[],"time":12345678,"timereceived":12345876,"vout":789,"otheraccount":"otheracct"}`)
|
||||
},
|
||||
staticNtfn: func() interface{} {
|
||||
result := btcjson.ListTransactionsResult{
|
||||
Abandoned: false,
|
||||
Account: "acct",
|
||||
Address: "1Address",
|
||||
BIP125Replaceable: "unknown",
|
||||
Category: "send",
|
||||
Amount: 1.5,
|
||||
Fee: btcjson.Float64(0.0001),
|
||||
Confirmations: 1,
|
||||
TxID: "456",
|
||||
WalletConflicts: []string{},
|
||||
Time: 12345678,
|
||||
TimeReceived: 12345876,
|
||||
Trusted: true,
|
||||
Vout: 789,
|
||||
OtherAccount: "otheracct",
|
||||
}
|
||||
return btcjson.NewNewTxNtfn("acct", result)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"newtx","params":["acct",{"abandoned":false,"account":"acct","address":"1Address","amount":1.5,"bip125-replaceable":"unknown","category":"send","confirmations":1,"fee":0.0001,"time":12345678,"timereceived":12345876,"trusted":true,"txid":"456","vout":789,"walletconflicts":[],"otheraccount":"otheracct"}],"id":null}`,
|
||||
unmarshalled: &btcjson.NewTxNtfn{
|
||||
Account: "acct",
|
||||
Details: btcjson.ListTransactionsResult{
|
||||
Abandoned: false,
|
||||
Account: "acct",
|
||||
Address: "1Address",
|
||||
BIP125Replaceable: "unknown",
|
||||
Category: "send",
|
||||
Amount: 1.5,
|
||||
Fee: btcjson.Float64(0.0001),
|
||||
Confirmations: 1,
|
||||
TxID: "456",
|
||||
WalletConflicts: []string{},
|
||||
Time: 12345678,
|
||||
TimeReceived: 12345876,
|
||||
Trusted: true,
|
||||
Vout: 789,
|
||||
OtherAccount: "otheracct",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Marshal the notification as created by the new static
|
||||
// creation function. The ID is nil for notifications.
|
||||
marshalled, err := btcjson.MarshalCmd(nil, test.staticNtfn())
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.marshalled)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the notification is created without error via the
|
||||
// generic new notification creation function.
|
||||
cmd, err := test.newNtfn()
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ",
|
||||
i, test.name, err)
|
||||
}
|
||||
|
||||
// Marshal the notification as created by the generic new
|
||||
// notification creation function. The ID is nil for
|
||||
// notifications.
|
||||
marshalled, err = btcjson.MarshalCmd(nil, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !bytes.Equal(marshalled, []byte(test.marshalled)) {
|
||||
t.Errorf("Test #%d (%s) unexpected marshalled data - "+
|
||||
"got %s, want %s", i, test.name, marshalled,
|
||||
test.marshalled)
|
||||
continue
|
||||
}
|
||||
|
||||
var request btcjson.Request
|
||||
if err := json.Unmarshal(marshalled, &request); err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error while "+
|
||||
"unmarshalling JSON-RPC request: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
cmd, err = btcjson.UnmarshalCmd(&request)
|
||||
if err != nil {
|
||||
t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(cmd, test.unmarshalled) {
|
||||
t.Errorf("Test #%d (%s) unexpected unmarshalled command "+
|
||||
"- got %s, want %s", i, test.name,
|
||||
fmt.Sprintf("(%T) %+[1]v", cmd),
|
||||
fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user