├── CONTRIBUTORS ├── LICENSE ├── README.md ├── btcjson ├── CONTRIBUTORS ├── README.md ├── btcdextcmds.go ├── btcdextcmds_test.go ├── btcdextresults.go ├── btcdextresults_test.go ├── btcwalletextcmds.go ├── btcwalletextcmds_test.go ├── chainsvrcmds.go ├── chainsvrcmds_test.go ├── chainsvrresults.go ├── chainsvrresults_test.go ├── cmdinfo.go ├── cmdinfo_test.go ├── cmdparse.go ├── cmdparse_test.go ├── doc.go ├── error.go ├── error_test.go ├── example_test.go ├── export_test.go ├── help.go ├── help_test.go ├── helpers.go ├── helpers_test.go ├── jsonrpc.go ├── jsonrpc_test.go ├── jsonrpcerr.go ├── register.go ├── register_test.go ├── walletsvrcmds.go ├── walletsvrcmds_test.go └── walletsvrresults.go ├── chain.go ├── doc.go ├── examples ├── README.md └── main.go ├── infrastructure.go ├── log.go ├── mining.go ├── net.go ├── rawrequest.go ├── rawtransactions.go └── wallet.go /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | # This is the list of people who have contributed code to the repository. 2 | # 3 | # Names should be added to this file only after verifying that the individual 4 | # or the individual's organization has agreed to the LICENSE. 5 | # 6 | # Names should be added to this file like so: 7 | # Name 8 | 9 | Dave Collins 10 | Geert-Johan Riemer 11 | Josh Rickmar 12 | Michalis Kargakis 13 | Ruben de Vries 8 | 9 | John C. Vernaleo 10 | Dave Collins 11 | Owain G. Ainsworth 12 | David Hill 13 | Josh Rickmar 14 | Andreas Metsälä 15 | Francis Lam 16 | Geert-Johan Riemer 17 | -------------------------------------------------------------------------------- /btcjson/README.md: -------------------------------------------------------------------------------- 1 | btcjson 2 | ======= 3 | 4 | [![Build Status](https://travis-ci.org/btcsuite/btcd.png?branch=master)](https://travis-ci.org/btcsuite/btcd) 5 | [![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) 6 | [![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/btcsuite/btcd/btcjson) 7 | 8 | Package btcjson implements concrete types for marshalling to and from the 9 | bitcoin JSON-RPC API. A comprehensive suite of tests is provided to ensure 10 | proper functionality. 11 | 12 | Although this package was primarily written for the btcsuite, it has 13 | intentionally been designed so it can be used as a standalone package for any 14 | projects needing to marshal to and from bitcoin JSON-RPC requests and responses. 15 | 16 | Note that although it's possible to use this package directly to implement an 17 | RPC client, it is not recommended since it is only intended as an infrastructure 18 | package. Instead, RPC clients should use the 19 | [btcrpcclient](https://github.com/btcsuite/btcrpcclient) package which provides 20 | a full blown RPC client with many features such as automatic connection 21 | management, websocket support, automatic notification re-registration on 22 | reconnect, and conversion from the raw underlying RPC types (strings, floats, 23 | ints, etc) to higher-level types with many nice and useful properties. 24 | 25 | ## Installation and Updating 26 | 27 | ```bash 28 | $ go get -u github.com/btcsuite/btcd/btcjson 29 | ``` 30 | 31 | ## Examples 32 | 33 | * [Marshal Command](http://godoc.org/github.com/btcsuite/btcd/btcjson#example-MarshalCmd) 34 | Demonstrates how to create and marshal a command into a JSON-RPC request. 35 | 36 | * [Unmarshal Command](http://godoc.org/github.com/btcsuite/btcd/btcjson#example-UnmarshalCmd) 37 | Demonstrates how to unmarshal a JSON-RPC request and then unmarshal the 38 | concrete request into a concrete command. 39 | 40 | * [Marshal Response](http://godoc.org/github.com/btcsuite/btcd/btcjson#example-MarshalResponse) 41 | Demonstrates how to marshal a JSON-RPC response. 42 | 43 | * [Unmarshal Response](http://godoc.org/github.com/btcsuite/btcd/btcjson#example-package--UnmarshalResponse) 44 | Demonstrates how to unmarshal a JSON-RPC response and then unmarshal the 45 | result field in the response to a concrete type. 46 | 47 | ## GPG Verification Key 48 | 49 | All official release tags are signed by Conformal so users can ensure the code 50 | has not been tampered with and is coming from the btcsuite developers. To 51 | verify the signature perform the following: 52 | 53 | - Download the public key from the Conformal website at 54 | https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt 55 | 56 | - Import the public key into your GPG keyring: 57 | ```bash 58 | gpg --import GIT-GPG-KEY-conformal.txt 59 | ``` 60 | 61 | - Verify the release tag with the following command where `TAG_NAME` is a 62 | placeholder for the specific tag: 63 | ```bash 64 | git tag -v TAG_NAME 65 | ``` 66 | 67 | ## License 68 | 69 | Package btcjson is licensed under the [copyfree](http://copyfree.org) ISC 70 | License. 71 | -------------------------------------------------------------------------------- /btcjson/btcdextcmds.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 The btcsuite developers 2 | // Copyright (c) 2015-2016 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | // NOTE: This file is intended to house the RPC commands that are supported by 7 | // a chain server with btcd extensions. 8 | 9 | package btcjson 10 | 11 | // NodeSubCmd defines the type used in the addnode JSON-RPC command for the 12 | // sub command field. 13 | type NodeSubCmd string 14 | 15 | const ( 16 | // NConnect indicates the specified host that should be connected to. 17 | NConnect NodeSubCmd = "connect" 18 | 19 | // NRemove indicates the specified peer that should be removed as a 20 | // persistent peer. 21 | NRemove NodeSubCmd = "remove" 22 | 23 | // NDisconnect indicates the specified peer should be disonnected. 24 | NDisconnect NodeSubCmd = "disconnect" 25 | ) 26 | 27 | // NodeCmd defines the dropnode JSON-RPC command. 28 | type NodeCmd struct { 29 | SubCmd NodeSubCmd `jsonrpcusage:"\"connect|remove|disconnect\""` 30 | Target string 31 | ConnectSubCmd *string `jsonrpcusage:"\"perm|temp\""` 32 | } 33 | 34 | // NewNodeCmd returns a new instance which can be used to issue a `node` 35 | // JSON-RPC command. 36 | // 37 | // The parameters which are pointers indicate they are optional. Passing nil 38 | // for optional parameters will use the default value. 39 | func NewNodeCmd(subCmd NodeSubCmd, target string, connectSubCmd *string) *NodeCmd { 40 | return &NodeCmd{ 41 | SubCmd: subCmd, 42 | Target: target, 43 | ConnectSubCmd: connectSubCmd, 44 | } 45 | } 46 | 47 | // DebugLevelCmd defines the debuglevel JSON-RPC command. This command is not a 48 | // standard Bitcoin command. It is an extension for btcd. 49 | type DebugLevelCmd struct { 50 | LevelSpec string 51 | } 52 | 53 | // NewDebugLevelCmd returns a new DebugLevelCmd which can be used to issue a 54 | // debuglevel JSON-RPC command. This command is not a standard Bitcoin command. 55 | // It is an extension for btcd. 56 | func NewDebugLevelCmd(levelSpec string) *DebugLevelCmd { 57 | return &DebugLevelCmd{ 58 | LevelSpec: levelSpec, 59 | } 60 | } 61 | 62 | // GenerateCmd defines the generate JSON-RPC command. 63 | type GenerateCmd struct { 64 | NumBlocks uint32 65 | } 66 | 67 | // NewGenerateCmd returns a new instance which can be used to issue a generate 68 | // JSON-RPC command. 69 | func NewGenerateCmd(numBlocks uint32) *GenerateCmd { 70 | return &GenerateCmd{ 71 | NumBlocks: numBlocks, 72 | } 73 | } 74 | 75 | // GetBestBlockCmd defines the getbestblock JSON-RPC command. 76 | type GetBestBlockCmd struct{} 77 | 78 | // NewGetBestBlockCmd returns a new instance which can be used to issue a 79 | // getbestblock JSON-RPC command. 80 | func NewGetBestBlockCmd() *GetBestBlockCmd { 81 | return &GetBestBlockCmd{} 82 | } 83 | 84 | // GetCurrentNetCmd defines the getcurrentnet JSON-RPC command. 85 | type GetCurrentNetCmd struct{} 86 | 87 | // NewGetCurrentNetCmd returns a new instance which can be used to issue a 88 | // getcurrentnet JSON-RPC command. 89 | func NewGetCurrentNetCmd() *GetCurrentNetCmd { 90 | return &GetCurrentNetCmd{} 91 | } 92 | 93 | // GetHeadersCmd defines the getheaders JSON-RPC command. 94 | // 95 | // NOTE: This is a btcsuite extension ported from 96 | // github.com/decred/dcrd/dcrjson. 97 | type GetHeadersCmd struct { 98 | BlockLocators []string `json:"blocklocators"` 99 | HashStop string `json:"hashstop"` 100 | } 101 | 102 | // NewGetHeadersCmd returns a new instance which can be used to issue a 103 | // getheaders JSON-RPC command. 104 | // 105 | // NOTE: This is a btcsuite extension ported from 106 | // github.com/decred/dcrd/dcrjson. 107 | func NewGetHeadersCmd(blockLocators []string, hashStop string) *GetHeadersCmd { 108 | return &GetHeadersCmd{ 109 | BlockLocators: blockLocators, 110 | HashStop: hashStop, 111 | } 112 | } 113 | 114 | // VersionCmd defines the version JSON-RPC command. 115 | // 116 | // NOTE: This is a btcsuite extension ported from 117 | // github.com/decred/dcrd/dcrjson. 118 | type VersionCmd struct{} 119 | 120 | // NewVersionCmd returns a new instance which can be used to issue a JSON-RPC 121 | // version command. 122 | // 123 | // NOTE: This is a btcsuite extension ported from 124 | // github.com/decred/dcrd/dcrjson. 125 | func NewVersionCmd() *VersionCmd { return new(VersionCmd) } 126 | 127 | func init() { 128 | // No special flags for commands in this file. 129 | flags := UsageFlag(0) 130 | 131 | MustRegisterCmd("debuglevel", (*DebugLevelCmd)(nil), flags) 132 | MustRegisterCmd("node", (*NodeCmd)(nil), flags) 133 | MustRegisterCmd("generate", (*GenerateCmd)(nil), flags) 134 | MustRegisterCmd("getbestblock", (*GetBestBlockCmd)(nil), flags) 135 | MustRegisterCmd("getcurrentnet", (*GetCurrentNetCmd)(nil), flags) 136 | MustRegisterCmd("getheaders", (*GetHeadersCmd)(nil), flags) 137 | MustRegisterCmd("version", (*VersionCmd)(nil), flags) 138 | } 139 | -------------------------------------------------------------------------------- /btcjson/btcdextcmds_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2016 The btcsuite developers 2 | // Copyright (c) 2015-2016 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package btcjson_test 7 | 8 | import ( 9 | "bytes" 10 | "encoding/json" 11 | "fmt" 12 | "reflect" 13 | "testing" 14 | 15 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 16 | ) 17 | 18 | // TestBtcdExtCmds tests all of the btcd extended commands marshal and unmarshal 19 | // into valid results include handling of optional fields being omitted in the 20 | // marshalled command, while optional fields with defaults have the default 21 | // assigned on unmarshalled commands. 22 | func TestBtcdExtCmds(t *testing.T) { 23 | t.Parallel() 24 | 25 | testID := int(1) 26 | tests := []struct { 27 | name string 28 | newCmd func() (interface{}, error) 29 | staticCmd func() interface{} 30 | marshalled string 31 | unmarshalled interface{} 32 | }{ 33 | { 34 | name: "debuglevel", 35 | newCmd: func() (interface{}, error) { 36 | return btcjson.NewCmd("debuglevel", "trace") 37 | }, 38 | staticCmd: func() interface{} { 39 | return btcjson.NewDebugLevelCmd("trace") 40 | }, 41 | marshalled: `{"jsonrpc":"1.0","method":"debuglevel","params":["trace"],"id":1}`, 42 | unmarshalled: &btcjson.DebugLevelCmd{ 43 | LevelSpec: "trace", 44 | }, 45 | }, 46 | { 47 | name: "node", 48 | newCmd: func() (interface{}, error) { 49 | return btcjson.NewCmd("node", btcjson.NRemove, "1.1.1.1") 50 | }, 51 | staticCmd: func() interface{} { 52 | return btcjson.NewNodeCmd("remove", "1.1.1.1", nil) 53 | }, 54 | marshalled: `{"jsonrpc":"1.0","method":"node","params":["remove","1.1.1.1"],"id":1}`, 55 | unmarshalled: &btcjson.NodeCmd{ 56 | SubCmd: btcjson.NRemove, 57 | Target: "1.1.1.1", 58 | }, 59 | }, 60 | { 61 | name: "node", 62 | newCmd: func() (interface{}, error) { 63 | return btcjson.NewCmd("node", btcjson.NDisconnect, "1.1.1.1") 64 | }, 65 | staticCmd: func() interface{} { 66 | return btcjson.NewNodeCmd("disconnect", "1.1.1.1", nil) 67 | }, 68 | marshalled: `{"jsonrpc":"1.0","method":"node","params":["disconnect","1.1.1.1"],"id":1}`, 69 | unmarshalled: &btcjson.NodeCmd{ 70 | SubCmd: btcjson.NDisconnect, 71 | Target: "1.1.1.1", 72 | }, 73 | }, 74 | { 75 | name: "node", 76 | newCmd: func() (interface{}, error) { 77 | return btcjson.NewCmd("node", btcjson.NConnect, "1.1.1.1", "perm") 78 | }, 79 | staticCmd: func() interface{} { 80 | return btcjson.NewNodeCmd("connect", "1.1.1.1", btcjson.String("perm")) 81 | }, 82 | marshalled: `{"jsonrpc":"1.0","method":"node","params":["connect","1.1.1.1","perm"],"id":1}`, 83 | unmarshalled: &btcjson.NodeCmd{ 84 | SubCmd: btcjson.NConnect, 85 | Target: "1.1.1.1", 86 | ConnectSubCmd: btcjson.String("perm"), 87 | }, 88 | }, 89 | { 90 | name: "node", 91 | newCmd: func() (interface{}, error) { 92 | return btcjson.NewCmd("node", btcjson.NConnect, "1.1.1.1", "temp") 93 | }, 94 | staticCmd: func() interface{} { 95 | return btcjson.NewNodeCmd("connect", "1.1.1.1", btcjson.String("temp")) 96 | }, 97 | marshalled: `{"jsonrpc":"1.0","method":"node","params":["connect","1.1.1.1","temp"],"id":1}`, 98 | unmarshalled: &btcjson.NodeCmd{ 99 | SubCmd: btcjson.NConnect, 100 | Target: "1.1.1.1", 101 | ConnectSubCmd: btcjson.String("temp"), 102 | }, 103 | }, 104 | { 105 | name: "generate", 106 | newCmd: func() (interface{}, error) { 107 | return btcjson.NewCmd("generate", 1) 108 | }, 109 | staticCmd: func() interface{} { 110 | return btcjson.NewGenerateCmd(1) 111 | }, 112 | marshalled: `{"jsonrpc":"1.0","method":"generate","params":[1],"id":1}`, 113 | unmarshalled: &btcjson.GenerateCmd{ 114 | NumBlocks: 1, 115 | }, 116 | }, 117 | { 118 | name: "getbestblock", 119 | newCmd: func() (interface{}, error) { 120 | return btcjson.NewCmd("getbestblock") 121 | }, 122 | staticCmd: func() interface{} { 123 | return btcjson.NewGetBestBlockCmd() 124 | }, 125 | marshalled: `{"jsonrpc":"1.0","method":"getbestblock","params":[],"id":1}`, 126 | unmarshalled: &btcjson.GetBestBlockCmd{}, 127 | }, 128 | { 129 | name: "getcurrentnet", 130 | newCmd: func() (interface{}, error) { 131 | return btcjson.NewCmd("getcurrentnet") 132 | }, 133 | staticCmd: func() interface{} { 134 | return btcjson.NewGetCurrentNetCmd() 135 | }, 136 | marshalled: `{"jsonrpc":"1.0","method":"getcurrentnet","params":[],"id":1}`, 137 | unmarshalled: &btcjson.GetCurrentNetCmd{}, 138 | }, 139 | { 140 | name: "getheaders", 141 | newCmd: func() (interface{}, error) { 142 | return btcjson.NewCmd("getheaders", []string{}, "") 143 | }, 144 | staticCmd: func() interface{} { 145 | return btcjson.NewGetHeadersCmd( 146 | []string{}, 147 | "", 148 | ) 149 | }, 150 | marshalled: `{"jsonrpc":"1.0","method":"getheaders","params":[[],""],"id":1}`, 151 | unmarshalled: &btcjson.GetHeadersCmd{ 152 | BlockLocators: []string{}, 153 | HashStop: "", 154 | }, 155 | }, 156 | { 157 | name: "getheaders - with arguments", 158 | newCmd: func() (interface{}, error) { 159 | return btcjson.NewCmd("getheaders", []string{"000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16", "0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10"}, "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7") 160 | }, 161 | staticCmd: func() interface{} { 162 | return btcjson.NewGetHeadersCmd( 163 | []string{ 164 | "000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16", 165 | "0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10", 166 | }, 167 | "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7", 168 | ) 169 | }, 170 | marshalled: `{"jsonrpc":"1.0","method":"getheaders","params":[["000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16","0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10"],"000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7"],"id":1}`, 171 | unmarshalled: &btcjson.GetHeadersCmd{ 172 | BlockLocators: []string{ 173 | "000000000000000001f1739002418e2f9a84c47a4fd2a0eb7a787a6b7dc12f16", 174 | "0000000000000000026f4b7f56eef057b32167eb5ad9ff62006f1807b7336d10", 175 | }, 176 | HashStop: "000000000000000000ba33b33e1fad70b69e234fc24414dd47113bff38f523f7", 177 | }, 178 | }, 179 | { 180 | name: "version", 181 | newCmd: func() (interface{}, error) { 182 | return btcjson.NewCmd("version") 183 | }, 184 | staticCmd: func() interface{} { 185 | return btcjson.NewVersionCmd() 186 | }, 187 | marshalled: `{"jsonrpc":"1.0","method":"version","params":[],"id":1}`, 188 | unmarshalled: &btcjson.VersionCmd{}, 189 | }, 190 | } 191 | 192 | t.Logf("Running %d tests", len(tests)) 193 | for i, test := range tests { 194 | // Marshal the command as created by the new static command 195 | // creation function. 196 | marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd()) 197 | if err != nil { 198 | t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i, 199 | test.name, err) 200 | continue 201 | } 202 | 203 | if !bytes.Equal(marshalled, []byte(test.marshalled)) { 204 | t.Errorf("Test #%d (%s) unexpected marshalled data - "+ 205 | "got %s, want %s", i, test.name, marshalled, 206 | test.marshalled) 207 | continue 208 | } 209 | 210 | // Ensure the command is created without error via the generic 211 | // new command creation function. 212 | cmd, err := test.newCmd() 213 | if err != nil { 214 | t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ", 215 | i, test.name, err) 216 | } 217 | 218 | // Marshal the command as created by the generic new command 219 | // creation function. 220 | marshalled, err = btcjson.MarshalCmd(testID, cmd) 221 | if err != nil { 222 | t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i, 223 | test.name, err) 224 | continue 225 | } 226 | 227 | if !bytes.Equal(marshalled, []byte(test.marshalled)) { 228 | t.Errorf("Test #%d (%s) unexpected marshalled data - "+ 229 | "got %s, want %s", i, test.name, marshalled, 230 | test.marshalled) 231 | continue 232 | } 233 | 234 | var request btcjson.Request 235 | if err := json.Unmarshal(marshalled, &request); err != nil { 236 | t.Errorf("Test #%d (%s) unexpected error while "+ 237 | "unmarshalling JSON-RPC request: %v", i, 238 | test.name, err) 239 | continue 240 | } 241 | 242 | cmd, err = btcjson.UnmarshalCmd(&request) 243 | if err != nil { 244 | t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i, 245 | test.name, err) 246 | continue 247 | } 248 | 249 | if !reflect.DeepEqual(cmd, test.unmarshalled) { 250 | t.Errorf("Test #%d (%s) unexpected unmarshalled command "+ 251 | "- got %s, want %s", i, test.name, 252 | fmt.Sprintf("(%T) %+[1]v", cmd), 253 | fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled)) 254 | continue 255 | } 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /btcjson/btcdextresults.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2017 The btcsuite developers 2 | // Copyright (c) 2015-2017 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package btcjson 7 | 8 | // VersionResult models objects included in the version response. In the actual 9 | // result, these objects are keyed by the program or API name. 10 | // 11 | // NOTE: This is a btcsuite extension ported from 12 | // github.com/decred/dcrd/dcrjson. 13 | type VersionResult struct { 14 | VersionString string `json:"versionstring"` 15 | Major uint32 `json:"major"` 16 | Minor uint32 `json:"minor"` 17 | Patch uint32 `json:"patch"` 18 | Prerelease string `json:"prerelease"` 19 | BuildMetadata string `json:"buildmetadata"` 20 | } 21 | -------------------------------------------------------------------------------- /btcjson/btcdextresults_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016-2017 The btcsuite developers 2 | // Copyright (c) 2015-2016 The Decred developers 3 | // Use of this source code is governed by an ISC 4 | // license that can be found in the LICENSE file. 5 | 6 | package btcjson_test 7 | 8 | import ( 9 | "encoding/json" 10 | "testing" 11 | 12 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 13 | ) 14 | 15 | // TestBtcdExtCustomResults ensures any results that have custom marshalling 16 | // work as inteded. 17 | // and unmarshal code of results are as expected. 18 | func TestBtcdExtCustomResults(t *testing.T) { 19 | t.Parallel() 20 | 21 | tests := []struct { 22 | name string 23 | result interface{} 24 | expected string 25 | }{ 26 | { 27 | name: "versionresult", 28 | result: &btcjson.VersionResult{ 29 | VersionString: "1.0.0", 30 | Major: 1, 31 | Minor: 0, 32 | Patch: 0, 33 | Prerelease: "pr", 34 | BuildMetadata: "bm", 35 | }, 36 | expected: `{"versionstring":"1.0.0","major":1,"minor":0,"patch":0,"prerelease":"pr","buildmetadata":"bm"}`, 37 | }, 38 | } 39 | 40 | t.Logf("Running %d tests", len(tests)) 41 | for i, test := range tests { 42 | marshalled, err := json.Marshal(test.result) 43 | if err != nil { 44 | t.Errorf("Test #%d (%s) unexpected error: %v", i, 45 | test.name, err) 46 | continue 47 | } 48 | if string(marshalled) != test.expected { 49 | t.Errorf("Test #%d (%s) unexpected marhsalled data - "+ 50 | "got %s, want %s", i, test.name, marshalled, 51 | test.expected) 52 | continue 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /btcjson/btcwalletextcmds.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | // NOTE: This file is intended to house the RPC commands that are supported by 6 | // a wallet server with btcwallet extensions. 7 | 8 | package btcjson 9 | 10 | // CreateNewAccountCmd defines the createnewaccount JSON-RPC command. 11 | type CreateNewAccountCmd struct { 12 | Account string 13 | } 14 | 15 | // NewCreateNewAccountCmd returns a new instance which can be used to issue a 16 | // createnewaccount JSON-RPC command. 17 | func NewCreateNewAccountCmd(account string) *CreateNewAccountCmd { 18 | return &CreateNewAccountCmd{ 19 | Account: account, 20 | } 21 | } 22 | 23 | // DumpWalletCmd defines the dumpwallet JSON-RPC command. 24 | type DumpWalletCmd struct { 25 | Filename string 26 | } 27 | 28 | // NewDumpWalletCmd returns a new instance which can be used to issue a 29 | // dumpwallet JSON-RPC command. 30 | func NewDumpWalletCmd(filename string) *DumpWalletCmd { 31 | return &DumpWalletCmd{ 32 | Filename: filename, 33 | } 34 | } 35 | 36 | // ImportAddressCmd defines the importaddress JSON-RPC command. 37 | type ImportAddressCmd struct { 38 | Address string 39 | Account string 40 | Rescan *bool `jsonrpcdefault:"true"` 41 | } 42 | 43 | // NewImportAddressCmd returns a new instance which can be used to issue an 44 | // importaddress JSON-RPC command. 45 | func NewImportAddressCmd(address string, rescan *bool) *ImportAddressCmd { 46 | return &ImportAddressCmd{ 47 | Address: address, 48 | Account: "*", 49 | Rescan: rescan, 50 | } 51 | } 52 | 53 | // ImportPubKeyCmd defines the importpubkey JSON-RPC command. 54 | type ImportPubKeyCmd struct { 55 | PubKey string 56 | Rescan *bool `jsonrpcdefault:"true"` 57 | } 58 | 59 | // NewImportPubKeyCmd returns a new instance which can be used to issue an 60 | // importpubkey JSON-RPC command. 61 | func NewImportPubKeyCmd(pubKey string, rescan *bool) *ImportPubKeyCmd { 62 | return &ImportPubKeyCmd{ 63 | PubKey: pubKey, 64 | Rescan: rescan, 65 | } 66 | } 67 | 68 | // ImportWalletCmd defines the importwallet JSON-RPC command. 69 | type ImportWalletCmd struct { 70 | Filename string 71 | } 72 | 73 | // NewImportWalletCmd returns a new instance which can be used to issue a 74 | // importwallet JSON-RPC command. 75 | func NewImportWalletCmd(filename string) *ImportWalletCmd { 76 | return &ImportWalletCmd{ 77 | Filename: filename, 78 | } 79 | } 80 | 81 | // RenameAccountCmd defines the renameaccount JSON-RPC command. 82 | type RenameAccountCmd struct { 83 | OldAccount string 84 | NewAccount string 85 | } 86 | 87 | // NewRenameAccountCmd returns a new instance which can be used to issue a 88 | // renameaccount JSON-RPC command. 89 | func NewRenameAccountCmd(oldAccount, newAccount string) *RenameAccountCmd { 90 | return &RenameAccountCmd{ 91 | OldAccount: oldAccount, 92 | NewAccount: newAccount, 93 | } 94 | } 95 | 96 | func init() { 97 | // The commands in this file are only usable with a wallet server. 98 | flags := UFWalletOnly 99 | 100 | MustRegisterCmd("createnewaccount", (*CreateNewAccountCmd)(nil), flags) 101 | MustRegisterCmd("dumpwallet", (*DumpWalletCmd)(nil), flags) 102 | MustRegisterCmd("importaddress", (*ImportAddressCmd)(nil), flags) 103 | MustRegisterCmd("importpubkey", (*ImportPubKeyCmd)(nil), flags) 104 | MustRegisterCmd("importwallet", (*ImportWalletCmd)(nil), flags) 105 | MustRegisterCmd("renameaccount", (*RenameAccountCmd)(nil), flags) 106 | } 107 | -------------------------------------------------------------------------------- /btcjson/btcwalletextcmds_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson_test 6 | 7 | import ( 8 | "bytes" 9 | "encoding/json" 10 | "fmt" 11 | "reflect" 12 | "testing" 13 | 14 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 15 | ) 16 | 17 | // TestBtcWalletExtCmds tests all of the btcwallet extended commands marshal and 18 | // unmarshal into valid results include handling of optional fields being 19 | // omitted in the marshalled command, while optional fields with defaults have 20 | // the default assigned on unmarshalled commands. 21 | func TestBtcWalletExtCmds(t *testing.T) { 22 | t.Parallel() 23 | 24 | testID := int(1) 25 | tests := []struct { 26 | name string 27 | newCmd func() (interface{}, error) 28 | staticCmd func() interface{} 29 | marshalled string 30 | unmarshalled interface{} 31 | }{ 32 | { 33 | name: "createnewaccount", 34 | newCmd: func() (interface{}, error) { 35 | return btcjson.NewCmd("createnewaccount", "acct") 36 | }, 37 | staticCmd: func() interface{} { 38 | return btcjson.NewCreateNewAccountCmd("acct") 39 | }, 40 | marshalled: `{"jsonrpc":"1.0","method":"createnewaccount","params":["acct"],"id":1}`, 41 | unmarshalled: &btcjson.CreateNewAccountCmd{ 42 | Account: "acct", 43 | }, 44 | }, 45 | { 46 | name: "dumpwallet", 47 | newCmd: func() (interface{}, error) { 48 | return btcjson.NewCmd("dumpwallet", "filename") 49 | }, 50 | staticCmd: func() interface{} { 51 | return btcjson.NewDumpWalletCmd("filename") 52 | }, 53 | marshalled: `{"jsonrpc":"1.0","method":"dumpwallet","params":["filename"],"id":1}`, 54 | unmarshalled: &btcjson.DumpWalletCmd{ 55 | Filename: "filename", 56 | }, 57 | }, 58 | { 59 | name: "importaddress", 60 | newCmd: func() (interface{}, error) { 61 | return btcjson.NewCmd("importaddress", "1Address", "*") 62 | }, 63 | staticCmd: func() interface{} { 64 | return btcjson.NewImportAddressCmd("1Address", nil) 65 | }, 66 | marshalled: `{"jsonrpc":"1.0","method":"importaddress","params":["1Address","*"],"id":1}`, 67 | unmarshalled: &btcjson.ImportAddressCmd{ 68 | Address: "1Address", 69 | Account: "*", 70 | Rescan: btcjson.Bool(true), 71 | }, 72 | }, 73 | { 74 | name: "importaddress optional", 75 | newCmd: func() (interface{}, error) { 76 | return btcjson.NewCmd("importaddress", "1Address", "*", false) 77 | }, 78 | staticCmd: func() interface{} { 79 | return btcjson.NewImportAddressCmd("1Address", btcjson.Bool(false)) 80 | }, 81 | marshalled: `{"jsonrpc":"1.0","method":"importaddress","params":["1Address","*",false],"id":1}`, 82 | unmarshalled: &btcjson.ImportAddressCmd{ 83 | Address: "1Address", 84 | Account: "*", 85 | Rescan: btcjson.Bool(false), 86 | }, 87 | }, 88 | { 89 | name: "importpubkey", 90 | newCmd: func() (interface{}, error) { 91 | return btcjson.NewCmd("importpubkey", "031234") 92 | }, 93 | staticCmd: func() interface{} { 94 | return btcjson.NewImportPubKeyCmd("031234", nil) 95 | }, 96 | marshalled: `{"jsonrpc":"1.0","method":"importpubkey","params":["031234"],"id":1}`, 97 | unmarshalled: &btcjson.ImportPubKeyCmd{ 98 | PubKey: "031234", 99 | Rescan: btcjson.Bool(true), 100 | }, 101 | }, 102 | { 103 | name: "importpubkey optional", 104 | newCmd: func() (interface{}, error) { 105 | return btcjson.NewCmd("importpubkey", "031234", false) 106 | }, 107 | staticCmd: func() interface{} { 108 | return btcjson.NewImportPubKeyCmd("031234", btcjson.Bool(false)) 109 | }, 110 | marshalled: `{"jsonrpc":"1.0","method":"importpubkey","params":["031234",false],"id":1}`, 111 | unmarshalled: &btcjson.ImportPubKeyCmd{ 112 | PubKey: "031234", 113 | Rescan: btcjson.Bool(false), 114 | }, 115 | }, 116 | { 117 | name: "importwallet", 118 | newCmd: func() (interface{}, error) { 119 | return btcjson.NewCmd("importwallet", "filename") 120 | }, 121 | staticCmd: func() interface{} { 122 | return btcjson.NewImportWalletCmd("filename") 123 | }, 124 | marshalled: `{"jsonrpc":"1.0","method":"importwallet","params":["filename"],"id":1}`, 125 | unmarshalled: &btcjson.ImportWalletCmd{ 126 | Filename: "filename", 127 | }, 128 | }, 129 | { 130 | name: "renameaccount", 131 | newCmd: func() (interface{}, error) { 132 | return btcjson.NewCmd("renameaccount", "oldacct", "newacct") 133 | }, 134 | staticCmd: func() interface{} { 135 | return btcjson.NewRenameAccountCmd("oldacct", "newacct") 136 | }, 137 | marshalled: `{"jsonrpc":"1.0","method":"renameaccount","params":["oldacct","newacct"],"id":1}`, 138 | unmarshalled: &btcjson.RenameAccountCmd{ 139 | OldAccount: "oldacct", 140 | NewAccount: "newacct", 141 | }, 142 | }, 143 | } 144 | 145 | t.Logf("Running %d tests", len(tests)) 146 | for i, test := range tests { 147 | // Marshal the command as created by the new static command 148 | // creation function. 149 | marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd()) 150 | if err != nil { 151 | t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i, 152 | test.name, err) 153 | continue 154 | } 155 | 156 | if !bytes.Equal(marshalled, []byte(test.marshalled)) { 157 | t.Errorf("Test #%d (%s) unexpected marshalled data - "+ 158 | "got %s, want %s", i, test.name, marshalled, 159 | test.marshalled) 160 | continue 161 | } 162 | 163 | // Ensure the command is created without error via the generic 164 | // new command creation function. 165 | cmd, err := test.newCmd() 166 | if err != nil { 167 | t.Errorf("Test #%d (%s) unexpected NewCmd error: %v ", 168 | i, test.name, err) 169 | } 170 | 171 | // Marshal the command as created by the generic new command 172 | // creation function. 173 | marshalled, err = btcjson.MarshalCmd(testID, cmd) 174 | if err != nil { 175 | t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i, 176 | test.name, err) 177 | continue 178 | } 179 | 180 | if !bytes.Equal(marshalled, []byte(test.marshalled)) { 181 | t.Errorf("Test #%d (%s) unexpected marshalled data - "+ 182 | "got %s, want %s", i, test.name, marshalled, 183 | test.marshalled) 184 | continue 185 | } 186 | 187 | var request btcjson.Request 188 | if err := json.Unmarshal(marshalled, &request); err != nil { 189 | t.Errorf("Test #%d (%s) unexpected error while "+ 190 | "unmarshalling JSON-RPC request: %v", i, 191 | test.name, err) 192 | continue 193 | } 194 | 195 | cmd, err = btcjson.UnmarshalCmd(&request) 196 | if err != nil { 197 | t.Errorf("UnmarshalCmd #%d (%s) unexpected error: %v", i, 198 | test.name, err) 199 | continue 200 | } 201 | 202 | if !reflect.DeepEqual(cmd, test.unmarshalled) { 203 | t.Errorf("Test #%d (%s) unexpected unmarshalled command "+ 204 | "- got %s, want %s", i, test.name, 205 | fmt.Sprintf("(%T) %+[1]v", cmd), 206 | fmt.Sprintf("(%T) %+[1]v\n", test.unmarshalled)) 207 | continue 208 | } 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /btcjson/chainsvrresults_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson_test 6 | 7 | import ( 8 | "encoding/json" 9 | "testing" 10 | 11 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 12 | ) 13 | 14 | // TestChainSvrCustomResults ensures any results that have custom marshalling 15 | // work as inteded. 16 | // and unmarshal code of results are as expected. 17 | func TestChainSvrCustomResults(t *testing.T) { 18 | t.Parallel() 19 | 20 | staticFee := float64(0.00002) 21 | 22 | tests := []struct { 23 | name string 24 | result interface{} 25 | expected string 26 | }{ 27 | { 28 | name: "custom vin marshal with coinbase", 29 | result: &btcjson.Vin{ 30 | Coinbase: "021234", 31 | Sequence: 4294967295, 32 | }, 33 | expected: `{"coinbase":"021234","sequence":4294967295}`, 34 | }, 35 | { 36 | name: "custom vin marshal without coinbase", 37 | result: &btcjson.Vin{ 38 | Txid: "123", 39 | Vout: 1, 40 | ScriptSig: &btcjson.ScriptSig{ 41 | Asm: "0", 42 | Hex: "00", 43 | }, 44 | Sequence: 4294967295, 45 | }, 46 | expected: `{"txid":"123","vout":1,"scriptSig":{"asm":"0","hex":"00"},"sequence":4294967295}`, 47 | }, 48 | { 49 | name: "custom vinprevout marshal with coinbase", 50 | result: &btcjson.VinPrevOut{ 51 | Coinbase: "021234", 52 | Sequence: 4294967295, 53 | }, 54 | expected: `{"coinbase":"021234","sequence":4294967295}`, 55 | }, 56 | { 57 | name: "custom vinprevout marshal without coinbase", 58 | result: &btcjson.VinPrevOut{ 59 | Txid: "123", 60 | Vout: 1, 61 | ScriptSig: &btcjson.ScriptSig{ 62 | Asm: "0", 63 | Hex: "00", 64 | }, 65 | PrevOut: &btcjson.PrevOut{ 66 | Addresses: []string{"addr1"}, 67 | Value: 0, 68 | }, 69 | Sequence: 4294967295, 70 | }, 71 | expected: `{"txid":"123","vout":1,"scriptSig":{"asm":"0","hex":"00"},"prevOut":{"addresses":["addr1"],"value":0},"sequence":4294967295}`, 72 | }, 73 | { 74 | name: "estimatesmartfee with no errors", 75 | result: &btcjson.EstimateSmartFeeResult{ 76 | FeeRate: &staticFee, 77 | Blocks: 6, 78 | }, 79 | expected: `{"feerate":0.00002,"blocks":6}`, 80 | }, 81 | { 82 | name: "estimatesmartfee with errors", 83 | result: &btcjson.EstimateSmartFeeResult{ 84 | Errors: &[]string{"An error has occurred"}, 85 | Blocks: 6, 86 | }, 87 | expected: `{"errors":["An error has occurred"],"blocks":6}`, 88 | }, 89 | } 90 | 91 | t.Logf("Running %d tests", len(tests)) 92 | for i, test := range tests { 93 | marshalled, err := json.Marshal(test.result) 94 | if err != nil { 95 | t.Errorf("Test #%d (%s) unexpected error: %v", i, 96 | test.name, err) 97 | continue 98 | } 99 | if string(marshalled) != test.expected { 100 | t.Errorf("Test #%d (%s) unexpected marhsalled data - "+ 101 | "got %s, want %s", i, test.name, marshalled, 102 | test.expected) 103 | continue 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /btcjson/cmdinfo.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | "strings" 11 | ) 12 | 13 | // CmdMethod returns the method for the passed command. The provided command 14 | // type must be a registered type. All commands provided by this package are 15 | // registered by default. 16 | func CmdMethod(cmd interface{}) (string, error) { 17 | // Look up the cmd type and error out if not registered. 18 | rt := reflect.TypeOf(cmd) 19 | registerLock.RLock() 20 | method, ok := concreteTypeToMethod[rt] 21 | registerLock.RUnlock() 22 | if !ok { 23 | str := fmt.Sprintf("%q is not registered", method) 24 | return "", makeError(ErrUnregisteredMethod, str) 25 | } 26 | 27 | return method, nil 28 | } 29 | 30 | // MethodUsageFlags returns the usage flags for the passed command method. The 31 | // provided method must be associated with a registered type. All commands 32 | // provided by this package are registered by default. 33 | func MethodUsageFlags(method string) (UsageFlag, error) { 34 | // Look up details about the provided method and error out if not 35 | // registered. 36 | registerLock.RLock() 37 | info, ok := methodToInfo[method] 38 | registerLock.RUnlock() 39 | if !ok { 40 | str := fmt.Sprintf("%q is not registered", method) 41 | return 0, makeError(ErrUnregisteredMethod, str) 42 | } 43 | 44 | return info.flags, nil 45 | } 46 | 47 | // subStructUsage returns a string for use in the one-line usage for the given 48 | // sub struct. Note that this is specifically for fields which consist of 49 | // structs (or an array/slice of structs) as opposed to the top-level command 50 | // struct. 51 | // 52 | // Any fields that include a jsonrpcusage struct tag will use that instead of 53 | // being automatically generated. 54 | func subStructUsage(structType reflect.Type) string { 55 | numFields := structType.NumField() 56 | fieldUsages := make([]string, 0, numFields) 57 | for i := 0; i < structType.NumField(); i++ { 58 | rtf := structType.Field(i) 59 | 60 | // When the field has a jsonrpcusage struct tag specified use 61 | // that instead of automatically generating it. 62 | if tag := rtf.Tag.Get("jsonrpcusage"); tag != "" { 63 | fieldUsages = append(fieldUsages, tag) 64 | continue 65 | } 66 | 67 | // Create the name/value entry for the field while considering 68 | // the type of the field. Not all possible types are covered 69 | // here and when one of the types not specifically covered is 70 | // encountered, the field name is simply reused for the value. 71 | fieldName := strings.ToLower(rtf.Name) 72 | fieldValue := fieldName 73 | fieldKind := rtf.Type.Kind() 74 | switch { 75 | case isNumeric(fieldKind): 76 | if fieldKind == reflect.Float32 || fieldKind == reflect.Float64 { 77 | fieldValue = "n.nnn" 78 | } else { 79 | fieldValue = "n" 80 | } 81 | case fieldKind == reflect.String: 82 | fieldValue = `"value"` 83 | 84 | case fieldKind == reflect.Struct: 85 | fieldValue = subStructUsage(rtf.Type) 86 | 87 | case fieldKind == reflect.Array || fieldKind == reflect.Slice: 88 | fieldValue = subArrayUsage(rtf.Type, fieldName) 89 | } 90 | 91 | usage := fmt.Sprintf("%q:%s", fieldName, fieldValue) 92 | fieldUsages = append(fieldUsages, usage) 93 | } 94 | 95 | return fmt.Sprintf("{%s}", strings.Join(fieldUsages, ",")) 96 | } 97 | 98 | // subArrayUsage returns a string for use in the one-line usage for the given 99 | // array or slice. It also contains logic to convert plural field names to 100 | // singular so the generated usage string reads better. 101 | func subArrayUsage(arrayType reflect.Type, fieldName string) string { 102 | // Convert plural field names to singular. Only works for English. 103 | singularFieldName := fieldName 104 | if strings.HasSuffix(fieldName, "ies") { 105 | singularFieldName = strings.TrimSuffix(fieldName, "ies") 106 | singularFieldName = singularFieldName + "y" 107 | } else if strings.HasSuffix(fieldName, "es") { 108 | singularFieldName = strings.TrimSuffix(fieldName, "es") 109 | } else if strings.HasSuffix(fieldName, "s") { 110 | singularFieldName = strings.TrimSuffix(fieldName, "s") 111 | } 112 | 113 | elemType := arrayType.Elem() 114 | switch elemType.Kind() { 115 | case reflect.String: 116 | return fmt.Sprintf("[%q,...]", singularFieldName) 117 | 118 | case reflect.Struct: 119 | return fmt.Sprintf("[%s,...]", subStructUsage(elemType)) 120 | } 121 | 122 | // Fall back to simply showing the field name in array syntax. 123 | return fmt.Sprintf(`[%s,...]`, singularFieldName) 124 | } 125 | 126 | // fieldUsage returns a string for use in the one-line usage for the struct 127 | // field of a command. 128 | // 129 | // Any fields that include a jsonrpcusage struct tag will use that instead of 130 | // being automatically generated. 131 | func fieldUsage(structField reflect.StructField, defaultVal *reflect.Value) string { 132 | // When the field has a jsonrpcusage struct tag specified use that 133 | // instead of automatically generating it. 134 | if tag := structField.Tag.Get("jsonrpcusage"); tag != "" { 135 | return tag 136 | } 137 | 138 | // Indirect the pointer if needed. 139 | fieldType := structField.Type 140 | if fieldType.Kind() == reflect.Ptr { 141 | fieldType = fieldType.Elem() 142 | } 143 | 144 | // When there is a default value, it must also be a pointer due to the 145 | // rules enforced by RegisterCmd. 146 | if defaultVal != nil { 147 | indirect := defaultVal.Elem() 148 | defaultVal = &indirect 149 | } 150 | 151 | // Handle certain types uniquely to provide nicer usage. 152 | fieldName := strings.ToLower(structField.Name) 153 | switch fieldType.Kind() { 154 | case reflect.String: 155 | if defaultVal != nil { 156 | return fmt.Sprintf("%s=%q", fieldName, 157 | defaultVal.Interface()) 158 | } 159 | 160 | return fmt.Sprintf("%q", fieldName) 161 | 162 | case reflect.Array, reflect.Slice: 163 | return subArrayUsage(fieldType, fieldName) 164 | 165 | case reflect.Struct: 166 | return subStructUsage(fieldType) 167 | } 168 | 169 | // Simply return the field name when none of the above special cases 170 | // apply. 171 | if defaultVal != nil { 172 | return fmt.Sprintf("%s=%v", fieldName, defaultVal.Interface()) 173 | } 174 | return fieldName 175 | } 176 | 177 | // methodUsageText returns a one-line usage string for the provided command and 178 | // method info. This is the main work horse for the exported MethodUsageText 179 | // function. 180 | func methodUsageText(rtp reflect.Type, defaults map[int]reflect.Value, method string) string { 181 | // Generate the individual usage for each field in the command. Several 182 | // simplifying assumptions are made here because the RegisterCmd 183 | // function has already rigorously enforced the layout. 184 | rt := rtp.Elem() 185 | numFields := rt.NumField() 186 | reqFieldUsages := make([]string, 0, numFields) 187 | optFieldUsages := make([]string, 0, numFields) 188 | for i := 0; i < numFields; i++ { 189 | rtf := rt.Field(i) 190 | var isOptional bool 191 | if kind := rtf.Type.Kind(); kind == reflect.Ptr { 192 | isOptional = true 193 | } 194 | 195 | var defaultVal *reflect.Value 196 | if defVal, ok := defaults[i]; ok { 197 | defaultVal = &defVal 198 | } 199 | 200 | // Add human-readable usage to the appropriate slice that is 201 | // later used to generate the one-line usage. 202 | usage := fieldUsage(rtf, defaultVal) 203 | if isOptional { 204 | optFieldUsages = append(optFieldUsages, usage) 205 | } else { 206 | reqFieldUsages = append(reqFieldUsages, usage) 207 | } 208 | } 209 | 210 | // Generate and return the one-line usage string. 211 | usageStr := method 212 | if len(reqFieldUsages) > 0 { 213 | usageStr += " " + strings.Join(reqFieldUsages, " ") 214 | } 215 | if len(optFieldUsages) > 0 { 216 | usageStr += fmt.Sprintf(" (%s)", strings.Join(optFieldUsages, " ")) 217 | } 218 | return usageStr 219 | } 220 | 221 | // MethodUsageText returns a one-line usage string for the provided method. The 222 | // provided method must be associated with a registered type. All commands 223 | // provided by this package are registered by default. 224 | func MethodUsageText(method string) (string, error) { 225 | // Look up details about the provided method and error out if not 226 | // registered. 227 | registerLock.RLock() 228 | rtp, ok := methodToConcreteType[method] 229 | info := methodToInfo[method] 230 | registerLock.RUnlock() 231 | if !ok { 232 | str := fmt.Sprintf("%q is not registered", method) 233 | return "", makeError(ErrUnregisteredMethod, str) 234 | } 235 | 236 | // When the usage for this method has already been generated, simply 237 | // return it. 238 | if info.usage != "" { 239 | return info.usage, nil 240 | } 241 | 242 | // Generate and store the usage string for future calls and return it. 243 | usage := methodUsageText(rtp, info.defaults, method) 244 | registerLock.Lock() 245 | info.usage = usage 246 | methodToInfo[method] = info 247 | registerLock.Unlock() 248 | return usage, nil 249 | } 250 | -------------------------------------------------------------------------------- /btcjson/cmdinfo_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson_test 6 | 7 | import ( 8 | "reflect" 9 | "testing" 10 | 11 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 12 | ) 13 | 14 | // TestCmdMethod tests the CmdMethod function to ensure it retunrs the expected 15 | // methods and errors. 16 | func TestCmdMethod(t *testing.T) { 17 | t.Parallel() 18 | 19 | tests := []struct { 20 | name string 21 | cmd interface{} 22 | method string 23 | err error 24 | }{ 25 | { 26 | name: "unregistered type", 27 | cmd: (*int)(nil), 28 | err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod}, 29 | }, 30 | { 31 | name: "nil pointer of registered type", 32 | cmd: (*btcjson.GetBlockCmd)(nil), 33 | method: "getblock", 34 | }, 35 | { 36 | name: "nil instance of registered type", 37 | cmd: &btcjson.GetBlockCountCmd{}, 38 | method: "getblockcount", 39 | }, 40 | } 41 | 42 | t.Logf("Running %d tests", len(tests)) 43 | for i, test := range tests { 44 | method, err := btcjson.CmdMethod(test.cmd) 45 | if reflect.TypeOf(err) != reflect.TypeOf(test.err) { 46 | t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+ 47 | "want %T", i, test.name, err, test.err) 48 | continue 49 | } 50 | if err != nil { 51 | gotErrorCode := err.(btcjson.Error).ErrorCode 52 | if gotErrorCode != test.err.(btcjson.Error).ErrorCode { 53 | t.Errorf("Test #%d (%s) mismatched error code "+ 54 | "- got %v (%v), want %v", i, test.name, 55 | gotErrorCode, err, 56 | test.err.(btcjson.Error).ErrorCode) 57 | continue 58 | } 59 | 60 | continue 61 | } 62 | 63 | // Ensure method matches the expected value. 64 | if method != test.method { 65 | t.Errorf("Test #%d (%s) mismatched method - got %v, "+ 66 | "want %v", i, test.name, method, test.method) 67 | continue 68 | } 69 | } 70 | } 71 | 72 | // TestMethodUsageFlags tests the MethodUsage function ensure it returns the 73 | // expected flags and errors. 74 | func TestMethodUsageFlags(t *testing.T) { 75 | t.Parallel() 76 | 77 | tests := []struct { 78 | name string 79 | method string 80 | err error 81 | flags btcjson.UsageFlag 82 | }{ 83 | { 84 | name: "unregistered type", 85 | method: "bogusmethod", 86 | err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod}, 87 | }, 88 | { 89 | name: "getblock", 90 | method: "getblock", 91 | flags: 0, 92 | }, 93 | { 94 | name: "walletpassphrase", 95 | method: "walletpassphrase", 96 | flags: btcjson.UFWalletOnly, 97 | }, 98 | } 99 | 100 | t.Logf("Running %d tests", len(tests)) 101 | for i, test := range tests { 102 | flags, err := btcjson.MethodUsageFlags(test.method) 103 | if reflect.TypeOf(err) != reflect.TypeOf(test.err) { 104 | t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+ 105 | "want %T", i, test.name, err, test.err) 106 | continue 107 | } 108 | if err != nil { 109 | gotErrorCode := err.(btcjson.Error).ErrorCode 110 | if gotErrorCode != test.err.(btcjson.Error).ErrorCode { 111 | t.Errorf("Test #%d (%s) mismatched error code "+ 112 | "- got %v (%v), want %v", i, test.name, 113 | gotErrorCode, err, 114 | test.err.(btcjson.Error).ErrorCode) 115 | continue 116 | } 117 | 118 | continue 119 | } 120 | 121 | // Ensure flags match the expected value. 122 | if flags != test.flags { 123 | t.Errorf("Test #%d (%s) mismatched flags - got %v, "+ 124 | "want %v", i, test.name, flags, test.flags) 125 | continue 126 | } 127 | } 128 | } 129 | 130 | // TestMethodUsageText tests the MethodUsageText function ensure it returns the 131 | // expected text. 132 | func TestMethodUsageText(t *testing.T) { 133 | t.Parallel() 134 | 135 | tests := []struct { 136 | name string 137 | method string 138 | err error 139 | expected string 140 | }{ 141 | { 142 | name: "unregistered type", 143 | method: "bogusmethod", 144 | err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod}, 145 | }, 146 | { 147 | name: "getblockcount", 148 | method: "getblockcount", 149 | expected: "getblockcount", 150 | }, 151 | { 152 | name: "getblock", 153 | method: "getblock", 154 | expected: `getblock "hash" (verbose=true verbosetx=false)`, 155 | }, 156 | } 157 | 158 | t.Logf("Running %d tests", len(tests)) 159 | for i, test := range tests { 160 | usage, err := btcjson.MethodUsageText(test.method) 161 | if reflect.TypeOf(err) != reflect.TypeOf(test.err) { 162 | t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+ 163 | "want %T", i, test.name, err, test.err) 164 | continue 165 | } 166 | if err != nil { 167 | gotErrorCode := err.(btcjson.Error).ErrorCode 168 | if gotErrorCode != test.err.(btcjson.Error).ErrorCode { 169 | t.Errorf("Test #%d (%s) mismatched error code "+ 170 | "- got %v (%v), want %v", i, test.name, 171 | gotErrorCode, err, 172 | test.err.(btcjson.Error).ErrorCode) 173 | continue 174 | } 175 | 176 | continue 177 | } 178 | 179 | // Ensure usage matches the expected value. 180 | if usage != test.expected { 181 | t.Errorf("Test #%d (%s) mismatched usage - got %v, "+ 182 | "want %v", i, test.name, usage, test.expected) 183 | continue 184 | } 185 | 186 | // Get the usage again to exercise caching. 187 | usage, err = btcjson.MethodUsageText(test.method) 188 | if err != nil { 189 | t.Errorf("Test #%d (%s) unexpected error: %v", i, 190 | test.name, err) 191 | continue 192 | } 193 | 194 | // Ensure usage still matches the expected value. 195 | if usage != test.expected { 196 | t.Errorf("Test #%d (%s) mismatched usage - got %v, "+ 197 | "want %v", i, test.name, usage, test.expected) 198 | continue 199 | } 200 | } 201 | } 202 | 203 | // TestFieldUsage tests the internal fieldUsage function ensure it returns the 204 | // expected text. 205 | func TestFieldUsage(t *testing.T) { 206 | t.Parallel() 207 | 208 | tests := []struct { 209 | name string 210 | field reflect.StructField 211 | defValue *reflect.Value 212 | expected string 213 | }{ 214 | { 215 | name: "jsonrpcusage tag override", 216 | field: func() reflect.StructField { 217 | type s struct { 218 | Test int `jsonrpcusage:"testvalue"` 219 | } 220 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 221 | }(), 222 | defValue: nil, 223 | expected: "testvalue", 224 | }, 225 | { 226 | name: "generic interface", 227 | field: func() reflect.StructField { 228 | type s struct { 229 | Test interface{} 230 | } 231 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 232 | }(), 233 | defValue: nil, 234 | expected: `test`, 235 | }, 236 | { 237 | name: "string without default value", 238 | field: func() reflect.StructField { 239 | type s struct { 240 | Test string 241 | } 242 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 243 | }(), 244 | defValue: nil, 245 | expected: `"test"`, 246 | }, 247 | { 248 | name: "string with default value", 249 | field: func() reflect.StructField { 250 | type s struct { 251 | Test string 252 | } 253 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 254 | }(), 255 | defValue: func() *reflect.Value { 256 | value := "default" 257 | rv := reflect.ValueOf(&value) 258 | return &rv 259 | }(), 260 | expected: `test="default"`, 261 | }, 262 | { 263 | name: "array of strings", 264 | field: func() reflect.StructField { 265 | type s struct { 266 | Test []string 267 | } 268 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 269 | }(), 270 | defValue: nil, 271 | expected: `["test",...]`, 272 | }, 273 | { 274 | name: "array of strings with plural field name 1", 275 | field: func() reflect.StructField { 276 | type s struct { 277 | Keys []string 278 | } 279 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 280 | }(), 281 | defValue: nil, 282 | expected: `["key",...]`, 283 | }, 284 | { 285 | name: "array of strings with plural field name 2", 286 | field: func() reflect.StructField { 287 | type s struct { 288 | Addresses []string 289 | } 290 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 291 | }(), 292 | defValue: nil, 293 | expected: `["address",...]`, 294 | }, 295 | { 296 | name: "array of strings with plural field name 3", 297 | field: func() reflect.StructField { 298 | type s struct { 299 | Capabilities []string 300 | } 301 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 302 | }(), 303 | defValue: nil, 304 | expected: `["capability",...]`, 305 | }, 306 | { 307 | name: "array of structs", 308 | field: func() reflect.StructField { 309 | type s2 struct { 310 | Txid string 311 | } 312 | type s struct { 313 | Capabilities []s2 314 | } 315 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 316 | }(), 317 | defValue: nil, 318 | expected: `[{"txid":"value"},...]`, 319 | }, 320 | { 321 | name: "array of ints", 322 | field: func() reflect.StructField { 323 | type s struct { 324 | Test []int 325 | } 326 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 327 | }(), 328 | defValue: nil, 329 | expected: `[test,...]`, 330 | }, 331 | { 332 | name: "sub struct with jsonrpcusage tag override", 333 | field: func() reflect.StructField { 334 | type s2 struct { 335 | Test string `jsonrpcusage:"testusage"` 336 | } 337 | type s struct { 338 | Test s2 339 | } 340 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 341 | }(), 342 | defValue: nil, 343 | expected: `{testusage}`, 344 | }, 345 | { 346 | name: "sub struct with string", 347 | field: func() reflect.StructField { 348 | type s2 struct { 349 | Txid string 350 | } 351 | type s struct { 352 | Test s2 353 | } 354 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 355 | }(), 356 | defValue: nil, 357 | expected: `{"txid":"value"}`, 358 | }, 359 | { 360 | name: "sub struct with int", 361 | field: func() reflect.StructField { 362 | type s2 struct { 363 | Vout int 364 | } 365 | type s struct { 366 | Test s2 367 | } 368 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 369 | }(), 370 | defValue: nil, 371 | expected: `{"vout":n}`, 372 | }, 373 | { 374 | name: "sub struct with float", 375 | field: func() reflect.StructField { 376 | type s2 struct { 377 | Amount float64 378 | } 379 | type s struct { 380 | Test s2 381 | } 382 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 383 | }(), 384 | defValue: nil, 385 | expected: `{"amount":n.nnn}`, 386 | }, 387 | { 388 | name: "sub struct with sub struct", 389 | field: func() reflect.StructField { 390 | type s3 struct { 391 | Amount float64 392 | } 393 | type s2 struct { 394 | Template s3 395 | } 396 | type s struct { 397 | Test s2 398 | } 399 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 400 | }(), 401 | defValue: nil, 402 | expected: `{"template":{"amount":n.nnn}}`, 403 | }, 404 | { 405 | name: "sub struct with slice", 406 | field: func() reflect.StructField { 407 | type s2 struct { 408 | Capabilities []string 409 | } 410 | type s struct { 411 | Test s2 412 | } 413 | return reflect.TypeOf((*s)(nil)).Elem().Field(0) 414 | }(), 415 | defValue: nil, 416 | expected: `{"capabilities":["capability",...]}`, 417 | }, 418 | } 419 | 420 | t.Logf("Running %d tests", len(tests)) 421 | for i, test := range tests { 422 | // Ensure usage matches the expected value. 423 | usage := btcjson.TstFieldUsage(test.field, test.defValue) 424 | if usage != test.expected { 425 | t.Errorf("Test #%d (%s) mismatched usage - got %v, "+ 426 | "want %v", i, test.name, usage, test.expected) 427 | continue 428 | } 429 | } 430 | } 431 | -------------------------------------------------------------------------------- /btcjson/cmdparse.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | "reflect" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | // makeParams creates a slice of interface values for the given struct. 16 | func makeParams(rt reflect.Type, rv reflect.Value) []interface{} { 17 | numFields := rt.NumField() 18 | params := make([]interface{}, 0, numFields) 19 | for i := 0; i < numFields; i++ { 20 | rtf := rt.Field(i) 21 | rvf := rv.Field(i) 22 | if rtf.Type.Kind() == reflect.Ptr { 23 | if rvf.IsNil() { 24 | break 25 | } 26 | rvf.Elem() 27 | } 28 | params = append(params, rvf.Interface()) 29 | } 30 | 31 | return params 32 | } 33 | 34 | // MarshalCmd marshals the passed command to a JSON-RPC request byte slice that 35 | // is suitable for transmission to an RPC server. The provided command type 36 | // must be a registered type. All commands provided by this package are 37 | // registered by default. 38 | func MarshalCmd(id interface{}, cmd interface{}) ([]byte, error) { 39 | // Look up the cmd type and error out if not registered. 40 | rt := reflect.TypeOf(cmd) 41 | registerLock.RLock() 42 | method, ok := concreteTypeToMethod[rt] 43 | registerLock.RUnlock() 44 | if !ok { 45 | str := fmt.Sprintf("%q is not registered", method) 46 | return nil, makeError(ErrUnregisteredMethod, str) 47 | } 48 | 49 | // The provided command must not be nil. 50 | rv := reflect.ValueOf(cmd) 51 | if rv.IsNil() { 52 | str := "the specified command is nil" 53 | return nil, makeError(ErrInvalidType, str) 54 | } 55 | 56 | // Create a slice of interface values in the order of the struct fields 57 | // while respecting pointer fields as optional params and only adding 58 | // them if they are non-nil. 59 | params := makeParams(rt.Elem(), rv.Elem()) 60 | 61 | // Generate and marshal the final JSON-RPC request. 62 | rawCmd, err := NewRequest(id, method, params) 63 | if err != nil { 64 | return nil, err 65 | } 66 | return json.Marshal(rawCmd) 67 | } 68 | 69 | // checkNumParams ensures the supplied number of params is at least the minimum 70 | // required number for the command and less than the maximum allowed. 71 | func checkNumParams(numParams int, info *methodInfo) error { 72 | if numParams < info.numReqParams || numParams > info.maxParams { 73 | if info.numReqParams == info.maxParams { 74 | str := fmt.Sprintf("wrong number of params (expected "+ 75 | "%d, received %d)", info.numReqParams, 76 | numParams) 77 | return makeError(ErrNumParams, str) 78 | } 79 | 80 | str := fmt.Sprintf("wrong number of params (expected "+ 81 | "between %d and %d, received %d)", info.numReqParams, 82 | info.maxParams, numParams) 83 | return makeError(ErrNumParams, str) 84 | } 85 | 86 | return nil 87 | } 88 | 89 | // populateDefaults populates default values into any remaining optional struct 90 | // fields that did not have parameters explicitly provided. The caller should 91 | // have previously checked that the number of parameters being passed is at 92 | // least the required number of parameters to avoid unnecessary work in this 93 | // function, but since required fields never have default values, it will work 94 | // properly even without the check. 95 | func populateDefaults(numParams int, info *methodInfo, rv reflect.Value) { 96 | // When there are no more parameters left in the supplied parameters, 97 | // any remaining struct fields must be optional. Thus, populate them 98 | // with their associated default value as needed. 99 | for i := numParams; i < info.maxParams; i++ { 100 | rvf := rv.Field(i) 101 | if defaultVal, ok := info.defaults[i]; ok { 102 | rvf.Set(defaultVal) 103 | } 104 | } 105 | } 106 | 107 | // UnmarshalCmd unmarshals a JSON-RPC request into a suitable concrete command 108 | // so long as the method type contained within the marshalled request is 109 | // registered. 110 | func UnmarshalCmd(r *Request) (interface{}, error) { 111 | registerLock.RLock() 112 | rtp, ok := methodToConcreteType[r.Method] 113 | info := methodToInfo[r.Method] 114 | registerLock.RUnlock() 115 | if !ok { 116 | str := fmt.Sprintf("%q is not registered", r.Method) 117 | return nil, makeError(ErrUnregisteredMethod, str) 118 | } 119 | rt := rtp.Elem() 120 | rvp := reflect.New(rt) 121 | rv := rvp.Elem() 122 | 123 | // Ensure the number of parameters are correct. 124 | numParams := len(r.Params) 125 | if err := checkNumParams(numParams, &info); err != nil { 126 | return nil, err 127 | } 128 | 129 | // Loop through each of the struct fields and unmarshal the associated 130 | // parameter into them. 131 | for i := 0; i < numParams; i++ { 132 | rvf := rv.Field(i) 133 | // Unmarshal the parameter into the struct field. 134 | concreteVal := rvf.Addr().Interface() 135 | if err := json.Unmarshal(r.Params[i], &concreteVal); err != nil { 136 | // The most common error is the wrong type, so 137 | // explicitly detect that error and make it nicer. 138 | fieldName := strings.ToLower(rt.Field(i).Name) 139 | if jerr, ok := err.(*json.UnmarshalTypeError); ok { 140 | str := fmt.Sprintf("parameter #%d '%s' must "+ 141 | "be type %v (got %v)", i+1, fieldName, 142 | jerr.Type, jerr.Value) 143 | return nil, makeError(ErrInvalidType, str) 144 | } 145 | 146 | // Fallback to showing the underlying error. 147 | str := fmt.Sprintf("parameter #%d '%s' failed to "+ 148 | "unmarshal: %v", i+1, fieldName, err) 149 | return nil, makeError(ErrInvalidType, str) 150 | } 151 | } 152 | 153 | // When there are less supplied parameters than the total number of 154 | // params, any remaining struct fields must be optional. Thus, populate 155 | // them with their associated default value as needed. 156 | if numParams < info.maxParams { 157 | populateDefaults(numParams, &info, rv) 158 | } 159 | 160 | return rvp.Interface(), nil 161 | } 162 | 163 | // isNumeric returns whether the passed reflect kind is a signed or unsigned 164 | // integer of any magnitude or a float of any magnitude. 165 | func isNumeric(kind reflect.Kind) bool { 166 | switch kind { 167 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, 168 | reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 169 | reflect.Uint64, reflect.Float32, reflect.Float64: 170 | 171 | return true 172 | } 173 | 174 | return false 175 | } 176 | 177 | // typesMaybeCompatible returns whether the source type can possibly be 178 | // assigned to the destination type. This is intended as a relatively quick 179 | // check to weed out obviously invalid conversions. 180 | func typesMaybeCompatible(dest reflect.Type, src reflect.Type) bool { 181 | // The same types are obviously compatible. 182 | if dest == src { 183 | return true 184 | } 185 | 186 | // When both types are numeric, they are potentially compatible. 187 | srcKind := src.Kind() 188 | destKind := dest.Kind() 189 | if isNumeric(destKind) && isNumeric(srcKind) { 190 | return true 191 | } 192 | 193 | if srcKind == reflect.String { 194 | // Strings can potentially be converted to numeric types. 195 | if isNumeric(destKind) { 196 | return true 197 | } 198 | 199 | switch destKind { 200 | // Strings can potentially be converted to bools by 201 | // strconv.ParseBool. 202 | case reflect.Bool: 203 | return true 204 | 205 | // Strings can be converted to any other type which has as 206 | // underlying type of string. 207 | case reflect.String: 208 | return true 209 | 210 | // Strings can potentially be converted to arrays, slice, 211 | // structs, and maps via json.Unmarshal. 212 | case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map: 213 | return true 214 | } 215 | } 216 | 217 | return false 218 | } 219 | 220 | // baseType returns the type of the argument after indirecting through all 221 | // pointers along with how many indirections were necessary. 222 | func baseType(arg reflect.Type) (reflect.Type, int) { 223 | var numIndirects int 224 | for arg.Kind() == reflect.Ptr { 225 | arg = arg.Elem() 226 | numIndirects++ 227 | } 228 | return arg, numIndirects 229 | } 230 | 231 | // assignField is the main workhorse for the NewCmd function which handles 232 | // assigning the provided source value to the destination field. It supports 233 | // direct type assignments, indirection, conversion of numeric types, and 234 | // unmarshaling of strings into arrays, slices, structs, and maps via 235 | // json.Unmarshal. 236 | func assignField(paramNum int, fieldName string, dest reflect.Value, src reflect.Value) error { 237 | // Just error now when the types have no chance of being compatible. 238 | destBaseType, destIndirects := baseType(dest.Type()) 239 | srcBaseType, srcIndirects := baseType(src.Type()) 240 | if !typesMaybeCompatible(destBaseType, srcBaseType) { 241 | str := fmt.Sprintf("parameter #%d '%s' must be type %v (got "+ 242 | "%v)", paramNum, fieldName, destBaseType, srcBaseType) 243 | return makeError(ErrInvalidType, str) 244 | } 245 | 246 | // Check if it's possible to simply set the dest to the provided source. 247 | // This is the case when the base types are the same or they are both 248 | // pointers that can be indirected to be the same without needing to 249 | // create pointers for the destination field. 250 | if destBaseType == srcBaseType && srcIndirects >= destIndirects { 251 | for i := 0; i < srcIndirects-destIndirects; i++ { 252 | src = src.Elem() 253 | } 254 | dest.Set(src) 255 | return nil 256 | } 257 | 258 | // When the destination has more indirects than the source, the extra 259 | // pointers have to be created. Only create enough pointers to reach 260 | // the same level of indirection as the source so the dest can simply be 261 | // set to the provided source when the types are the same. 262 | destIndirectsRemaining := destIndirects 263 | if destIndirects > srcIndirects { 264 | indirectDiff := destIndirects - srcIndirects 265 | for i := 0; i < indirectDiff; i++ { 266 | dest.Set(reflect.New(dest.Type().Elem())) 267 | dest = dest.Elem() 268 | destIndirectsRemaining-- 269 | } 270 | } 271 | 272 | if destBaseType == srcBaseType { 273 | dest.Set(src) 274 | return nil 275 | } 276 | 277 | // Make any remaining pointers needed to get to the base dest type since 278 | // the above direct assign was not possible and conversions are done 279 | // against the base types. 280 | for i := 0; i < destIndirectsRemaining; i++ { 281 | dest.Set(reflect.New(dest.Type().Elem())) 282 | dest = dest.Elem() 283 | } 284 | 285 | // Indirect through to the base source value. 286 | for src.Kind() == reflect.Ptr { 287 | src = src.Elem() 288 | } 289 | 290 | // Perform supported type conversions. 291 | switch src.Kind() { 292 | // Source value is a signed integer of various magnitude. 293 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 294 | reflect.Int64: 295 | 296 | switch dest.Kind() { 297 | // Destination is a signed integer of various magnitude. 298 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 299 | reflect.Int64: 300 | 301 | srcInt := src.Int() 302 | if dest.OverflowInt(srcInt) { 303 | str := fmt.Sprintf("parameter #%d '%s' "+ 304 | "overflows destination type %v", 305 | paramNum, fieldName, destBaseType) 306 | return makeError(ErrInvalidType, str) 307 | } 308 | 309 | dest.SetInt(srcInt) 310 | 311 | // Destination is an unsigned integer of various magnitude. 312 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 313 | reflect.Uint64: 314 | 315 | srcInt := src.Int() 316 | if srcInt < 0 || dest.OverflowUint(uint64(srcInt)) { 317 | str := fmt.Sprintf("parameter #%d '%s' "+ 318 | "overflows destination type %v", 319 | paramNum, fieldName, destBaseType) 320 | return makeError(ErrInvalidType, str) 321 | } 322 | dest.SetUint(uint64(srcInt)) 323 | 324 | default: 325 | str := fmt.Sprintf("parameter #%d '%s' must be type "+ 326 | "%v (got %v)", paramNum, fieldName, destBaseType, 327 | srcBaseType) 328 | return makeError(ErrInvalidType, str) 329 | } 330 | 331 | // Source value is an unsigned integer of various magnitude. 332 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 333 | reflect.Uint64: 334 | 335 | switch dest.Kind() { 336 | // Destination is a signed integer of various magnitude. 337 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 338 | reflect.Int64: 339 | 340 | srcUint := src.Uint() 341 | if srcUint > uint64(1<<63)-1 { 342 | str := fmt.Sprintf("parameter #%d '%s' "+ 343 | "overflows destination type %v", 344 | paramNum, fieldName, destBaseType) 345 | return makeError(ErrInvalidType, str) 346 | } 347 | if dest.OverflowInt(int64(srcUint)) { 348 | str := fmt.Sprintf("parameter #%d '%s' "+ 349 | "overflows destination type %v", 350 | paramNum, fieldName, destBaseType) 351 | return makeError(ErrInvalidType, str) 352 | } 353 | dest.SetInt(int64(srcUint)) 354 | 355 | // Destination is an unsigned integer of various magnitude. 356 | case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, 357 | reflect.Uint64: 358 | 359 | srcUint := src.Uint() 360 | if dest.OverflowUint(srcUint) { 361 | str := fmt.Sprintf("parameter #%d '%s' "+ 362 | "overflows destination type %v", 363 | paramNum, fieldName, destBaseType) 364 | return makeError(ErrInvalidType, str) 365 | } 366 | dest.SetUint(srcUint) 367 | 368 | default: 369 | str := fmt.Sprintf("parameter #%d '%s' must be type "+ 370 | "%v (got %v)", paramNum, fieldName, destBaseType, 371 | srcBaseType) 372 | return makeError(ErrInvalidType, str) 373 | } 374 | 375 | // Source value is a float. 376 | case reflect.Float32, reflect.Float64: 377 | destKind := dest.Kind() 378 | if destKind != reflect.Float32 && destKind != reflect.Float64 { 379 | str := fmt.Sprintf("parameter #%d '%s' must be type "+ 380 | "%v (got %v)", paramNum, fieldName, destBaseType, 381 | srcBaseType) 382 | return makeError(ErrInvalidType, str) 383 | } 384 | 385 | srcFloat := src.Float() 386 | if dest.OverflowFloat(srcFloat) { 387 | str := fmt.Sprintf("parameter #%d '%s' overflows "+ 388 | "destination type %v", paramNum, fieldName, 389 | destBaseType) 390 | return makeError(ErrInvalidType, str) 391 | } 392 | dest.SetFloat(srcFloat) 393 | 394 | // Source value is a string. 395 | case reflect.String: 396 | switch dest.Kind() { 397 | // String -> bool 398 | case reflect.Bool: 399 | b, err := strconv.ParseBool(src.String()) 400 | if err != nil { 401 | str := fmt.Sprintf("parameter #%d '%s' must "+ 402 | "parse to a %v", paramNum, fieldName, 403 | destBaseType) 404 | return makeError(ErrInvalidType, str) 405 | } 406 | dest.SetBool(b) 407 | 408 | // String -> signed integer of varying size. 409 | case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, 410 | reflect.Int64: 411 | 412 | srcInt, err := strconv.ParseInt(src.String(), 0, 0) 413 | if err != nil { 414 | str := fmt.Sprintf("parameter #%d '%s' must "+ 415 | "parse to a %v", paramNum, fieldName, 416 | destBaseType) 417 | return makeError(ErrInvalidType, str) 418 | } 419 | if dest.OverflowInt(srcInt) { 420 | str := fmt.Sprintf("parameter #%d '%s' "+ 421 | "overflows destination type %v", 422 | paramNum, fieldName, destBaseType) 423 | return makeError(ErrInvalidType, str) 424 | } 425 | dest.SetInt(srcInt) 426 | 427 | // String -> unsigned integer of varying size. 428 | case reflect.Uint, reflect.Uint8, reflect.Uint16, 429 | reflect.Uint32, reflect.Uint64: 430 | 431 | srcUint, err := strconv.ParseUint(src.String(), 0, 0) 432 | if err != nil { 433 | str := fmt.Sprintf("parameter #%d '%s' must "+ 434 | "parse to a %v", paramNum, fieldName, 435 | destBaseType) 436 | return makeError(ErrInvalidType, str) 437 | } 438 | if dest.OverflowUint(srcUint) { 439 | str := fmt.Sprintf("parameter #%d '%s' "+ 440 | "overflows destination type %v", 441 | paramNum, fieldName, destBaseType) 442 | return makeError(ErrInvalidType, str) 443 | } 444 | dest.SetUint(srcUint) 445 | 446 | // String -> float of varying size. 447 | case reflect.Float32, reflect.Float64: 448 | srcFloat, err := strconv.ParseFloat(src.String(), 0) 449 | if err != nil { 450 | str := fmt.Sprintf("parameter #%d '%s' must "+ 451 | "parse to a %v", paramNum, fieldName, 452 | destBaseType) 453 | return makeError(ErrInvalidType, str) 454 | } 455 | if dest.OverflowFloat(srcFloat) { 456 | str := fmt.Sprintf("parameter #%d '%s' "+ 457 | "overflows destination type %v", 458 | paramNum, fieldName, destBaseType) 459 | return makeError(ErrInvalidType, str) 460 | } 461 | dest.SetFloat(srcFloat) 462 | 463 | // String -> string (typecast). 464 | case reflect.String: 465 | dest.SetString(src.String()) 466 | 467 | // String -> arrays, slices, structs, and maps via 468 | // json.Unmarshal. 469 | case reflect.Array, reflect.Slice, reflect.Struct, reflect.Map: 470 | concreteVal := dest.Addr().Interface() 471 | err := json.Unmarshal([]byte(src.String()), &concreteVal) 472 | if err != nil { 473 | str := fmt.Sprintf("parameter #%d '%s' must "+ 474 | "be valid JSON which unsmarshals to a %v", 475 | paramNum, fieldName, destBaseType) 476 | return makeError(ErrInvalidType, str) 477 | } 478 | dest.Set(reflect.ValueOf(concreteVal).Elem()) 479 | } 480 | } 481 | 482 | return nil 483 | } 484 | 485 | // NewCmd provides a generic mechanism to create a new command that can marshal 486 | // to a JSON-RPC request while respecting the requirements of the provided 487 | // method. The method must have been registered with the package already along 488 | // with its type definition. All methods associated with the commands exported 489 | // by this package are already registered by default. 490 | // 491 | // The arguments are most efficient when they are the exact same type as the 492 | // underlying field in the command struct associated with the the method, 493 | // however this function also will perform a variety of conversions to make it 494 | // more flexible. This allows, for example, command line args which are strings 495 | // to be passed unaltered. In particular, the following conversions are 496 | // supported: 497 | // 498 | // - Conversion between any size signed or unsigned integer so long as the 499 | // value does not overflow the destination type 500 | // - Conversion between float32 and float64 so long as the value does not 501 | // overflow the destination type 502 | // - Conversion from string to boolean for everything strconv.ParseBool 503 | // recognizes 504 | // - Conversion from string to any size integer for everything 505 | // strconv.ParseInt and strconv.ParseUint recognizes 506 | // - Conversion from string to any size float for everything 507 | // strconv.ParseFloat recognizes 508 | // - Conversion from string to arrays, slices, structs, and maps by treating 509 | // the string as marshalled JSON and calling json.Unmarshal into the 510 | // destination field 511 | func NewCmd(method string, args ...interface{}) (interface{}, error) { 512 | // Look up details about the provided method. Any methods that aren't 513 | // registered are an error. 514 | registerLock.RLock() 515 | rtp, ok := methodToConcreteType[method] 516 | info := methodToInfo[method] 517 | registerLock.RUnlock() 518 | if !ok { 519 | str := fmt.Sprintf("%q is not registered", method) 520 | return nil, makeError(ErrUnregisteredMethod, str) 521 | } 522 | 523 | // Ensure the number of parameters are correct. 524 | numParams := len(args) 525 | if err := checkNumParams(numParams, &info); err != nil { 526 | return nil, err 527 | } 528 | 529 | // Create the appropriate command type for the method. Since all types 530 | // are enforced to be a pointer to a struct at registration time, it's 531 | // safe to indirect to the struct now. 532 | rvp := reflect.New(rtp.Elem()) 533 | rv := rvp.Elem() 534 | rt := rtp.Elem() 535 | 536 | // Loop through each of the struct fields and assign the associated 537 | // parameter into them after checking its type validity. 538 | for i := 0; i < numParams; i++ { 539 | // Attempt to assign each of the arguments to the according 540 | // struct field. 541 | rvf := rv.Field(i) 542 | fieldName := strings.ToLower(rt.Field(i).Name) 543 | err := assignField(i+1, fieldName, rvf, reflect.ValueOf(args[i])) 544 | if err != nil { 545 | return nil, err 546 | } 547 | } 548 | 549 | return rvp.Interface(), nil 550 | } 551 | -------------------------------------------------------------------------------- /btcjson/cmdparse_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson_test 6 | 7 | import ( 8 | "encoding/json" 9 | "math" 10 | "reflect" 11 | "testing" 12 | 13 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 14 | ) 15 | 16 | // TestAssignField tests the assignField function handles supported combinations 17 | // properly. 18 | func TestAssignField(t *testing.T) { 19 | t.Parallel() 20 | 21 | tests := []struct { 22 | name string 23 | dest interface{} 24 | src interface{} 25 | expected interface{} 26 | }{ 27 | { 28 | name: "same types", 29 | dest: int8(0), 30 | src: int8(100), 31 | expected: int8(100), 32 | }, 33 | { 34 | name: "same types - more source pointers", 35 | dest: int8(0), 36 | src: func() interface{} { 37 | i := int8(100) 38 | return &i 39 | }(), 40 | expected: int8(100), 41 | }, 42 | { 43 | name: "same types - more dest pointers", 44 | dest: func() interface{} { 45 | i := int8(0) 46 | return &i 47 | }(), 48 | src: int8(100), 49 | expected: int8(100), 50 | }, 51 | { 52 | name: "convertible types - more source pointers", 53 | dest: int16(0), 54 | src: func() interface{} { 55 | i := int8(100) 56 | return &i 57 | }(), 58 | expected: int16(100), 59 | }, 60 | { 61 | name: "convertible types - both pointers", 62 | dest: func() interface{} { 63 | i := int8(0) 64 | return &i 65 | }(), 66 | src: func() interface{} { 67 | i := int16(100) 68 | return &i 69 | }(), 70 | expected: int8(100), 71 | }, 72 | { 73 | name: "convertible types - int16 -> int8", 74 | dest: int8(0), 75 | src: int16(100), 76 | expected: int8(100), 77 | }, 78 | { 79 | name: "convertible types - int16 -> uint8", 80 | dest: uint8(0), 81 | src: int16(100), 82 | expected: uint8(100), 83 | }, 84 | { 85 | name: "convertible types - uint16 -> int8", 86 | dest: int8(0), 87 | src: uint16(100), 88 | expected: int8(100), 89 | }, 90 | { 91 | name: "convertible types - uint16 -> uint8", 92 | dest: uint8(0), 93 | src: uint16(100), 94 | expected: uint8(100), 95 | }, 96 | { 97 | name: "convertible types - float32 -> float64", 98 | dest: float64(0), 99 | src: float32(1.5), 100 | expected: float64(1.5), 101 | }, 102 | { 103 | name: "convertible types - float64 -> float32", 104 | dest: float32(0), 105 | src: float64(1.5), 106 | expected: float32(1.5), 107 | }, 108 | { 109 | name: "convertible types - string -> bool", 110 | dest: false, 111 | src: "true", 112 | expected: true, 113 | }, 114 | { 115 | name: "convertible types - string -> int8", 116 | dest: int8(0), 117 | src: "100", 118 | expected: int8(100), 119 | }, 120 | { 121 | name: "convertible types - string -> uint8", 122 | dest: uint8(0), 123 | src: "100", 124 | expected: uint8(100), 125 | }, 126 | { 127 | name: "convertible types - string -> float32", 128 | dest: float32(0), 129 | src: "1.5", 130 | expected: float32(1.5), 131 | }, 132 | { 133 | name: "convertible types - typecase string -> string", 134 | dest: "", 135 | src: func() interface{} { 136 | type foo string 137 | return foo("foo") 138 | }(), 139 | expected: "foo", 140 | }, 141 | { 142 | name: "convertible types - string -> array", 143 | dest: [2]string{}, 144 | src: `["test","test2"]`, 145 | expected: [2]string{"test", "test2"}, 146 | }, 147 | { 148 | name: "convertible types - string -> slice", 149 | dest: []string{}, 150 | src: `["test","test2"]`, 151 | expected: []string{"test", "test2"}, 152 | }, 153 | { 154 | name: "convertible types - string -> struct", 155 | dest: struct{ A int }{}, 156 | src: `{"A":100}`, 157 | expected: struct{ A int }{100}, 158 | }, 159 | { 160 | name: "convertible types - string -> map", 161 | dest: map[string]float64{}, 162 | src: `{"1Address":1.5}`, 163 | expected: map[string]float64{"1Address": 1.5}, 164 | }, 165 | } 166 | 167 | t.Logf("Running %d tests", len(tests)) 168 | for i, test := range tests { 169 | dst := reflect.New(reflect.TypeOf(test.dest)).Elem() 170 | src := reflect.ValueOf(test.src) 171 | err := btcjson.TstAssignField(1, "testField", dst, src) 172 | if err != nil { 173 | t.Errorf("Test #%d (%s) unexpected error: %v", i, 174 | test.name, err) 175 | continue 176 | } 177 | 178 | // Inidirect through to the base types to ensure their values 179 | // are the same. 180 | for dst.Kind() == reflect.Ptr { 181 | dst = dst.Elem() 182 | } 183 | if !reflect.DeepEqual(dst.Interface(), test.expected) { 184 | t.Errorf("Test #%d (%s) unexpected value - got %v, "+ 185 | "want %v", i, test.name, dst.Interface(), 186 | test.expected) 187 | continue 188 | } 189 | } 190 | } 191 | 192 | // TestAssignFieldErrors tests the assignField function error paths. 193 | func TestAssignFieldErrors(t *testing.T) { 194 | t.Parallel() 195 | 196 | tests := []struct { 197 | name string 198 | dest interface{} 199 | src interface{} 200 | err btcjson.Error 201 | }{ 202 | { 203 | name: "general incompatible int -> string", 204 | dest: string(0), 205 | src: int(0), 206 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 207 | }, 208 | { 209 | name: "overflow source int -> dest int", 210 | dest: int8(0), 211 | src: int(128), 212 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 213 | }, 214 | { 215 | name: "overflow source int -> dest uint", 216 | dest: uint8(0), 217 | src: int(256), 218 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 219 | }, 220 | { 221 | name: "int -> float", 222 | dest: float32(0), 223 | src: int(256), 224 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 225 | }, 226 | { 227 | name: "overflow source uint64 -> dest int64", 228 | dest: int64(0), 229 | src: uint64(1 << 63), 230 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 231 | }, 232 | { 233 | name: "overflow source uint -> dest int", 234 | dest: int8(0), 235 | src: uint(128), 236 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 237 | }, 238 | { 239 | name: "overflow source uint -> dest uint", 240 | dest: uint8(0), 241 | src: uint(256), 242 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 243 | }, 244 | { 245 | name: "uint -> float", 246 | dest: float32(0), 247 | src: uint(256), 248 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 249 | }, 250 | { 251 | name: "float -> int", 252 | dest: int(0), 253 | src: float32(1.0), 254 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 255 | }, 256 | { 257 | name: "overflow float64 -> float32", 258 | dest: float32(0), 259 | src: float64(math.MaxFloat64), 260 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 261 | }, 262 | { 263 | name: "invalid string -> bool", 264 | dest: true, 265 | src: "foo", 266 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 267 | }, 268 | { 269 | name: "invalid string -> int", 270 | dest: int8(0), 271 | src: "foo", 272 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 273 | }, 274 | { 275 | name: "overflow string -> int", 276 | dest: int8(0), 277 | src: "128", 278 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 279 | }, 280 | { 281 | name: "invalid string -> uint", 282 | dest: uint8(0), 283 | src: "foo", 284 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 285 | }, 286 | { 287 | name: "overflow string -> uint", 288 | dest: uint8(0), 289 | src: "256", 290 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 291 | }, 292 | { 293 | name: "invalid string -> float", 294 | dest: float32(0), 295 | src: "foo", 296 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 297 | }, 298 | { 299 | name: "overflow string -> float", 300 | dest: float32(0), 301 | src: "1.7976931348623157e+308", 302 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 303 | }, 304 | { 305 | name: "invalid string -> array", 306 | dest: [3]int{}, 307 | src: "foo", 308 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 309 | }, 310 | { 311 | name: "invalid string -> slice", 312 | dest: []int{}, 313 | src: "foo", 314 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 315 | }, 316 | { 317 | name: "invalid string -> struct", 318 | dest: struct{ A int }{}, 319 | src: "foo", 320 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 321 | }, 322 | { 323 | name: "invalid string -> map", 324 | dest: map[string]int{}, 325 | src: "foo", 326 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 327 | }, 328 | } 329 | 330 | t.Logf("Running %d tests", len(tests)) 331 | for i, test := range tests { 332 | dst := reflect.New(reflect.TypeOf(test.dest)).Elem() 333 | src := reflect.ValueOf(test.src) 334 | err := btcjson.TstAssignField(1, "testField", dst, src) 335 | if reflect.TypeOf(err) != reflect.TypeOf(test.err) { 336 | t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+ 337 | "want %T", i, test.name, err, test.err) 338 | continue 339 | } 340 | gotErrorCode := err.(btcjson.Error).ErrorCode 341 | if gotErrorCode != test.err.ErrorCode { 342 | t.Errorf("Test #%d (%s) mismatched error code - got "+ 343 | "%v (%v), want %v", i, test.name, gotErrorCode, 344 | err, test.err.ErrorCode) 345 | continue 346 | } 347 | } 348 | } 349 | 350 | // TestNewCmdErrors ensures the error paths of NewCmd behave as expected. 351 | func TestNewCmdErrors(t *testing.T) { 352 | t.Parallel() 353 | 354 | tests := []struct { 355 | name string 356 | method string 357 | args []interface{} 358 | err btcjson.Error 359 | }{ 360 | { 361 | name: "unregistered command", 362 | method: "boguscommand", 363 | args: []interface{}{}, 364 | err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod}, 365 | }, 366 | { 367 | name: "too few parameters to command with required + optional", 368 | method: "getblock", 369 | args: []interface{}{}, 370 | err: btcjson.Error{ErrorCode: btcjson.ErrNumParams}, 371 | }, 372 | { 373 | name: "too many parameters to command with no optional", 374 | method: "getblockcount", 375 | args: []interface{}{"123"}, 376 | err: btcjson.Error{ErrorCode: btcjson.ErrNumParams}, 377 | }, 378 | { 379 | name: "incorrect parameter type", 380 | method: "getblock", 381 | args: []interface{}{1}, 382 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 383 | }, 384 | } 385 | 386 | t.Logf("Running %d tests", len(tests)) 387 | for i, test := range tests { 388 | _, err := btcjson.NewCmd(test.method, test.args...) 389 | if reflect.TypeOf(err) != reflect.TypeOf(test.err) { 390 | t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+ 391 | "want %T", i, test.name, err, err, test.err) 392 | continue 393 | } 394 | gotErrorCode := err.(btcjson.Error).ErrorCode 395 | if gotErrorCode != test.err.ErrorCode { 396 | t.Errorf("Test #%d (%s) mismatched error code - got "+ 397 | "%v (%v), want %v", i, test.name, gotErrorCode, 398 | err, test.err.ErrorCode) 399 | continue 400 | } 401 | } 402 | } 403 | 404 | // TestMarshalCmdErrors tests the error paths of the MarshalCmd function. 405 | func TestMarshalCmdErrors(t *testing.T) { 406 | t.Parallel() 407 | 408 | tests := []struct { 409 | name string 410 | id interface{} 411 | cmd interface{} 412 | err btcjson.Error 413 | }{ 414 | { 415 | name: "unregistered type", 416 | id: 1, 417 | cmd: (*int)(nil), 418 | err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod}, 419 | }, 420 | { 421 | name: "nil instance of registered type", 422 | id: 1, 423 | cmd: (*btcjson.GetBlockCmd)(nil), 424 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 425 | }, 426 | { 427 | name: "nil instance of registered type", 428 | id: []int{0, 1}, 429 | cmd: &btcjson.GetBlockCountCmd{}, 430 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 431 | }, 432 | } 433 | 434 | t.Logf("Running %d tests", len(tests)) 435 | for i, test := range tests { 436 | _, err := btcjson.MarshalCmd(test.id, test.cmd) 437 | if reflect.TypeOf(err) != reflect.TypeOf(test.err) { 438 | t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+ 439 | "want %T", i, test.name, err, err, test.err) 440 | continue 441 | } 442 | gotErrorCode := err.(btcjson.Error).ErrorCode 443 | if gotErrorCode != test.err.ErrorCode { 444 | t.Errorf("Test #%d (%s) mismatched error code - got "+ 445 | "%v (%v), want %v", i, test.name, gotErrorCode, 446 | err, test.err.ErrorCode) 447 | continue 448 | } 449 | } 450 | } 451 | 452 | // TestUnmarshalCmdErrors tests the error paths of the UnmarshalCmd function. 453 | func TestUnmarshalCmdErrors(t *testing.T) { 454 | t.Parallel() 455 | 456 | tests := []struct { 457 | name string 458 | request btcjson.Request 459 | err btcjson.Error 460 | }{ 461 | { 462 | name: "unregistered type", 463 | request: btcjson.Request{ 464 | Jsonrpc: "1.0", 465 | Method: "bogusmethod", 466 | Params: nil, 467 | ID: nil, 468 | }, 469 | err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod}, 470 | }, 471 | { 472 | name: "incorrect number of params", 473 | request: btcjson.Request{ 474 | Jsonrpc: "1.0", 475 | Method: "getblockcount", 476 | Params: []json.RawMessage{[]byte(`"bogusparam"`)}, 477 | ID: nil, 478 | }, 479 | err: btcjson.Error{ErrorCode: btcjson.ErrNumParams}, 480 | }, 481 | { 482 | name: "invalid type for a parameter", 483 | request: btcjson.Request{ 484 | Jsonrpc: "1.0", 485 | Method: "getblock", 486 | Params: []json.RawMessage{[]byte("1")}, 487 | ID: nil, 488 | }, 489 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 490 | }, 491 | { 492 | name: "invalid JSON for a parameter", 493 | request: btcjson.Request{ 494 | Jsonrpc: "1.0", 495 | Method: "getblock", 496 | Params: []json.RawMessage{[]byte(`"1`)}, 497 | ID: nil, 498 | }, 499 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 500 | }, 501 | } 502 | 503 | t.Logf("Running %d tests", len(tests)) 504 | for i, test := range tests { 505 | _, err := btcjson.UnmarshalCmd(&test.request) 506 | if reflect.TypeOf(err) != reflect.TypeOf(test.err) { 507 | t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+ 508 | "want %T", i, test.name, err, err, test.err) 509 | continue 510 | } 511 | gotErrorCode := err.(btcjson.Error).ErrorCode 512 | if gotErrorCode != test.err.ErrorCode { 513 | t.Errorf("Test #%d (%s) mismatched error code - got "+ 514 | "%v (%v), want %v", i, test.name, gotErrorCode, 515 | err, test.err.ErrorCode) 516 | continue 517 | } 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /btcjson/doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package btcjson provides primitives for working with the bitcoin JSON-RPC API. 7 | 8 | Overview 9 | 10 | When communicating via the JSON-RPC protocol, all of the commands need to be 11 | marshalled to and from the the wire in the appropriate format. This package 12 | provides data structures and primitives to ease this process. 13 | 14 | In addition, it also provides some additional features such as custom command 15 | registration, command categorization, and reflection-based help generation. 16 | 17 | JSON-RPC Protocol Overview 18 | 19 | This information is not necessary in order to use this package, but it does 20 | provide some intuition into what the marshalling and unmarshalling that is 21 | discussed below is doing under the hood. 22 | 23 | As defined by the JSON-RPC spec, there are effectively two forms of messages on 24 | the wire: 25 | 26 | - Request Objects 27 | {"jsonrpc":"1.0","id":"SOMEID","method":"SOMEMETHOD","params":[SOMEPARAMS]} 28 | NOTE: Notifications are the same format except the id field is null. 29 | 30 | - Response Objects 31 | {"result":SOMETHING,"error":null,"id":"SOMEID"} 32 | {"result":null,"error":{"code":SOMEINT,"message":SOMESTRING},"id":"SOMEID"} 33 | 34 | For requests, the params field can vary in what it contains depending on the 35 | method (a.k.a. command) being sent. Each parameter can be as simple as an int 36 | or a complex structure containing many nested fields. The id field is used to 37 | identify a request and will be included in the associated response. 38 | 39 | When working with asynchronous transports, such as websockets, spontaneous 40 | notifications are also possible. As indicated, they are the same as a request 41 | object, except they have the id field set to null. Therefore, servers will 42 | ignore requests with the id field set to null, while clients can choose to 43 | consume or ignore them. 44 | 45 | Unfortunately, the original Bitcoin JSON-RPC API (and hence anything compatible 46 | with it) doesn't always follow the spec and will sometimes return an error 47 | string in the result field with a null error for certain commands. However, 48 | for the most part, the error field will be set as described on failure. 49 | 50 | Marshalling and Unmarshalling 51 | 52 | Based upon the discussion above, it should be easy to see how the types of this 53 | package map into the required parts of the protocol 54 | 55 | - Request Objects (type Request) 56 | - Commands (type Cmd) 57 | - Notifications (type Ntfn) 58 | - Response Objects (type Response) 59 | - Result (type Result) 60 | 61 | To simplify the marshalling of the requests and responses, the MarshalCmd and 62 | MarshalResponse functions are provided. They return the raw bytes ready to be 63 | sent across the wire. 64 | 65 | Unmarshalling a received Request object is a two step process: 66 | 1) Unmarshal the raw bytes into a Request struct instance via json.Unmarshal 67 | 2) Use UnmarshalCmd on the Result field of the unmarshalled Request to create 68 | a concrete command or notification instance with all struct fields set 69 | accordingly 70 | 71 | This approach is used since it provides the caller with access to the additional 72 | fields in the request that are not part of the command such as the ID. 73 | 74 | Unmarshalling a received Response object is also a two step process: 75 | 1) Unmarhsal the raw bytes into a Response struct instance via json.Unmarshal 76 | 2) Depending on the ID, unmarshal the Result field of the unmarshalled 77 | Response to create a concrete type instance 78 | 79 | As above, this approach is used since it provides the caller with access to the 80 | fields in the response such as the ID and Error. 81 | 82 | Command Creation 83 | 84 | This package provides two approaches for creating a new command. This first, 85 | and preferred, method is to use one of the NewCmd functions. This allows 86 | static compile-time checking to help ensure the parameters stay in sync with 87 | the struct definitions. 88 | 89 | The second approach is the NewCmd function which takes a method (command) name 90 | and variable arguments. The function includes full checking to ensure the 91 | parameters are accurate according to provided method, however these checks are, 92 | obviously, run-time which means any mistakes won't be found until the code is 93 | actually executed. However, it is quite useful for user-supplied commands 94 | that are intentionally dynamic. 95 | 96 | Custom Command Registration 97 | 98 | The command handling of this package is built around the concept of registered 99 | commands. This is true for the wide variety of commands already provided by the 100 | package, but it also means caller can easily provide custom commands with all 101 | of the same functionality as the built-in commands. Use the RegisterCmd 102 | function for this purpose. 103 | 104 | A list of all registered methods can be obtained with the RegisteredCmdMethods 105 | function. 106 | 107 | Command Inspection 108 | 109 | All registered commands are registered with flags that identify information such 110 | as whether the command applies to a chain server, wallet server, or is a 111 | notification along with the method name to use. These flags can be obtained 112 | with the MethodUsageFlags flags, and the method can be obtained with the 113 | CmdMethod function. 114 | 115 | Help Generation 116 | 117 | To facilitate providing consistent help to users of the RPC server, this package 118 | exposes the GenerateHelp and function which uses reflection on registered 119 | commands or notifications, as well as the provided expected result types, to 120 | generate the final help text. 121 | 122 | In addition, the MethodUsageText function is provided to generate consistent 123 | one-line usage for registered commands and notifications using reflection. 124 | 125 | Errors 126 | 127 | There are 2 distinct type of errors supported by this package: 128 | 129 | - General errors related to marshalling or unmarshalling or improper use of 130 | the package (type Error) 131 | - RPC errors which are intended to be returned across the wire as a part of 132 | the JSON-RPC response (type RPCError) 133 | 134 | The first category of errors (type Error) typically indicates a programmer error 135 | and can be avoided by properly using the API. Errors of this type will be 136 | returned from the various functions available in this package. They identify 137 | issues such as unsupported field types, attempts to register malformed commands, 138 | and attempting to create a new command with an improper number of parameters. 139 | The specific reason for the error can be detected by type asserting it to a 140 | *btcjson.Error and accessing the ErrorCode field. 141 | 142 | The second category of errors (type RPCError), on the other hand, are useful for 143 | returning errors to RPC clients. Consequently, they are used in the previously 144 | described Response type. 145 | */ 146 | package btcjson 147 | -------------------------------------------------------------------------------- /btcjson/error.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | // ErrorCode identifies a kind of error. These error codes are NOT used for 12 | // JSON-RPC response errors. 13 | type ErrorCode int 14 | 15 | // These constants are used to identify a specific RuleError. 16 | const ( 17 | // ErrDuplicateMethod indicates a command with the specified method 18 | // already exists. 19 | ErrDuplicateMethod ErrorCode = iota 20 | 21 | // ErrInvalidUsageFlags indicates one or more unrecognized flag bits 22 | // were specified. 23 | ErrInvalidUsageFlags 24 | 25 | // ErrInvalidType indicates a type was passed that is not the required 26 | // type. 27 | ErrInvalidType 28 | 29 | // ErrEmbeddedType indicates the provided command struct contains an 30 | // embedded type which is not not supported. 31 | ErrEmbeddedType 32 | 33 | // ErrUnexportedField indiciates the provided command struct contains an 34 | // unexported field which is not supported. 35 | ErrUnexportedField 36 | 37 | // ErrUnsupportedFieldType indicates the type of a field in the provided 38 | // command struct is not one of the supported types. 39 | ErrUnsupportedFieldType 40 | 41 | // ErrNonOptionalField indicates a non-optional field was specified 42 | // after an optional field. 43 | ErrNonOptionalField 44 | 45 | // ErrNonOptionalDefault indicates a 'jsonrpcdefault' struct tag was 46 | // specified for a non-optional field. 47 | ErrNonOptionalDefault 48 | 49 | // ErrMismatchedDefault indicates a 'jsonrpcdefault' struct tag contains 50 | // a value that doesn't match the type of the field. 51 | ErrMismatchedDefault 52 | 53 | // ErrUnregisteredMethod indicates a method was specified that has not 54 | // been registered. 55 | ErrUnregisteredMethod 56 | 57 | // ErrMissingDescription indicates a description required to generate 58 | // help is missing. 59 | ErrMissingDescription 60 | 61 | // ErrNumParams inidcates the number of params supplied do not 62 | // match the requirements of the associated command. 63 | ErrNumParams 64 | 65 | // numErrorCodes is the maximum error code number used in tests. 66 | numErrorCodes 67 | ) 68 | 69 | // Map of ErrorCode values back to their constant names for pretty printing. 70 | var errorCodeStrings = map[ErrorCode]string{ 71 | ErrDuplicateMethod: "ErrDuplicateMethod", 72 | ErrInvalidUsageFlags: "ErrInvalidUsageFlags", 73 | ErrInvalidType: "ErrInvalidType", 74 | ErrEmbeddedType: "ErrEmbeddedType", 75 | ErrUnexportedField: "ErrUnexportedField", 76 | ErrUnsupportedFieldType: "ErrUnsupportedFieldType", 77 | ErrNonOptionalField: "ErrNonOptionalField", 78 | ErrNonOptionalDefault: "ErrNonOptionalDefault", 79 | ErrMismatchedDefault: "ErrMismatchedDefault", 80 | ErrUnregisteredMethod: "ErrUnregisteredMethod", 81 | ErrMissingDescription: "ErrMissingDescription", 82 | ErrNumParams: "ErrNumParams", 83 | } 84 | 85 | // String returns the ErrorCode as a human-readable name. 86 | func (e ErrorCode) String() string { 87 | if s := errorCodeStrings[e]; s != "" { 88 | return s 89 | } 90 | return fmt.Sprintf("Unknown ErrorCode (%d)", int(e)) 91 | } 92 | 93 | // Error identifies a general error. This differs from an RPCError in that this 94 | // error typically is used more by the consumers of the package as opposed to 95 | // RPCErrors which are intended to be returned to the client across the wire via 96 | // a JSON-RPC Response. The caller can use type assertions to determine the 97 | // specific error and access the ErrorCode field. 98 | type Error struct { 99 | ErrorCode ErrorCode // Describes the kind of error 100 | Description string // Human readable description of the issue 101 | } 102 | 103 | // Error satisfies the error interface and prints human-readable errors. 104 | func (e Error) Error() string { 105 | return e.Description 106 | } 107 | 108 | // makeError creates an Error given a set of arguments. 109 | func makeError(c ErrorCode, desc string) Error { 110 | return Error{ErrorCode: c, Description: desc} 111 | } 112 | -------------------------------------------------------------------------------- /btcjson/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson_test 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 11 | ) 12 | 13 | // TestErrorCodeStringer tests the stringized output for the ErrorCode type. 14 | func TestErrorCodeStringer(t *testing.T) { 15 | t.Parallel() 16 | 17 | tests := []struct { 18 | in btcjson.ErrorCode 19 | want string 20 | }{ 21 | {btcjson.ErrDuplicateMethod, "ErrDuplicateMethod"}, 22 | {btcjson.ErrInvalidUsageFlags, "ErrInvalidUsageFlags"}, 23 | {btcjson.ErrInvalidType, "ErrInvalidType"}, 24 | {btcjson.ErrEmbeddedType, "ErrEmbeddedType"}, 25 | {btcjson.ErrUnexportedField, "ErrUnexportedField"}, 26 | {btcjson.ErrUnsupportedFieldType, "ErrUnsupportedFieldType"}, 27 | {btcjson.ErrNonOptionalField, "ErrNonOptionalField"}, 28 | {btcjson.ErrNonOptionalDefault, "ErrNonOptionalDefault"}, 29 | {btcjson.ErrMismatchedDefault, "ErrMismatchedDefault"}, 30 | {btcjson.ErrUnregisteredMethod, "ErrUnregisteredMethod"}, 31 | {btcjson.ErrNumParams, "ErrNumParams"}, 32 | {btcjson.ErrMissingDescription, "ErrMissingDescription"}, 33 | {0xffff, "Unknown ErrorCode (65535)"}, 34 | } 35 | 36 | // Detect additional error codes that don't have the stringer added. 37 | if len(tests)-1 != int(btcjson.TstNumErrorCodes) { 38 | t.Errorf("It appears an error code was added without adding an " + 39 | "associated stringer test") 40 | } 41 | 42 | t.Logf("Running %d tests", len(tests)) 43 | for i, test := range tests { 44 | result := test.in.String() 45 | if result != test.want { 46 | t.Errorf("String #%d\n got: %s want: %s", i, result, 47 | test.want) 48 | continue 49 | } 50 | } 51 | } 52 | 53 | // TestError tests the error output for the Error type. 54 | func TestError(t *testing.T) { 55 | t.Parallel() 56 | 57 | tests := []struct { 58 | in btcjson.Error 59 | want string 60 | }{ 61 | { 62 | btcjson.Error{Description: "some error"}, 63 | "some error", 64 | }, 65 | { 66 | btcjson.Error{Description: "human-readable error"}, 67 | "human-readable error", 68 | }, 69 | } 70 | 71 | t.Logf("Running %d tests", len(tests)) 72 | for i, test := range tests { 73 | result := test.in.Error() 74 | if result != test.want { 75 | t.Errorf("Error #%d\n got: %s want: %s", i, result, 76 | test.want) 77 | continue 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /btcjson/example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson_test 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | 11 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 12 | ) 13 | 14 | // This example demonstrates how to create and marshal a command into a JSON-RPC 15 | // request. 16 | func ExampleMarshalCmd() { 17 | // Create a new getblock command. Notice the nil parameter indicates 18 | // to use the default parameter for that fields. This is a common 19 | // pattern used in all of the NewCmd functions in this package for 20 | // optional fields. Also, notice the call to btcjson.Bool which is a 21 | // convenience function for creating a pointer out of a primitive for 22 | // optional parameters. 23 | blockHash := "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" 24 | gbCmd := btcjson.NewGetBlockCmd(blockHash, btcjson.Bool(false), nil) 25 | 26 | // Marshal the command to the format suitable for sending to the RPC 27 | // server. Typically the client would increment the id here which is 28 | // request so the response can be identified. 29 | id := 1 30 | marshalledBytes, err := btcjson.MarshalCmd(id, gbCmd) 31 | if err != nil { 32 | fmt.Println(err) 33 | return 34 | } 35 | 36 | // Display the marshalled command. Ordinarily this would be sent across 37 | // the wire to the RPC server, but for this example, just display it. 38 | fmt.Printf("%s\n", marshalledBytes) 39 | 40 | // Output: 41 | // {"jsonrpc":"1.0","method":"getblock","params":["000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",false],"id":1} 42 | } 43 | 44 | // This example demonstrates how to unmarshal a JSON-RPC request and then 45 | // unmarshal the concrete request into a concrete command. 46 | func ExampleUnmarshalCmd() { 47 | // Ordinarily this would be read from the wire, but for this example, 48 | // it is hard coded here for clarity. 49 | data := []byte(`{"jsonrpc":"1.0","method":"getblock","params":["000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f",false],"id":1}`) 50 | 51 | // Unmarshal the raw bytes from the wire into a JSON-RPC request. 52 | var request btcjson.Request 53 | if err := json.Unmarshal(data, &request); err != nil { 54 | fmt.Println(err) 55 | return 56 | } 57 | 58 | // Typically there isn't any need to examine the request fields directly 59 | // like this as the caller already knows what response to expect based 60 | // on the command it sent. However, this is done here to demonstrate 61 | // why the unmarshal process is two steps. 62 | if request.ID == nil { 63 | fmt.Println("Unexpected notification") 64 | return 65 | } 66 | if request.Method != "getblock" { 67 | fmt.Println("Unexpected method") 68 | return 69 | } 70 | 71 | // Unmarshal the request into a concrete command. 72 | cmd, err := btcjson.UnmarshalCmd(&request) 73 | if err != nil { 74 | fmt.Println(err) 75 | return 76 | } 77 | 78 | // Type assert the command to the appropriate type. 79 | gbCmd, ok := cmd.(*btcjson.GetBlockCmd) 80 | if !ok { 81 | fmt.Printf("Incorrect command type: %T\n", cmd) 82 | return 83 | } 84 | 85 | // Display the fields in the concrete command. 86 | fmt.Println("Hash:", gbCmd.Hash) 87 | fmt.Println("Verbose:", *gbCmd.Verbose) 88 | fmt.Println("VerboseTx:", *gbCmd.VerboseTx) 89 | 90 | // Output: 91 | // Hash: 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f 92 | // Verbose: false 93 | // VerboseTx: false 94 | } 95 | 96 | // This example demonstrates how to marshal a JSON-RPC response. 97 | func ExampleMarshalResponse() { 98 | // Marshal a new JSON-RPC response. For example, this is a response 99 | // to a getblockheight request. 100 | marshalledBytes, err := btcjson.MarshalResponse(1, 350001, nil) 101 | if err != nil { 102 | fmt.Println(err) 103 | return 104 | } 105 | 106 | // Display the marshalled response. Ordinarily this would be sent 107 | // across the wire to the RPC client, but for this example, just display 108 | // it. 109 | fmt.Printf("%s\n", marshalledBytes) 110 | 111 | // Output: 112 | // {"result":350001,"error":null,"id":1} 113 | } 114 | 115 | // This example demonstrates how to unmarshal a JSON-RPC response and then 116 | // unmarshal the result field in the response to a concrete type. 117 | func Example_unmarshalResponse() { 118 | // Ordinarily this would be read from the wire, but for this example, 119 | // it is hard coded here for clarity. This is an example response to a 120 | // getblockheight request. 121 | data := []byte(`{"result":350001,"error":null,"id":1}`) 122 | 123 | // Unmarshal the raw bytes from the wire into a JSON-RPC response. 124 | var response btcjson.Response 125 | if err := json.Unmarshal(data, &response); err != nil { 126 | fmt.Println("Malformed JSON-RPC response:", err) 127 | return 128 | } 129 | 130 | // Check the response for an error from the server. For example, the 131 | // server might return an error if an invalid/unknown block hash is 132 | // requested. 133 | if response.Error != nil { 134 | fmt.Println(response.Error) 135 | return 136 | } 137 | 138 | // Unmarshal the result into the expected type for the response. 139 | var blockHeight int32 140 | if err := json.Unmarshal(response.Result, &blockHeight); err != nil { 141 | fmt.Printf("Unexpected result type: %T\n", response.Result) 142 | return 143 | } 144 | fmt.Println("Block height:", blockHeight) 145 | 146 | // Output: 147 | // Block height: 350001 148 | } 149 | -------------------------------------------------------------------------------- /btcjson/export_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson 6 | 7 | // TstHighestUsageFlagBit makes the internal highestUsageFlagBit parameter 8 | // available to the test package. 9 | var TstHighestUsageFlagBit = highestUsageFlagBit 10 | 11 | // TstNumErrorCodes makes the internal numErrorCodes parameter available to the 12 | // test package. 13 | var TstNumErrorCodes = numErrorCodes 14 | 15 | // TstAssignField makes the internal assignField function available to the test 16 | // package. 17 | var TstAssignField = assignField 18 | 19 | // TstFieldUsage makes the internal fieldUsage function available to the test 20 | // package. 21 | var TstFieldUsage = fieldUsage 22 | 23 | // TstReflectTypeToJSONType makes the internal reflectTypeToJSONType function 24 | // available to the test package. 25 | var TstReflectTypeToJSONType = reflectTypeToJSONType 26 | 27 | // TstResultStructHelp makes the internal resultStructHelp function available to 28 | // the test package. 29 | var TstResultStructHelp = resultStructHelp 30 | 31 | // TstReflectTypeToJSONExample makes the internal reflectTypeToJSONExample 32 | // function available to the test package. 33 | var TstReflectTypeToJSONExample = reflectTypeToJSONExample 34 | 35 | // TstResultTypeHelp makes the internal resultTypeHelp function available to the 36 | // test package. 37 | var TstResultTypeHelp = resultTypeHelp 38 | 39 | // TstArgHelp makes the internal argHelp function available to the test package. 40 | var TstArgHelp = argHelp 41 | 42 | // TestMethodHelp makes the internal methodHelp function available to the test 43 | // package. 44 | var TestMethodHelp = methodHelp 45 | 46 | // TstIsValidResultType makes the internal isValidResultType function available 47 | // to the test package. 48 | var TstIsValidResultType = isValidResultType 49 | -------------------------------------------------------------------------------- /btcjson/help.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2015 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson 6 | 7 | import ( 8 | "bytes" 9 | "fmt" 10 | "reflect" 11 | "strings" 12 | "text/tabwriter" 13 | ) 14 | 15 | // baseHelpDescs house the various help labels, types, and example values used 16 | // when generating help. The per-command synopsis, field descriptions, 17 | // conditions, and result descriptions are to be provided by the caller. 18 | var baseHelpDescs = map[string]string{ 19 | // Misc help labels and output. 20 | "help-arguments": "Arguments", 21 | "help-arguments-none": "None", 22 | "help-result": "Result", 23 | "help-result-nothing": "Nothing", 24 | "help-default": "default", 25 | "help-optional": "optional", 26 | "help-required": "required", 27 | 28 | // JSON types. 29 | "json-type-numeric": "numeric", 30 | "json-type-string": "string", 31 | "json-type-bool": "boolean", 32 | "json-type-array": "array of ", 33 | "json-type-object": "object", 34 | "json-type-value": "value", 35 | 36 | // JSON examples. 37 | "json-example-string": "value", 38 | "json-example-bool": "true|false", 39 | "json-example-map-data": "data", 40 | "json-example-unknown": "unknown", 41 | } 42 | 43 | // descLookupFunc is a function which is used to lookup a description given 44 | // a key. 45 | type descLookupFunc func(string) string 46 | 47 | // reflectTypeToJSONType returns a string that represents the JSON type 48 | // associated with the provided Go type. 49 | func reflectTypeToJSONType(xT descLookupFunc, rt reflect.Type) string { 50 | kind := rt.Kind() 51 | if isNumeric(kind) { 52 | return xT("json-type-numeric") 53 | } 54 | 55 | switch kind { 56 | case reflect.String: 57 | return xT("json-type-string") 58 | 59 | case reflect.Bool: 60 | return xT("json-type-bool") 61 | 62 | case reflect.Array, reflect.Slice: 63 | return xT("json-type-array") + reflectTypeToJSONType(xT, 64 | rt.Elem()) 65 | 66 | case reflect.Struct: 67 | return xT("json-type-object") 68 | 69 | case reflect.Map: 70 | return xT("json-type-object") 71 | } 72 | 73 | return xT("json-type-value") 74 | } 75 | 76 | // resultStructHelp returns a slice of strings containing the result help output 77 | // for a struct. Each line makes use of tabs to separate the relevant pieces so 78 | // a tabwriter can be used later to line everything up. The descriptions are 79 | // pulled from the active help descriptions map based on the lowercase version 80 | // of the provided reflect type and json name (or the lowercase version of the 81 | // field name if no json tag was specified). 82 | func resultStructHelp(xT descLookupFunc, rt reflect.Type, indentLevel int) []string { 83 | indent := strings.Repeat(" ", indentLevel) 84 | typeName := strings.ToLower(rt.Name()) 85 | 86 | // Generate the help for each of the fields in the result struct. 87 | numField := rt.NumField() 88 | results := make([]string, 0, numField) 89 | for i := 0; i < numField; i++ { 90 | rtf := rt.Field(i) 91 | 92 | // The field name to display is the json name when it's 93 | // available, otherwise use the lowercase field name. 94 | var fieldName string 95 | if tag := rtf.Tag.Get("json"); tag != "" { 96 | fieldName = strings.Split(tag, ",")[0] 97 | } else { 98 | fieldName = strings.ToLower(rtf.Name) 99 | } 100 | 101 | // Deference pointer if needed. 102 | rtfType := rtf.Type 103 | if rtfType.Kind() == reflect.Ptr { 104 | rtfType = rtf.Type.Elem() 105 | } 106 | 107 | // Generate the JSON example for the result type of this struct 108 | // field. When it is a complex type, examine the type and 109 | // adjust the opening bracket and brace combination accordingly. 110 | fieldType := reflectTypeToJSONType(xT, rtfType) 111 | fieldDescKey := typeName + "-" + fieldName 112 | fieldExamples, isComplex := reflectTypeToJSONExample(xT, 113 | rtfType, indentLevel, fieldDescKey) 114 | if isComplex { 115 | var brace string 116 | kind := rtfType.Kind() 117 | if kind == reflect.Array || kind == reflect.Slice { 118 | brace = "[{" 119 | } else { 120 | brace = "{" 121 | } 122 | result := fmt.Sprintf("%s\"%s\": %s\t(%s)\t%s", indent, 123 | fieldName, brace, fieldType, xT(fieldDescKey)) 124 | results = append(results, result) 125 | results = append(results, fieldExamples...) 126 | } else { 127 | result := fmt.Sprintf("%s\"%s\": %s,\t(%s)\t%s", indent, 128 | fieldName, fieldExamples[0], fieldType, 129 | xT(fieldDescKey)) 130 | results = append(results, result) 131 | } 132 | } 133 | 134 | return results 135 | } 136 | 137 | // reflectTypeToJSONExample generates example usage in the format used by the 138 | // help output. It handles arrays, slices and structs recursively. The output 139 | // is returned as a slice of lines so the final help can be nicely aligned via 140 | // a tab writer. A bool is also returned which specifies whether or not the 141 | // type results in a complex JSON object since they need to be handled 142 | // differently. 143 | func reflectTypeToJSONExample(xT descLookupFunc, rt reflect.Type, indentLevel int, fieldDescKey string) ([]string, bool) { 144 | // Indirect pointer if needed. 145 | if rt.Kind() == reflect.Ptr { 146 | rt = rt.Elem() 147 | } 148 | kind := rt.Kind() 149 | if isNumeric(kind) { 150 | if kind == reflect.Float32 || kind == reflect.Float64 { 151 | return []string{"n.nnn"}, false 152 | } 153 | 154 | return []string{"n"}, false 155 | } 156 | 157 | switch kind { 158 | case reflect.String: 159 | return []string{`"` + xT("json-example-string") + `"`}, false 160 | 161 | case reflect.Bool: 162 | return []string{xT("json-example-bool")}, false 163 | 164 | case reflect.Struct: 165 | indent := strings.Repeat(" ", indentLevel) 166 | results := resultStructHelp(xT, rt, indentLevel+1) 167 | 168 | // An opening brace is needed for the first indent level. For 169 | // all others, it will be included as a part of the previous 170 | // field. 171 | if indentLevel == 0 { 172 | newResults := make([]string, len(results)+1) 173 | newResults[0] = "{" 174 | copy(newResults[1:], results) 175 | results = newResults 176 | } 177 | 178 | // The closing brace has a comma after it except for the first 179 | // indent level. The final tabs are necessary so the tab writer 180 | // lines things up properly. 181 | closingBrace := indent + "}" 182 | if indentLevel > 0 { 183 | closingBrace += "," 184 | } 185 | results = append(results, closingBrace+"\t\t") 186 | return results, true 187 | 188 | case reflect.Array, reflect.Slice: 189 | results, isComplex := reflectTypeToJSONExample(xT, rt.Elem(), 190 | indentLevel, fieldDescKey) 191 | 192 | // When the result is complex, it is because this is an array of 193 | // objects. 194 | if isComplex { 195 | // When this is at indent level zero, there is no 196 | // previous field to house the opening array bracket, so 197 | // replace the opening object brace with the array 198 | // syntax. Also, replace the final closing object brace 199 | // with the variadiac array closing syntax. 200 | indent := strings.Repeat(" ", indentLevel) 201 | if indentLevel == 0 { 202 | results[0] = indent + "[{" 203 | results[len(results)-1] = indent + "},...]" 204 | return results, true 205 | } 206 | 207 | // At this point, the indent level is greater than 0, so 208 | // the opening array bracket and object brace are 209 | // already a part of the previous field. However, the 210 | // closing entry is a simple object brace, so replace it 211 | // with the variadiac array closing syntax. The final 212 | // tabs are necessary so the tab writer lines things up 213 | // properly. 214 | results[len(results)-1] = indent + "},...],\t\t" 215 | return results, true 216 | } 217 | 218 | // It's an array of primitives, so return the formatted text 219 | // accordingly. 220 | return []string{fmt.Sprintf("[%s,...]", results[0])}, false 221 | 222 | case reflect.Map: 223 | indent := strings.Repeat(" ", indentLevel) 224 | results := make([]string, 0, 3) 225 | 226 | // An opening brace is needed for the first indent level. For 227 | // all others, it will be included as a part of the previous 228 | // field. 229 | if indentLevel == 0 { 230 | results = append(results, indent+"{") 231 | } 232 | 233 | // Maps are a bit special in that they need to have the key, 234 | // value, and description of the object entry specifically 235 | // called out. 236 | innerIndent := strings.Repeat(" ", indentLevel+1) 237 | result := fmt.Sprintf("%s%q: %s, (%s) %s", innerIndent, 238 | xT(fieldDescKey+"--key"), xT(fieldDescKey+"--value"), 239 | reflectTypeToJSONType(xT, rt), xT(fieldDescKey+"--desc")) 240 | results = append(results, result) 241 | results = append(results, innerIndent+"...") 242 | 243 | results = append(results, indent+"}") 244 | return results, true 245 | } 246 | 247 | return []string{xT("json-example-unknown")}, false 248 | } 249 | 250 | // resultTypeHelp generates and returns formatted help for the provided result 251 | // type. 252 | func resultTypeHelp(xT descLookupFunc, rt reflect.Type, fieldDescKey string) string { 253 | // Generate the JSON example for the result type. 254 | results, isComplex := reflectTypeToJSONExample(xT, rt, 0, fieldDescKey) 255 | 256 | // When this is a primitive type, add the associated JSON type and 257 | // result description into the final string, format it accordingly, 258 | // and return it. 259 | if !isComplex { 260 | return fmt.Sprintf("%s (%s) %s", results[0], 261 | reflectTypeToJSONType(xT, rt), xT(fieldDescKey)) 262 | } 263 | 264 | // At this point, this is a complex type that already has the JSON types 265 | // and descriptions in the results. Thus, use a tab writer to nicely 266 | // align the help text. 267 | var formatted bytes.Buffer 268 | w := new(tabwriter.Writer) 269 | w.Init(&formatted, 0, 4, 1, ' ', 0) 270 | for i, text := range results { 271 | if i == len(results)-1 { 272 | fmt.Fprintf(w, text) 273 | } else { 274 | fmt.Fprintln(w, text) 275 | } 276 | } 277 | w.Flush() 278 | return formatted.String() 279 | } 280 | 281 | // argTypeHelp returns the type of provided command argument as a string in the 282 | // format used by the help output. In particular, it includes the JSON type 283 | // (boolean, numeric, string, array, object) along with optional and the default 284 | // value if applicable. 285 | func argTypeHelp(xT descLookupFunc, structField reflect.StructField, defaultVal *reflect.Value) string { 286 | // Indirect the pointer if needed and track if it's an optional field. 287 | fieldType := structField.Type 288 | var isOptional bool 289 | if fieldType.Kind() == reflect.Ptr { 290 | fieldType = fieldType.Elem() 291 | isOptional = true 292 | } 293 | 294 | // When there is a default value, it must also be a pointer due to the 295 | // rules enforced by RegisterCmd. 296 | if defaultVal != nil { 297 | indirect := defaultVal.Elem() 298 | defaultVal = &indirect 299 | } 300 | 301 | // Convert the field type to a JSON type. 302 | details := make([]string, 0, 3) 303 | details = append(details, reflectTypeToJSONType(xT, fieldType)) 304 | 305 | // Add optional and default value to the details if needed. 306 | if isOptional { 307 | details = append(details, xT("help-optional")) 308 | 309 | // Add the default value if there is one. This is only checked 310 | // when the field is optional since a non-optional field can't 311 | // have a default value. 312 | if defaultVal != nil { 313 | val := defaultVal.Interface() 314 | if defaultVal.Kind() == reflect.String { 315 | val = fmt.Sprintf(`"%s"`, val) 316 | } 317 | str := fmt.Sprintf("%s=%v", xT("help-default"), val) 318 | details = append(details, str) 319 | } 320 | } else { 321 | details = append(details, xT("help-required")) 322 | } 323 | 324 | return strings.Join(details, ", ") 325 | } 326 | 327 | // argHelp generates and returns formatted help for the provided command. 328 | func argHelp(xT descLookupFunc, rtp reflect.Type, defaults map[int]reflect.Value, method string) string { 329 | // Return now if the command has no arguments. 330 | rt := rtp.Elem() 331 | numFields := rt.NumField() 332 | if numFields == 0 { 333 | return "" 334 | } 335 | 336 | // Generate the help for each argument in the command. Several 337 | // simplifying assumptions are made here because the RegisterCmd 338 | // function has already rigorously enforced the layout. 339 | args := make([]string, 0, numFields) 340 | for i := 0; i < numFields; i++ { 341 | rtf := rt.Field(i) 342 | var defaultVal *reflect.Value 343 | if defVal, ok := defaults[i]; ok { 344 | defaultVal = &defVal 345 | } 346 | 347 | fieldName := strings.ToLower(rtf.Name) 348 | helpText := fmt.Sprintf("%d.\t%s\t(%s)\t%s", i+1, fieldName, 349 | argTypeHelp(xT, rtf, defaultVal), 350 | xT(method+"-"+fieldName)) 351 | args = append(args, helpText) 352 | 353 | // For types which require a JSON object, or an array of JSON 354 | // objects, generate the full syntax for the argument. 355 | fieldType := rtf.Type 356 | if fieldType.Kind() == reflect.Ptr { 357 | fieldType = fieldType.Elem() 358 | } 359 | kind := fieldType.Kind() 360 | switch kind { 361 | case reflect.Struct: 362 | fieldDescKey := fmt.Sprintf("%s-%s", method, fieldName) 363 | resultText := resultTypeHelp(xT, fieldType, fieldDescKey) 364 | args = append(args, resultText) 365 | 366 | case reflect.Map: 367 | fieldDescKey := fmt.Sprintf("%s-%s", method, fieldName) 368 | resultText := resultTypeHelp(xT, fieldType, fieldDescKey) 369 | args = append(args, resultText) 370 | 371 | case reflect.Array, reflect.Slice: 372 | fieldDescKey := fmt.Sprintf("%s-%s", method, fieldName) 373 | if rtf.Type.Elem().Kind() == reflect.Struct { 374 | resultText := resultTypeHelp(xT, fieldType, 375 | fieldDescKey) 376 | args = append(args, resultText) 377 | } 378 | } 379 | } 380 | 381 | // Add argument names, types, and descriptions if there are any. Use a 382 | // tab writer to nicely align the help text. 383 | var formatted bytes.Buffer 384 | w := new(tabwriter.Writer) 385 | w.Init(&formatted, 0, 4, 1, ' ', 0) 386 | for _, text := range args { 387 | fmt.Fprintln(w, text) 388 | } 389 | w.Flush() 390 | return formatted.String() 391 | } 392 | 393 | // methodHelp generates and returns the help output for the provided command 394 | // and method info. This is the main work horse for the exported MethodHelp 395 | // function. 396 | func methodHelp(xT descLookupFunc, rtp reflect.Type, defaults map[int]reflect.Value, method string, resultTypes []interface{}) string { 397 | // Start off with the method usage and help synopsis. 398 | help := fmt.Sprintf("%s\n\n%s\n", methodUsageText(rtp, defaults, method), 399 | xT(method+"--synopsis")) 400 | 401 | // Generate the help for each argument in the command. 402 | if argText := argHelp(xT, rtp, defaults, method); argText != "" { 403 | help += fmt.Sprintf("\n%s:\n%s", xT("help-arguments"), 404 | argText) 405 | } else { 406 | help += fmt.Sprintf("\n%s:\n%s\n", xT("help-arguments"), 407 | xT("help-arguments-none")) 408 | } 409 | 410 | // Generate the help text for each result type. 411 | resultTexts := make([]string, 0, len(resultTypes)) 412 | for i := range resultTypes { 413 | rtp := reflect.TypeOf(resultTypes[i]) 414 | fieldDescKey := fmt.Sprintf("%s--result%d", method, i) 415 | if resultTypes[i] == nil { 416 | resultText := xT("help-result-nothing") 417 | resultTexts = append(resultTexts, resultText) 418 | continue 419 | } 420 | 421 | resultText := resultTypeHelp(xT, rtp.Elem(), fieldDescKey) 422 | resultTexts = append(resultTexts, resultText) 423 | } 424 | 425 | // Add result types and descriptions. When there is more than one 426 | // result type, also add the condition which triggers it. 427 | if len(resultTexts) > 1 { 428 | for i, resultText := range resultTexts { 429 | condKey := fmt.Sprintf("%s--condition%d", method, i) 430 | help += fmt.Sprintf("\n%s (%s):\n%s\n", 431 | xT("help-result"), xT(condKey), resultText) 432 | } 433 | } else if len(resultTexts) > 0 { 434 | help += fmt.Sprintf("\n%s:\n%s\n", xT("help-result"), 435 | resultTexts[0]) 436 | } else { 437 | help += fmt.Sprintf("\n%s:\n%s\n", xT("help-result"), 438 | xT("help-result-nothing")) 439 | } 440 | return help 441 | } 442 | 443 | // isValidResultType returns whether the passed reflect kind is one of the 444 | // acceptable types for results. 445 | func isValidResultType(kind reflect.Kind) bool { 446 | if isNumeric(kind) { 447 | return true 448 | } 449 | 450 | switch kind { 451 | case reflect.String, reflect.Struct, reflect.Array, reflect.Slice, 452 | reflect.Bool, reflect.Map: 453 | 454 | return true 455 | } 456 | 457 | return false 458 | } 459 | 460 | // GenerateHelp generates and returns help output for the provided method and 461 | // result types given a map to provide the appropriate keys for the method 462 | // synopsis, field descriptions, conditions, and result descriptions. The 463 | // method must be associated with a registered type. All commands provided by 464 | // this package are registered by default. 465 | // 466 | // The resultTypes must be pointer-to-types which represent the specific types 467 | // of values the command returns. For example, if the command only returns a 468 | // boolean value, there should only be a single entry of (*bool)(nil). Note 469 | // that each type must be a single pointer to the type. Therefore, it is 470 | // recommended to simply pass a nil pointer cast to the appropriate type as 471 | // previously shown. 472 | // 473 | // The provided descriptions map must contain all of the keys or an error will 474 | // be returned which includes the missing key, or the final missing key when 475 | // there is more than one key missing. The generated help in the case of such 476 | // an error will use the key in place of the description. 477 | // 478 | // The following outlines the required keys: 479 | // "--synopsis" Synopsis for the command 480 | // "-" Description for each command argument 481 | // "-" Description for each object field 482 | // "--condition<#>" Description for each result condition 483 | // "--result<#>" Description for each primitive result num 484 | // 485 | // Notice that the "special" keys synopsis, condition<#>, and result<#> are 486 | // preceded by a double dash to ensure they don't conflict with field names. 487 | // 488 | // The condition keys are only required when there is more than on result type, 489 | // and the result key for a given result type is only required if it's not an 490 | // object. 491 | // 492 | // For example, consider the 'help' command itself. There are two possible 493 | // returns depending on the provided parameters. So, the help would be 494 | // generated by calling the function as follows: 495 | // GenerateHelp("help", descs, (*string)(nil), (*string)(nil)). 496 | // 497 | // The following keys would then be required in the provided descriptions map: 498 | // 499 | // "help--synopsis": "Returns a list of all commands or help for ...." 500 | // "help-command": "The command to retrieve help for", 501 | // "help--condition0": "no command provided" 502 | // "help--condition1": "command specified" 503 | // "help--result0": "List of commands" 504 | // "help--result1": "Help for specified command" 505 | func GenerateHelp(method string, descs map[string]string, resultTypes ...interface{}) (string, error) { 506 | // Look up details about the provided method and error out if not 507 | // registered. 508 | registerLock.RLock() 509 | rtp, ok := methodToConcreteType[method] 510 | info := methodToInfo[method] 511 | registerLock.RUnlock() 512 | if !ok { 513 | str := fmt.Sprintf("%q is not registered", method) 514 | return "", makeError(ErrUnregisteredMethod, str) 515 | } 516 | 517 | // Validate each result type is a pointer to a supported type (or nil). 518 | for i, resultType := range resultTypes { 519 | if resultType == nil { 520 | continue 521 | } 522 | 523 | rtp := reflect.TypeOf(resultType) 524 | if rtp.Kind() != reflect.Ptr { 525 | str := fmt.Sprintf("result #%d (%v) is not a pointer", 526 | i, rtp.Kind()) 527 | return "", makeError(ErrInvalidType, str) 528 | } 529 | 530 | elemKind := rtp.Elem().Kind() 531 | if !isValidResultType(elemKind) { 532 | str := fmt.Sprintf("result #%d (%v) is not an allowed "+ 533 | "type", i, elemKind) 534 | return "", makeError(ErrInvalidType, str) 535 | } 536 | } 537 | 538 | // Create a closure for the description lookup function which falls back 539 | // to the base help descriptions map for unrecognized keys and tracks 540 | // and missing keys. 541 | var missingKey string 542 | xT := func(key string) string { 543 | if desc, ok := descs[key]; ok { 544 | return desc 545 | } 546 | if desc, ok := baseHelpDescs[key]; ok { 547 | return desc 548 | } 549 | 550 | missingKey = key 551 | return key 552 | } 553 | 554 | // Generate and return the help for the method. 555 | help := methodHelp(xT, rtp, info.defaults, method, resultTypes) 556 | if missingKey != "" { 557 | return help, makeError(ErrMissingDescription, missingKey) 558 | } 559 | return help, nil 560 | } 561 | -------------------------------------------------------------------------------- /btcjson/helpers.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson 6 | 7 | // Bool is a helper routine that allocates a new bool value to store v and 8 | // returns a pointer to it. This is useful when assigning optional parameters. 9 | func Bool(v bool) *bool { 10 | p := new(bool) 11 | *p = v 12 | return p 13 | } 14 | 15 | // Int is a helper routine that allocates a new int value to store v and 16 | // returns a pointer to it. This is useful when assigning optional parameters. 17 | func Int(v int) *int { 18 | p := new(int) 19 | *p = v 20 | return p 21 | } 22 | 23 | // Uint is a helper routine that allocates a new uint value to store v and 24 | // returns a pointer to it. This is useful when assigning optional parameters. 25 | func Uint(v uint) *uint { 26 | p := new(uint) 27 | *p = v 28 | return p 29 | } 30 | 31 | // Int32 is a helper routine that allocates a new int32 value to store v and 32 | // returns a pointer to it. This is useful when assigning optional parameters. 33 | func Int32(v int32) *int32 { 34 | p := new(int32) 35 | *p = v 36 | return p 37 | } 38 | 39 | // Uint32 is a helper routine that allocates a new uint32 value to store v and 40 | // returns a pointer to it. This is useful when assigning optional parameters. 41 | func Uint32(v uint32) *uint32 { 42 | p := new(uint32) 43 | *p = v 44 | return p 45 | } 46 | 47 | // Int64 is a helper routine that allocates a new int64 value to store v and 48 | // returns a pointer to it. This is useful when assigning optional parameters. 49 | func Int64(v int64) *int64 { 50 | p := new(int64) 51 | *p = v 52 | return p 53 | } 54 | 55 | // Uint64 is a helper routine that allocates a new uint64 value to store v and 56 | // returns a pointer to it. This is useful when assigning optional parameters. 57 | func Uint64(v uint64) *uint64 { 58 | p := new(uint64) 59 | *p = v 60 | return p 61 | } 62 | 63 | // Float64 is a helper routine that allocates a new float64 value to store v and 64 | // returns a pointer to it. This is useful when assigning optional parameters. 65 | func Float64(v float64) *float64 { 66 | p := new(float64) 67 | *p = v 68 | return p 69 | } 70 | 71 | // String is a helper routine that allocates a new string value to store v and 72 | // returns a pointer to it. This is useful when assigning optional parameters. 73 | func String(v string) *string { 74 | p := new(string) 75 | *p = v 76 | return p 77 | } 78 | -------------------------------------------------------------------------------- /btcjson/helpers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson_test 6 | 7 | import ( 8 | "reflect" 9 | "testing" 10 | 11 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 12 | ) 13 | 14 | // TestHelpers tests the various helper functions which create pointers to 15 | // primitive types. 16 | func TestHelpers(t *testing.T) { 17 | t.Parallel() 18 | 19 | tests := []struct { 20 | name string 21 | f func() interface{} 22 | expected interface{} 23 | }{ 24 | { 25 | name: "bool", 26 | f: func() interface{} { 27 | return btcjson.Bool(true) 28 | }, 29 | expected: func() interface{} { 30 | val := true 31 | return &val 32 | }(), 33 | }, 34 | { 35 | name: "int", 36 | f: func() interface{} { 37 | return btcjson.Int(5) 38 | }, 39 | expected: func() interface{} { 40 | val := int(5) 41 | return &val 42 | }(), 43 | }, 44 | { 45 | name: "uint", 46 | f: func() interface{} { 47 | return btcjson.Uint(5) 48 | }, 49 | expected: func() interface{} { 50 | val := uint(5) 51 | return &val 52 | }(), 53 | }, 54 | { 55 | name: "int32", 56 | f: func() interface{} { 57 | return btcjson.Int32(5) 58 | }, 59 | expected: func() interface{} { 60 | val := int32(5) 61 | return &val 62 | }(), 63 | }, 64 | { 65 | name: "uint32", 66 | f: func() interface{} { 67 | return btcjson.Uint32(5) 68 | }, 69 | expected: func() interface{} { 70 | val := uint32(5) 71 | return &val 72 | }(), 73 | }, 74 | { 75 | name: "int64", 76 | f: func() interface{} { 77 | return btcjson.Int64(5) 78 | }, 79 | expected: func() interface{} { 80 | val := int64(5) 81 | return &val 82 | }(), 83 | }, 84 | { 85 | name: "uint64", 86 | f: func() interface{} { 87 | return btcjson.Uint64(5) 88 | }, 89 | expected: func() interface{} { 90 | val := uint64(5) 91 | return &val 92 | }(), 93 | }, 94 | { 95 | name: "string", 96 | f: func() interface{} { 97 | return btcjson.String("abc") 98 | }, 99 | expected: func() interface{} { 100 | val := "abc" 101 | return &val 102 | }(), 103 | }, 104 | } 105 | 106 | t.Logf("Running %d tests", len(tests)) 107 | for i, test := range tests { 108 | result := test.f() 109 | if !reflect.DeepEqual(result, test.expected) { 110 | t.Errorf("Test #%d (%s) unexpected value - got %v, "+ 111 | "want %v", i, test.name, result, test.expected) 112 | continue 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /btcjson/jsonrpc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | ) 11 | 12 | // RPCErrorCode represents an error code to be used as a part of an RPCError 13 | // which is in turn used in a JSON-RPC Response object. 14 | // 15 | // A specific type is used to help ensure the wrong errors aren't used. 16 | type RPCErrorCode int 17 | 18 | // RPCError represents an error that is used as a part of a JSON-RPC Response 19 | // object. 20 | type RPCError struct { 21 | Code RPCErrorCode `json:"code,omitempty"` 22 | Message string `json:"message,omitempty"` 23 | } 24 | 25 | // Guarantee RPCError satisifies the builtin error interface. 26 | var _, _ error = RPCError{}, (*RPCError)(nil) 27 | 28 | // Error returns a string describing the RPC error. This satisifies the 29 | // builtin error interface. 30 | func (e RPCError) Error() string { 31 | return fmt.Sprintf("%d: %s", e.Code, e.Message) 32 | } 33 | 34 | // NewRPCError constructs and returns a new JSON-RPC error that is suitable 35 | // for use in a JSON-RPC Response object. 36 | func NewRPCError(code RPCErrorCode, message string) *RPCError { 37 | return &RPCError{ 38 | Code: code, 39 | Message: message, 40 | } 41 | } 42 | 43 | // IsValidIDType checks that the ID field (which can go in any of the JSON-RPC 44 | // requests, responses, or notifications) is valid. JSON-RPC 1.0 allows any 45 | // valid JSON type. JSON-RPC 2.0 (which bitcoind follows for some parts) only 46 | // allows string, number, or null, so this function restricts the allowed types 47 | // to that list. This function is only provided in case the caller is manually 48 | // marshalling for some reason. The functions which accept an ID in this 49 | // package already call this function to ensure the provided id is valid. 50 | func IsValidIDType(id interface{}) bool { 51 | switch id.(type) { 52 | case int, int8, int16, int32, int64, 53 | uint, uint8, uint16, uint32, uint64, 54 | float32, float64, 55 | string, 56 | nil: 57 | return true 58 | default: 59 | return false 60 | } 61 | } 62 | 63 | // Request is a type for raw JSON-RPC 1.0 requests. The Method field identifies 64 | // the specific command type which in turns leads to different parameters. 65 | // Callers typically will not use this directly since this package provides a 66 | // statically typed command infrastructure which handles creation of these 67 | // requests, however this struct it being exported in case the caller wants to 68 | // construct raw requests for some reason. 69 | type Request struct { 70 | Jsonrpc string `json:"jsonrpc"` 71 | Method string `json:"method"` 72 | Params []json.RawMessage `json:"params"` 73 | ID interface{} `json:"id"` 74 | } 75 | 76 | // NewRequest returns a new JSON-RPC 1.0 request object given the provided id, 77 | // method, and parameters. The parameters are marshalled into a json.RawMessage 78 | // for the Params field of the returned request object. This function is only 79 | // provided in case the caller wants to construct raw requests for some reason. 80 | // 81 | // Typically callers will instead want to create a registered concrete command 82 | // type with the NewCmd or NewCmd functions and call the MarshalCmd 83 | // function with that command to generate the marshalled JSON-RPC request. 84 | func NewRequest(id interface{}, method string, params []interface{}) (*Request, error) { 85 | if !IsValidIDType(id) { 86 | str := fmt.Sprintf("the id of type '%T' is invalid", id) 87 | return nil, makeError(ErrInvalidType, str) 88 | } 89 | 90 | rawParams := make([]json.RawMessage, 0, len(params)) 91 | for _, param := range params { 92 | marshalledParam, err := json.Marshal(param) 93 | if err != nil { 94 | return nil, err 95 | } 96 | rawMessage := json.RawMessage(marshalledParam) 97 | rawParams = append(rawParams, rawMessage) 98 | } 99 | 100 | return &Request{ 101 | Jsonrpc: "1.0", 102 | ID: id, 103 | Method: method, 104 | Params: rawParams, 105 | }, nil 106 | } 107 | 108 | // Response is the general form of a JSON-RPC response. The type of the Result 109 | // field varies from one command to the next, so it is implemented as an 110 | // interface. The ID field has to be a pointer for Go to put a null in it when 111 | // empty. 112 | type Response struct { 113 | Result json.RawMessage `json:"result"` 114 | Error *RPCError `json:"error"` 115 | ID *interface{} `json:"id"` 116 | } 117 | 118 | // NewResponse returns a new JSON-RPC response object given the provided id, 119 | // marshalled result, and RPC error. This function is only provided in case the 120 | // caller wants to construct raw responses for some reason. 121 | // 122 | // Typically callers will instead want to create the fully marshalled JSON-RPC 123 | // response to send over the wire with the MarshalResponse function. 124 | func NewResponse(id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Response, error) { 125 | if !IsValidIDType(id) { 126 | str := fmt.Sprintf("the id of type '%T' is invalid", id) 127 | return nil, makeError(ErrInvalidType, str) 128 | } 129 | 130 | pid := &id 131 | return &Response{ 132 | Result: marshalledResult, 133 | Error: rpcErr, 134 | ID: pid, 135 | }, nil 136 | } 137 | 138 | // MarshalResponse marshals the passed id, result, and RPCError to a JSON-RPC 139 | // response byte slice that is suitable for transmission to a JSON-RPC client. 140 | func MarshalResponse(id interface{}, result interface{}, rpcErr *RPCError) ([]byte, error) { 141 | marshalledResult, err := json.Marshal(result) 142 | if err != nil { 143 | return nil, err 144 | } 145 | response, err := NewResponse(id, marshalledResult, rpcErr) 146 | if err != nil { 147 | return nil, err 148 | } 149 | return json.Marshal(&response) 150 | } 151 | -------------------------------------------------------------------------------- /btcjson/jsonrpc_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson_test 6 | 7 | import ( 8 | "encoding/json" 9 | "reflect" 10 | "testing" 11 | 12 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 13 | ) 14 | 15 | // TestIsValidIDType ensures the IsValidIDType function behaves as expected. 16 | func TestIsValidIDType(t *testing.T) { 17 | t.Parallel() 18 | 19 | tests := []struct { 20 | name string 21 | id interface{} 22 | isValid bool 23 | }{ 24 | {"int", int(1), true}, 25 | {"int8", int8(1), true}, 26 | {"int16", int16(1), true}, 27 | {"int32", int32(1), true}, 28 | {"int64", int64(1), true}, 29 | {"uint", uint(1), true}, 30 | {"uint8", uint8(1), true}, 31 | {"uint16", uint16(1), true}, 32 | {"uint32", uint32(1), true}, 33 | {"uint64", uint64(1), true}, 34 | {"string", "1", true}, 35 | {"nil", nil, true}, 36 | {"float32", float32(1), true}, 37 | {"float64", float64(1), true}, 38 | {"bool", true, false}, 39 | {"chan int", make(chan int), false}, 40 | {"complex64", complex64(1), false}, 41 | {"complex128", complex128(1), false}, 42 | {"func", func() {}, false}, 43 | } 44 | 45 | t.Logf("Running %d tests", len(tests)) 46 | for i, test := range tests { 47 | if btcjson.IsValidIDType(test.id) != test.isValid { 48 | t.Errorf("Test #%d (%s) valid mismatch - got %v, "+ 49 | "want %v", i, test.name, !test.isValid, 50 | test.isValid) 51 | continue 52 | } 53 | } 54 | } 55 | 56 | // TestMarshalResponse ensures the MarshalResponse function works as expected. 57 | func TestMarshalResponse(t *testing.T) { 58 | t.Parallel() 59 | 60 | testID := 1 61 | tests := []struct { 62 | name string 63 | result interface{} 64 | jsonErr *btcjson.RPCError 65 | expected []byte 66 | }{ 67 | { 68 | name: "ordinary bool result with no error", 69 | result: true, 70 | jsonErr: nil, 71 | expected: []byte(`{"result":true,"error":null,"id":1}`), 72 | }, 73 | { 74 | name: "result with error", 75 | result: nil, 76 | jsonErr: func() *btcjson.RPCError { 77 | return btcjson.NewRPCError(btcjson.ErrRPCBlockNotFound, "123 not found") 78 | }(), 79 | expected: []byte(`{"result":null,"error":{"code":-5,"message":"123 not found"},"id":1}`), 80 | }, 81 | } 82 | 83 | t.Logf("Running %d tests", len(tests)) 84 | for i, test := range tests { 85 | _, _ = i, test 86 | marshalled, err := btcjson.MarshalResponse(testID, test.result, test.jsonErr) 87 | if err != nil { 88 | t.Errorf("Test #%d (%s) unexpected error: %v", i, 89 | test.name, err) 90 | continue 91 | } 92 | 93 | if !reflect.DeepEqual(marshalled, test.expected) { 94 | t.Errorf("Test #%d (%s) mismatched result - got %s, "+ 95 | "want %s", i, test.name, marshalled, 96 | test.expected) 97 | } 98 | } 99 | } 100 | 101 | // TestMiscErrors tests a few error conditions not covered elsewhere. 102 | func TestMiscErrors(t *testing.T) { 103 | t.Parallel() 104 | 105 | // Force an error in NewRequest by giving it a parameter type that is 106 | // not supported. 107 | _, err := btcjson.NewRequest(nil, "test", []interface{}{make(chan int)}) 108 | if err == nil { 109 | t.Error("NewRequest: did not receive error") 110 | return 111 | } 112 | 113 | // Force an error in MarshalResponse by giving it an id type that is not 114 | // supported. 115 | wantErr := btcjson.Error{ErrorCode: btcjson.ErrInvalidType} 116 | _, err = btcjson.MarshalResponse(make(chan int), nil, nil) 117 | if jerr, ok := err.(btcjson.Error); !ok || jerr.ErrorCode != wantErr.ErrorCode { 118 | t.Errorf("MarshalResult: did not receive expected error - got "+ 119 | "%v (%[1]T), want %v (%[2]T)", err, wantErr) 120 | return 121 | } 122 | 123 | // Force an error in MarshalResponse by giving it a result type that 124 | // can't be marshalled. 125 | _, err = btcjson.MarshalResponse(1, make(chan int), nil) 126 | if _, ok := err.(*json.UnsupportedTypeError); !ok { 127 | wantErr := &json.UnsupportedTypeError{} 128 | t.Errorf("MarshalResult: did not receive expected error - got "+ 129 | "%v (%[1]T), want %T", err, wantErr) 130 | return 131 | } 132 | } 133 | 134 | // TestRPCError tests the error output for the RPCError type. 135 | func TestRPCError(t *testing.T) { 136 | t.Parallel() 137 | 138 | tests := []struct { 139 | in *btcjson.RPCError 140 | want string 141 | }{ 142 | { 143 | btcjson.ErrRPCInvalidRequest, 144 | "-32600: Invalid request", 145 | }, 146 | { 147 | btcjson.ErrRPCMethodNotFound, 148 | "-32601: Method not found", 149 | }, 150 | } 151 | 152 | t.Logf("Running %d tests", len(tests)) 153 | for i, test := range tests { 154 | result := test.in.Error() 155 | if result != test.want { 156 | t.Errorf("Error #%d\n got: %s want: %s", i, result, 157 | test.want) 158 | continue 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /btcjson/jsonrpcerr.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson 6 | 7 | // Standard JSON-RPC 2.0 errors. 8 | var ( 9 | ErrRPCInvalidRequest = &RPCError{ 10 | Code: -32600, 11 | Message: "Invalid request", 12 | } 13 | ErrRPCMethodNotFound = &RPCError{ 14 | Code: -32601, 15 | Message: "Method not found", 16 | } 17 | ErrRPCInvalidParams = &RPCError{ 18 | Code: -32602, 19 | Message: "Invalid parameters", 20 | } 21 | ErrRPCInternal = &RPCError{ 22 | Code: -32603, 23 | Message: "Internal error", 24 | } 25 | ErrRPCParse = &RPCError{ 26 | Code: -32700, 27 | Message: "Parse error", 28 | } 29 | ) 30 | 31 | // General application defined JSON errors. 32 | const ( 33 | ErrRPCMisc RPCErrorCode = -1 34 | ErrRPCForbiddenBySafeMode RPCErrorCode = -2 35 | ErrRPCType RPCErrorCode = -3 36 | ErrRPCInvalidAddressOrKey RPCErrorCode = -5 37 | ErrRPCOutOfMemory RPCErrorCode = -7 38 | ErrRPCInvalidParameter RPCErrorCode = -8 39 | ErrRPCDatabase RPCErrorCode = -20 40 | ErrRPCDeserialization RPCErrorCode = -22 41 | ErrRPCVerify RPCErrorCode = -25 42 | ) 43 | 44 | // Peer-to-peer client errors. 45 | const ( 46 | ErrRPCClientNotConnected RPCErrorCode = -9 47 | ErrRPCClientInInitialDownload RPCErrorCode = -10 48 | ErrRPCClientNodeNotAdded RPCErrorCode = -24 49 | ) 50 | 51 | // Wallet JSON errors 52 | const ( 53 | ErrRPCWallet RPCErrorCode = -4 54 | ErrRPCWalletInsufficientFunds RPCErrorCode = -6 55 | ErrRPCWalletInvalidAccountName RPCErrorCode = -11 56 | ErrRPCWalletKeypoolRanOut RPCErrorCode = -12 57 | ErrRPCWalletUnlockNeeded RPCErrorCode = -13 58 | ErrRPCWalletPassphraseIncorrect RPCErrorCode = -14 59 | ErrRPCWalletWrongEncState RPCErrorCode = -15 60 | ErrRPCWalletEncryptionFailed RPCErrorCode = -16 61 | ErrRPCWalletAlreadyUnlocked RPCErrorCode = -17 62 | ) 63 | 64 | // Specific Errors related to commands. These are the ones a user of the RPC 65 | // server are most likely to see. Generally, the codes should match one of the 66 | // more general errors above. 67 | const ( 68 | ErrRPCBlockNotFound RPCErrorCode = -5 69 | ErrRPCBlockCount RPCErrorCode = -5 70 | ErrRPCBestBlockHash RPCErrorCode = -5 71 | ErrRPCDifficulty RPCErrorCode = -5 72 | ErrRPCOutOfRange RPCErrorCode = -1 73 | ErrRPCNoTxInfo RPCErrorCode = -5 74 | ErrRPCNoCFIndex RPCErrorCode = -5 75 | ErrRPCNoNewestBlockInfo RPCErrorCode = -5 76 | ErrRPCInvalidTxVout RPCErrorCode = -5 77 | ErrRPCRawTxString RPCErrorCode = -32602 78 | ErrRPCDecodeHexString RPCErrorCode = -22 79 | ) 80 | 81 | // Errors that are specific to btcd. 82 | const ( 83 | ErrRPCNoWallet RPCErrorCode = -1 84 | ErrRPCUnimplemented RPCErrorCode = -1 85 | ) 86 | -------------------------------------------------------------------------------- /btcjson/register.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | "reflect" 11 | "sort" 12 | "strconv" 13 | "strings" 14 | "sync" 15 | ) 16 | 17 | // UsageFlag define flags that specify additional properties about the 18 | // circumstances under which a command can be used. 19 | type UsageFlag uint32 20 | 21 | const ( 22 | // UFWalletOnly indicates that the command can only be used with an RPC 23 | // server that supports wallet commands. 24 | UFWalletOnly UsageFlag = 1 << iota 25 | 26 | // highestUsageFlagBit is the maximum usage flag bit and is used in the 27 | // stringer and tests to ensure all of the above constants have been 28 | // tested. 29 | highestUsageFlagBit 30 | ) 31 | 32 | // Map of UsageFlag values back to their constant names for pretty printing. 33 | var usageFlagStrings = map[UsageFlag]string{ 34 | UFWalletOnly: "UFWalletOnly", 35 | } 36 | 37 | // String returns the UsageFlag in human-readable form. 38 | func (fl UsageFlag) String() string { 39 | // No flags are set. 40 | if fl == 0 { 41 | return "0x0" 42 | } 43 | 44 | // Add individual bit flags. 45 | s := "" 46 | for flag := UFWalletOnly; flag < highestUsageFlagBit; flag <<= 1 { 47 | if fl&flag == flag { 48 | s += usageFlagStrings[flag] + "|" 49 | fl -= flag 50 | } 51 | } 52 | 53 | // Add remaining value as raw hex. 54 | s = strings.TrimRight(s, "|") 55 | if fl != 0 { 56 | s += "|0x" + strconv.FormatUint(uint64(fl), 16) 57 | } 58 | s = strings.TrimLeft(s, "|") 59 | return s 60 | } 61 | 62 | // methodInfo keeps track of information about each registered method such as 63 | // the parameter information. 64 | type methodInfo struct { 65 | maxParams int 66 | numReqParams int 67 | numOptParams int 68 | defaults map[int]reflect.Value 69 | flags UsageFlag 70 | usage string 71 | } 72 | 73 | var ( 74 | // These fields are used to map the registered types to method names. 75 | registerLock sync.RWMutex 76 | methodToConcreteType = make(map[string]reflect.Type) 77 | methodToInfo = make(map[string]methodInfo) 78 | concreteTypeToMethod = make(map[reflect.Type]string) 79 | ) 80 | 81 | // baseKindString returns the base kind for a given reflect.Type after 82 | // indirecting through all pointers. 83 | func baseKindString(rt reflect.Type) string { 84 | numIndirects := 0 85 | for rt.Kind() == reflect.Ptr { 86 | numIndirects++ 87 | rt = rt.Elem() 88 | } 89 | 90 | return fmt.Sprintf("%s%s", strings.Repeat("*", numIndirects), rt.Kind()) 91 | } 92 | 93 | // isAcceptableKind returns whether or not the passed field type is a supported 94 | // type. It is called after the first pointer indirection, so further pointers 95 | // are not supported. 96 | func isAcceptableKind(kind reflect.Kind) bool { 97 | switch kind { 98 | case reflect.Chan: 99 | fallthrough 100 | case reflect.Complex64: 101 | fallthrough 102 | case reflect.Complex128: 103 | fallthrough 104 | case reflect.Func: 105 | fallthrough 106 | case reflect.Ptr: 107 | fallthrough 108 | case reflect.Interface: 109 | return false 110 | } 111 | 112 | return true 113 | } 114 | 115 | // RegisterCmd registers a new command that will automatically marshal to and 116 | // from JSON-RPC with full type checking and positional parameter support. It 117 | // also accepts usage flags which identify the circumstances under which the 118 | // command can be used. 119 | // 120 | // This package automatically registers all of the exported commands by default 121 | // using this function, however it is also exported so callers can easily 122 | // register custom types. 123 | // 124 | // The type format is very strict since it needs to be able to automatically 125 | // marshal to and from JSON-RPC 1.0. The following enumerates the requirements: 126 | // 127 | // - The provided command must be a single pointer to a struct 128 | // - All fields must be exported 129 | // - The order of the positional parameters in the marshalled JSON will be in 130 | // the same order as declared in the struct definition 131 | // - Struct embedding is not supported 132 | // - Struct fields may NOT be channels, functions, complex, or interface 133 | // - A field in the provided struct with a pointer is treated as optional 134 | // - Multiple indirections (i.e **int) are not supported 135 | // - Once the first optional field (pointer) is encountered, the remaining 136 | // fields must also be optional fields (pointers) as required by positional 137 | // params 138 | // - A field that has a 'jsonrpcdefault' struct tag must be an optional field 139 | // (pointer) 140 | // 141 | // NOTE: This function only needs to be able to examine the structure of the 142 | // passed struct, so it does not need to be an actual instance. Therefore, it 143 | // is recommended to simply pass a nil pointer cast to the appropriate type. 144 | // For example, (*FooCmd)(nil). 145 | func RegisterCmd(method string, cmd interface{}, flags UsageFlag) error { 146 | registerLock.Lock() 147 | defer registerLock.Unlock() 148 | 149 | if _, ok := methodToConcreteType[method]; ok { 150 | str := fmt.Sprintf("method %q is already registered", method) 151 | return makeError(ErrDuplicateMethod, str) 152 | } 153 | 154 | // Ensure that no unrecognized flag bits were specified. 155 | if ^(highestUsageFlagBit-1)&flags != 0 { 156 | str := fmt.Sprintf("invalid usage flags specified for method "+ 157 | "%s: %v", method, flags) 158 | return makeError(ErrInvalidUsageFlags, str) 159 | } 160 | 161 | rtp := reflect.TypeOf(cmd) 162 | if rtp.Kind() != reflect.Ptr { 163 | str := fmt.Sprintf("type must be *struct not '%s (%s)'", rtp, 164 | rtp.Kind()) 165 | return makeError(ErrInvalidType, str) 166 | } 167 | rt := rtp.Elem() 168 | if rt.Kind() != reflect.Struct { 169 | str := fmt.Sprintf("type must be *struct not '%s (*%s)'", 170 | rtp, rt.Kind()) 171 | return makeError(ErrInvalidType, str) 172 | } 173 | 174 | // Enumerate the struct fields to validate them and gather parameter 175 | // information. 176 | numFields := rt.NumField() 177 | numOptFields := 0 178 | defaults := make(map[int]reflect.Value) 179 | for i := 0; i < numFields; i++ { 180 | rtf := rt.Field(i) 181 | if rtf.Anonymous { 182 | str := fmt.Sprintf("embedded fields are not supported "+ 183 | "(field name: %q)", rtf.Name) 184 | return makeError(ErrEmbeddedType, str) 185 | } 186 | if rtf.PkgPath != "" { 187 | str := fmt.Sprintf("unexported fields are not supported "+ 188 | "(field name: %q)", rtf.Name) 189 | return makeError(ErrUnexportedField, str) 190 | } 191 | 192 | // Disallow types that can't be JSON encoded. Also, determine 193 | // if the field is optional based on it being a pointer. 194 | var isOptional bool 195 | switch kind := rtf.Type.Kind(); kind { 196 | case reflect.Ptr: 197 | isOptional = true 198 | kind = rtf.Type.Elem().Kind() 199 | fallthrough 200 | default: 201 | if !isAcceptableKind(kind) { 202 | str := fmt.Sprintf("unsupported field type "+ 203 | "'%s (%s)' (field name %q)", rtf.Type, 204 | baseKindString(rtf.Type), rtf.Name) 205 | return makeError(ErrUnsupportedFieldType, str) 206 | } 207 | } 208 | 209 | // Count the optional fields and ensure all fields after the 210 | // first optional field are also optional. 211 | if isOptional { 212 | numOptFields++ 213 | } else { 214 | if numOptFields > 0 { 215 | str := fmt.Sprintf("all fields after the first "+ 216 | "optional field must also be optional "+ 217 | "(field name %q)", rtf.Name) 218 | return makeError(ErrNonOptionalField, str) 219 | } 220 | } 221 | 222 | // Ensure the default value can be unsmarshalled into the type 223 | // and that defaults are only specified for optional fields. 224 | if tag := rtf.Tag.Get("jsonrpcdefault"); tag != "" { 225 | if !isOptional { 226 | str := fmt.Sprintf("required fields must not "+ 227 | "have a default specified (field name "+ 228 | "%q)", rtf.Name) 229 | return makeError(ErrNonOptionalDefault, str) 230 | } 231 | 232 | rvf := reflect.New(rtf.Type.Elem()) 233 | err := json.Unmarshal([]byte(tag), rvf.Interface()) 234 | if err != nil { 235 | str := fmt.Sprintf("default value of %q is "+ 236 | "the wrong type (field name %q)", tag, 237 | rtf.Name) 238 | return makeError(ErrMismatchedDefault, str) 239 | } 240 | defaults[i] = rvf 241 | } 242 | } 243 | 244 | // Update the registration maps. 245 | methodToConcreteType[method] = rtp 246 | methodToInfo[method] = methodInfo{ 247 | maxParams: numFields, 248 | numReqParams: numFields - numOptFields, 249 | numOptParams: numOptFields, 250 | defaults: defaults, 251 | flags: flags, 252 | } 253 | concreteTypeToMethod[rtp] = method 254 | return nil 255 | } 256 | 257 | // MustRegisterCmd performs the same function as RegisterCmd except it panics 258 | // if there is an error. This should only be called from package init 259 | // functions. 260 | func MustRegisterCmd(method string, cmd interface{}, flags UsageFlag) { 261 | if err := RegisterCmd(method, cmd, flags); err != nil { 262 | panic(fmt.Sprintf("failed to register type %q: %v\n", method, 263 | err)) 264 | } 265 | } 266 | 267 | // RegisteredCmdMethods returns a sorted list of methods for all registered 268 | // commands. 269 | func RegisteredCmdMethods() []string { 270 | registerLock.Lock() 271 | defer registerLock.Unlock() 272 | 273 | methods := make([]string, 0, len(methodToInfo)) 274 | for k := range methodToInfo { 275 | methods = append(methods, k) 276 | } 277 | 278 | sort.Sort(sort.StringSlice(methods)) 279 | return methods 280 | } 281 | -------------------------------------------------------------------------------- /btcjson/register_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson_test 6 | 7 | import ( 8 | "reflect" 9 | "sort" 10 | "testing" 11 | 12 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 13 | ) 14 | 15 | // TestRegisterCmdErrors ensures the RegisterCmd function returns the expected 16 | // error when provided with invalid types. 17 | func TestRegisterCmdErrors(t *testing.T) { 18 | t.Parallel() 19 | 20 | tests := []struct { 21 | name string 22 | method string 23 | cmdFunc func() interface{} 24 | flags btcjson.UsageFlag 25 | err btcjson.Error 26 | }{ 27 | { 28 | name: "duplicate method", 29 | method: "getblock", 30 | cmdFunc: func() interface{} { 31 | return struct{}{} 32 | }, 33 | err: btcjson.Error{ErrorCode: btcjson.ErrDuplicateMethod}, 34 | }, 35 | { 36 | name: "invalid usage flags", 37 | method: "registertestcmd", 38 | cmdFunc: func() interface{} { 39 | return 0 40 | }, 41 | flags: btcjson.TstHighestUsageFlagBit, 42 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidUsageFlags}, 43 | }, 44 | { 45 | name: "invalid type", 46 | method: "registertestcmd", 47 | cmdFunc: func() interface{} { 48 | return 0 49 | }, 50 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 51 | }, 52 | { 53 | name: "invalid type 2", 54 | method: "registertestcmd", 55 | cmdFunc: func() interface{} { 56 | return &[]string{} 57 | }, 58 | err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType}, 59 | }, 60 | { 61 | name: "embedded field", 62 | method: "registertestcmd", 63 | cmdFunc: func() interface{} { 64 | type test struct{ int } 65 | return (*test)(nil) 66 | }, 67 | err: btcjson.Error{ErrorCode: btcjson.ErrEmbeddedType}, 68 | }, 69 | { 70 | name: "unexported field", 71 | method: "registertestcmd", 72 | cmdFunc: func() interface{} { 73 | type test struct{ a int } 74 | return (*test)(nil) 75 | }, 76 | err: btcjson.Error{ErrorCode: btcjson.ErrUnexportedField}, 77 | }, 78 | { 79 | name: "unsupported field type 1", 80 | method: "registertestcmd", 81 | cmdFunc: func() interface{} { 82 | type test struct{ A **int } 83 | return (*test)(nil) 84 | }, 85 | err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType}, 86 | }, 87 | { 88 | name: "unsupported field type 2", 89 | method: "registertestcmd", 90 | cmdFunc: func() interface{} { 91 | type test struct{ A chan int } 92 | return (*test)(nil) 93 | }, 94 | err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType}, 95 | }, 96 | { 97 | name: "unsupported field type 3", 98 | method: "registertestcmd", 99 | cmdFunc: func() interface{} { 100 | type test struct{ A complex64 } 101 | return (*test)(nil) 102 | }, 103 | err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType}, 104 | }, 105 | { 106 | name: "unsupported field type 4", 107 | method: "registertestcmd", 108 | cmdFunc: func() interface{} { 109 | type test struct{ A complex128 } 110 | return (*test)(nil) 111 | }, 112 | err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType}, 113 | }, 114 | { 115 | name: "unsupported field type 5", 116 | method: "registertestcmd", 117 | cmdFunc: func() interface{} { 118 | type test struct{ A func() } 119 | return (*test)(nil) 120 | }, 121 | err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType}, 122 | }, 123 | { 124 | name: "unsupported field type 6", 125 | method: "registertestcmd", 126 | cmdFunc: func() interface{} { 127 | type test struct{ A interface{} } 128 | return (*test)(nil) 129 | }, 130 | err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType}, 131 | }, 132 | { 133 | name: "required after optional", 134 | method: "registertestcmd", 135 | cmdFunc: func() interface{} { 136 | type test struct { 137 | A *int 138 | B int 139 | } 140 | return (*test)(nil) 141 | }, 142 | err: btcjson.Error{ErrorCode: btcjson.ErrNonOptionalField}, 143 | }, 144 | { 145 | name: "non-optional with default", 146 | method: "registertestcmd", 147 | cmdFunc: func() interface{} { 148 | type test struct { 149 | A int `jsonrpcdefault:"1"` 150 | } 151 | return (*test)(nil) 152 | }, 153 | err: btcjson.Error{ErrorCode: btcjson.ErrNonOptionalDefault}, 154 | }, 155 | { 156 | name: "mismatched default", 157 | method: "registertestcmd", 158 | cmdFunc: func() interface{} { 159 | type test struct { 160 | A *int `jsonrpcdefault:"1.7"` 161 | } 162 | return (*test)(nil) 163 | }, 164 | err: btcjson.Error{ErrorCode: btcjson.ErrMismatchedDefault}, 165 | }, 166 | } 167 | 168 | t.Logf("Running %d tests", len(tests)) 169 | for i, test := range tests { 170 | err := btcjson.RegisterCmd(test.method, test.cmdFunc(), 171 | test.flags) 172 | if reflect.TypeOf(err) != reflect.TypeOf(test.err) { 173 | t.Errorf("Test #%d (%s) wrong error - got %T, "+ 174 | "want %T", i, test.name, err, test.err) 175 | continue 176 | } 177 | gotErrorCode := err.(btcjson.Error).ErrorCode 178 | if gotErrorCode != test.err.ErrorCode { 179 | t.Errorf("Test #%d (%s) mismatched error code - got "+ 180 | "%v, want %v", i, test.name, gotErrorCode, 181 | test.err.ErrorCode) 182 | continue 183 | } 184 | } 185 | } 186 | 187 | // TestMustRegisterCmdPanic ensures the MustRegisterCmd function panics when 188 | // used to register an invalid type. 189 | func TestMustRegisterCmdPanic(t *testing.T) { 190 | t.Parallel() 191 | 192 | // Setup a defer to catch the expected panic to ensure it actually 193 | // paniced. 194 | defer func() { 195 | if err := recover(); err == nil { 196 | t.Error("MustRegisterCmd did not panic as expected") 197 | } 198 | }() 199 | 200 | // Intentionally try to register an invalid type to force a panic. 201 | btcjson.MustRegisterCmd("panicme", 0, 0) 202 | } 203 | 204 | // TestRegisteredCmdMethods tests the RegisteredCmdMethods function ensure it 205 | // works as expected. 206 | func TestRegisteredCmdMethods(t *testing.T) { 207 | t.Parallel() 208 | 209 | // Ensure the registered methods are returned. 210 | methods := btcjson.RegisteredCmdMethods() 211 | if len(methods) == 0 { 212 | t.Fatal("RegisteredCmdMethods: no methods") 213 | } 214 | 215 | // Ensure the returned methods are sorted. 216 | sortedMethods := make([]string, len(methods)) 217 | copy(sortedMethods, methods) 218 | sort.Sort(sort.StringSlice(sortedMethods)) 219 | if !reflect.DeepEqual(sortedMethods, methods) { 220 | t.Fatal("RegisteredCmdMethods: methods are not sorted") 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /btcjson/walletsvrresults.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package btcjson 6 | 7 | // GetTransactionDetailsResult models the details data from the gettransaction command. 8 | // 9 | // This models the "short" version of the ListTransactionsResult type, which 10 | // excludes fields common to the transaction. These common fields are instead 11 | // part of the GetTransactionResult. 12 | type GetTransactionDetailsResult struct { 13 | Account string `json:"account"` 14 | Address string `json:"address,omitempty"` 15 | Amount float64 `json:"amount"` 16 | Category string `json:"category"` 17 | InvolvesWatchOnly bool `json:"involveswatchonly,omitempty"` 18 | Fee *float64 `json:"fee,omitempty"` 19 | Vout uint32 `json:"vout"` 20 | } 21 | 22 | // GetTransactionResult models the data from the gettransaction command. 23 | type GetTransactionResult struct { 24 | Amount float64 `json:"amount"` 25 | Fee float64 `json:"fee,omitempty"` 26 | Confirmations int64 `json:"confirmations"` 27 | BlockHash string `json:"blockhash"` 28 | BlockIndex int64 `json:"blockindex"` 29 | BlockTime int64 `json:"blocktime"` 30 | TxID string `json:"txid"` 31 | WalletConflicts []string `json:"walletconflicts"` 32 | Time int64 `json:"time"` 33 | TimeReceived int64 `json:"timereceived"` 34 | Details []GetTransactionDetailsResult `json:"details"` 35 | Hex string `json:"hex"` 36 | } 37 | 38 | // InfoWalletResult models the data returned by the wallet server getinfo 39 | // command. 40 | type InfoWalletResult struct { 41 | Version int32 `json:"version"` 42 | ProtocolVersion int32 `json:"protocolversion"` 43 | WalletVersion int32 `json:"walletversion"` 44 | Balance float64 `json:"balance"` 45 | Blocks int32 `json:"blocks"` 46 | TimeOffset int64 `json:"timeoffset"` 47 | Connections int32 `json:"connections"` 48 | Proxy string `json:"proxy"` 49 | Difficulty float64 `json:"difficulty"` 50 | TestNet bool `json:"testnet"` 51 | KeypoolOldest int64 `json:"keypoololdest"` 52 | KeypoolSize int32 `json:"keypoolsize"` 53 | UnlockedUntil int64 `json:"unlocked_until"` 54 | PaytxFee float64 `json:"paytxfee"` 55 | RelayFee float64 `json:"relayfee"` 56 | Errors string `json:"errors"` 57 | } 58 | 59 | // ListTransactionsResult models the data from the listtransactions command. 60 | type ListTransactionsResult struct { 61 | Abandoned bool `json:"abandoned"` 62 | Account string `json:"account"` 63 | Address string `json:"address,omitempty"` 64 | Amount float64 `json:"amount"` 65 | BIP125Replaceable string `json:"bip125-replaceable,omitempty"` 66 | BlockHash string `json:"blockhash,omitempty"` 67 | BlockIndex *int64 `json:"blockindex,omitempty"` 68 | BlockTime int64 `json:"blocktime,omitempty"` 69 | Category string `json:"category"` 70 | Confirmations int64 `json:"confirmations"` 71 | Fee *float64 `json:"fee,omitempty"` 72 | Generated bool `json:"generated,omitempty"` 73 | InvolvesWatchOnly bool `json:"involveswatchonly,omitempty"` 74 | Time int64 `json:"time"` 75 | TimeReceived int64 `json:"timereceived"` 76 | Trusted bool `json:"trusted"` 77 | TxID string `json:"txid"` 78 | Vout uint32 `json:"vout"` 79 | WalletConflicts []string `json:"walletconflicts"` 80 | Comment string `json:"comment,omitempty"` 81 | OtherAccount string `json:"otheraccount,omitempty"` 82 | } 83 | 84 | // ListReceivedByAccountResult models the data from the listreceivedbyaccount 85 | // command. 86 | type ListReceivedByAccountResult struct { 87 | Account string `json:"account"` 88 | Amount float64 `json:"amount"` 89 | Confirmations uint64 `json:"confirmations"` 90 | } 91 | 92 | // ListReceivedByAddressResult models the data from the listreceivedbyaddress 93 | // command. 94 | type ListReceivedByAddressResult struct { 95 | Account string `json:"account"` 96 | Address string `json:"address"` 97 | Amount float64 `json:"amount"` 98 | Confirmations uint64 `json:"confirmations"` 99 | TxIDs []string `json:"txids,omitempty"` 100 | InvolvesWatchonly bool `json:"involvesWatchonly,omitempty"` 101 | } 102 | 103 | // ListSinceBlockResult models the data from the listsinceblock command. 104 | type ListSinceBlockResult struct { 105 | Transactions []ListTransactionsResult `json:"transactions"` 106 | LastBlock string `json:"lastblock"` 107 | } 108 | 109 | // ListUnspentResult models a successful response from the listunspent request. 110 | type ListUnspentResult struct { 111 | TxID string `json:"txid"` 112 | Vout uint32 `json:"vout"` 113 | Address string `json:"address"` 114 | Account string `json:"account"` 115 | ScriptPubKey string `json:"scriptPubKey"` 116 | RedeemScript string `json:"redeemScript,omitempty"` 117 | Amount float64 `json:"amount"` 118 | Confirmations int64 `json:"confirmations"` 119 | Spendable bool `json:"spendable"` 120 | } 121 | 122 | // SignRawTransactionError models the data that contains script verification 123 | // errors from the signrawtransaction request. 124 | type SignRawTransactionError struct { 125 | TxID string `json:"txid"` 126 | Vout uint32 `json:"vout"` 127 | ScriptSig string `json:"scriptSig"` 128 | Sequence uint32 `json:"sequence"` 129 | Error string `json:"error"` 130 | } 131 | 132 | // SignRawTransactionResult models the data from the signrawtransaction 133 | // command. 134 | type SignRawTransactionResult struct { 135 | Hex string `json:"hex"` 136 | Complete bool `json:"complete"` 137 | Errors []SignRawTransactionError `json:"errors,omitempty"` 138 | } 139 | 140 | // ValidateAddressWalletResult models the data returned by the wallet server 141 | // validateaddress command. 142 | type ValidateAddressWalletResult struct { 143 | IsValid bool `json:"isvalid"` 144 | Address string `json:"address,omitempty"` 145 | IsMine bool `json:"ismine,omitempty"` 146 | IsWatchOnly bool `json:"iswatchonly,omitempty"` 147 | IsScript bool `json:"isscript,omitempty"` 148 | PubKey string `json:"pubkey,omitempty"` 149 | IsCompressed bool `json:"iscompressed,omitempty"` 150 | Account string `json:"account,omitempty"` 151 | Addresses []string `json:"addresses,omitempty"` 152 | Hex string `json:"hex,omitempty"` 153 | Script string `json:"script,omitempty"` 154 | SigsRequired int32 `json:"sigsrequired,omitempty"` 155 | } 156 | 157 | // GetBestBlockResult models the data from the getbestblock command. 158 | type GetBestBlockResult struct { 159 | Hash string `json:"hash"` 160 | Height int32 `json:"height"` 161 | } 162 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | /* 6 | Package rpcclient implements a Bitcoin Core JSON-RPC client. 7 | 8 | Overview 9 | 10 | This client provides a robust and easy to use client for interfacing with a 11 | Bitcoin RPC server that uses a Bitcoin Core compatible Bitcoin JSON-RPC API. 12 | 13 | 14 | Errors 15 | 16 | There are 3 categories of errors that will be returned throughout this package: 17 | 18 | - Errors related to the client connection such as authentication and shutdown 19 | - Errors that occur before communicating with the remote RPC server such as 20 | command creation and marshaling errors or issues talking to the remote 21 | server 22 | - Errors returned from the remote RPC server like unimplemented commands, 23 | nonexistent requested blocks and transactions, malformed data, and incorrect 24 | networks 25 | 26 | The first category of errors are typically ErrInvalidAuth or ErrClientShutdown. 27 | 28 | The second category of errors typically indicates a programmer error and as such 29 | the type can vary, but usually will be best handled by simply showing/logging 30 | it. 31 | 32 | The third category of errors, that is errors returned by the server, can be 33 | detected by type asserting the error in a *btcjson.RPCError. For example, to 34 | detect if a command is unimplemented by the remote RPC server: 35 | 36 | amount, err := client.GetBalance("") 37 | if err != nil { 38 | if jerr, ok := err.(*btcjson.RPCError); ok { 39 | switch jerr.Code { 40 | case btcjson.ErrRPCUnimplemented: 41 | // Handle not implemented error 42 | 43 | // Handle other specific errors you care about 44 | } 45 | } 46 | 47 | // Log or otherwise handle the error knowing it was not one returned 48 | // from the remote RPC server. 49 | } 50 | 51 | Example Usage 52 | 53 | Check the examples directory. 54 | */ 55 | package rpcclient 56 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | Bitcoin Core HTTP POST Example 2 | ============================== 3 | 4 | This example shows how to use the rpcclient package to connect to a Bitcoin 5 | Core RPC server using HTTP POST mode with TLS disabled and gets the current 6 | block count. 7 | 8 | ## Running the Example 9 | 10 | The first step is to use `go get` to download and install the rpcclient package: 11 | 12 | ```bash 13 | $ go get github.com/stevenroose/go-bitcoin-core-rpc 14 | ``` 15 | 16 | Next, modify the `main.go` source to specify the correct RPC username and 17 | password for the RPC server: 18 | 19 | ```Go 20 | User: "yourrpcuser", 21 | Pass: "yourrpcpass", 22 | ``` 23 | 24 | Finally, navigate to the example's directory and run it with: 25 | 26 | ```bash 27 | $ cd $GOPATH/src/github.com/stevenroose/go-bitcoin-core-rpc/examples 28 | $ go run *.go 29 | ``` 30 | 31 | ## License 32 | 33 | This example is licensed under the [copyfree](http://copyfree.org) ISC License. 34 | -------------------------------------------------------------------------------- /examples/main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "log" 9 | 10 | rpcclient "github.com/stevenroose/go-bitcoin-core-rpc" 11 | ) 12 | 13 | func main() { 14 | // Connect to local bitcoin core RPC server using HTTP POST mode. 15 | connCfg := &rpcclient.ConnConfig{ 16 | Host: "localhost:8332", 17 | User: "yourrpcuser", 18 | Pass: "yourrpcpass", 19 | } 20 | // Notice the notification parameter is nil since notifications are 21 | // not supported in HTTP POST mode. 22 | client, err := rpcclient.New(connCfg) 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | defer client.Shutdown() 27 | 28 | // Get the current block count. 29 | blockCount, err := client.GetBlockCount() 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | log.Printf("Block count: %d", blockCount) 34 | } 35 | -------------------------------------------------------------------------------- /infrastructure.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package rpcclient 6 | 7 | import ( 8 | "bytes" 9 | "container/list" 10 | "crypto/tls" 11 | "crypto/x509" 12 | "encoding/json" 13 | "errors" 14 | "fmt" 15 | "io/ioutil" 16 | "net/http" 17 | "net/url" 18 | "sync" 19 | "sync/atomic" 20 | 21 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 22 | ) 23 | 24 | var ( 25 | // ErrClientShutdown is an error to describe the condition where the 26 | // client is either already shutdown, or in the process of shutting 27 | // down. Any outstanding futures when a client shutdown occurs will 28 | // return this error as will any new requests. 29 | ErrClientShutdown = errors.New("the client has been shutdown") 30 | ) 31 | 32 | const ( 33 | // sendPostBufferSize is the number of elements the HTTP POST send 34 | // channel can queue before blocking. 35 | sendPostBufferSize = 100 36 | ) 37 | 38 | // sendPostDetails houses an HTTP POST request to send to an RPC server as well 39 | // as the original JSON-RPC command and a channel to reply on when the server 40 | // responds with the result. 41 | type sendPostDetails struct { 42 | httpRequest *http.Request 43 | jsonRequest *jsonRequest 44 | } 45 | 46 | // jsonRequest holds information about a json request that is used to properly 47 | // detect, interpret, and deliver a reply to it. 48 | type jsonRequest struct { 49 | id uint64 50 | method string 51 | cmd interface{} 52 | marshalledJSON []byte 53 | responseChan chan *response 54 | } 55 | 56 | // Client represents a Bitcoin RPC client which allows easy access to the 57 | // various RPC methods available on a Bitcoin RPC server. Each of the wrapper 58 | // functions handle the details of converting the passed and return types to and 59 | // from the underlying JSON types which are required for the JSON-RPC 60 | // invocations 61 | // 62 | // The client provides each RPC in both synchronous (blocking) and asynchronous 63 | // (non-blocking) forms. The asynchronous forms are based on the concept of 64 | // futures where they return an instance of a type that promises to deliver the 65 | // result of the invocation at some future time. Invoking the Receive method on 66 | // the returned future will block until the result is available if it's not 67 | // already. 68 | type Client struct { 69 | id uint64 // atomic, so must stay 64-bit aligned 70 | 71 | // config holds the connection configuration assoiated with this client. 72 | config *ConnConfig 73 | 74 | // httpClient is the underlying HTTP client to use when running in HTTP 75 | // POST mode. 76 | httpClient *http.Client 77 | 78 | // Track command and their response channels by ID. 79 | requestLock sync.Mutex 80 | requestList *list.List 81 | 82 | // Networking infrastructure. 83 | sendPostChan chan *sendPostDetails 84 | shutdown chan struct{} 85 | wg sync.WaitGroup 86 | } 87 | 88 | // NextID returns the next id to be used when sending a JSON-RPC message. This 89 | // ID allows responses to be associated with particular requests per the 90 | // JSON-RPC specification. Typically the consumer of the client does not need 91 | // to call this function, however, if a custom request is being created and used 92 | // this function should be used to ensure the ID is unique amongst all requests 93 | // being made. 94 | func (c *Client) NextID() uint64 { 95 | return atomic.AddUint64(&c.id, 1) 96 | } 97 | 98 | // rawResponse is a partially-unmarshaled JSON-RPC response. For this 99 | // to be valid (according to JSON-RPC 1.0 spec), ID may not be nil. 100 | type rawResponse struct { 101 | ID *float64 `json:"id"` 102 | Result json.RawMessage `json:"result"` 103 | Error *btcjson.RPCError `json:"error"` 104 | } 105 | 106 | // response is the raw bytes of a JSON-RPC result, or the error if the response 107 | // error object was non-null. 108 | type response struct { 109 | result []byte 110 | err error 111 | } 112 | 113 | // result checks whether the unmarshaled response contains a non-nil error, 114 | // returning an unmarshaled btcjson.RPCError (or an unmarshaling error) if so. 115 | // If the response is not an error, the raw bytes of the request are 116 | // returned for further unmashaling into specific result types. 117 | func (r rawResponse) result() (result []byte, err error) { 118 | if r.Error != nil { 119 | return nil, r.Error 120 | } 121 | return r.Result, nil 122 | } 123 | 124 | // handleSendPostMessage handles performing the passed HTTP request, reading the 125 | // result, unmarshalling it, and delivering the unmarshalled result to the 126 | // provided response channel. 127 | func (c *Client) handleSendPostMessage(details *sendPostDetails) { 128 | jReq := details.jsonRequest 129 | log.Tracef("Sending command [%s] with id %d", jReq.method, jReq.id) 130 | httpResponse, err := c.httpClient.Do(details.httpRequest) 131 | if err != nil { 132 | jReq.responseChan <- &response{err: err} 133 | return 134 | } 135 | 136 | // Read the raw bytes and close the response. 137 | respBytes, err := ioutil.ReadAll(httpResponse.Body) 138 | _ = httpResponse.Body.Close() 139 | if err != nil { 140 | err = fmt.Errorf("error reading json reply: %v", err) 141 | jReq.responseChan <- &response{err: err} 142 | return 143 | } 144 | 145 | // Try to unmarshal the response as a regular JSON-RPC response. 146 | var resp rawResponse 147 | err = json.Unmarshal(respBytes, &resp) 148 | if err != nil { 149 | // When the response itself isn't a valid JSON-RPC response 150 | // return an error which includes the HTTP status code and raw 151 | // response bytes. 152 | err = fmt.Errorf("status code: %d, response: %q", 153 | httpResponse.StatusCode, string(respBytes)) 154 | jReq.responseChan <- &response{err: err} 155 | return 156 | } 157 | 158 | res, err := resp.result() 159 | jReq.responseChan <- &response{result: res, err: err} 160 | } 161 | 162 | // sendPostHandler handles all outgoing messages when the client is running 163 | // in HTTP POST mode. It uses a buffered channel to serialize output messages 164 | // while allowing the sender to continue running asynchronously. It must be run 165 | // as a goroutine. 166 | func (c *Client) sendPostHandler() { 167 | out: 168 | for { 169 | // Send any messages ready for send until the shutdown channel 170 | // is closed. 171 | select { 172 | case details := <-c.sendPostChan: 173 | c.handleSendPostMessage(details) 174 | 175 | case <-c.shutdown: 176 | break out 177 | } 178 | } 179 | 180 | // Drain any wait channels before exiting so nothing is left waiting 181 | // around to send. 182 | cleanup: 183 | for { 184 | select { 185 | case details := <-c.sendPostChan: 186 | details.jsonRequest.responseChan <- &response{ 187 | result: nil, 188 | err: ErrClientShutdown, 189 | } 190 | 191 | default: 192 | break cleanup 193 | } 194 | } 195 | c.wg.Done() 196 | log.Tracef("RPC client send handler done for %s", c.config.Host) 197 | 198 | } 199 | 200 | // sendPostRequest sends the passed HTTP request to the RPC server using the 201 | // HTTP client associated with the client. It is backed by a buffered channel, 202 | // so it will not block until the send channel is full. 203 | func (c *Client) sendPostRequest(httpReq *http.Request, jReq *jsonRequest) { 204 | // Don't send the message if shutting down. 205 | select { 206 | case <-c.shutdown: 207 | jReq.responseChan <- &response{result: nil, err: ErrClientShutdown} 208 | default: 209 | } 210 | 211 | c.sendPostChan <- &sendPostDetails{ 212 | jsonRequest: jReq, 213 | httpRequest: httpReq, 214 | } 215 | } 216 | 217 | // newFutureError returns a new future result channel that already has the 218 | // passed error waitin on the channel with the reply set to nil. This is useful 219 | // to easily return errors from the various Async functions. 220 | func newFutureError(err error) chan *response { 221 | responseChan := make(chan *response, 1) 222 | responseChan <- &response{err: err} 223 | return responseChan 224 | } 225 | 226 | // receiveFuture receives from the passed futureResult channel to extract a 227 | // reply or any errors. The examined errors include an error in the 228 | // futureResult and the error in the reply from the server. This will block 229 | // until the result is available on the passed channel. 230 | func receiveFuture(f chan *response) ([]byte, error) { 231 | // Wait for a response on the returned channel. 232 | r := <-f 233 | return r.result, r.err 234 | } 235 | 236 | // sendRequest sends the passed request to the server by issuing an HTTP POST 237 | // request using the provided response channel for the reply. Typically a new 238 | // connection is opened and closed for each command when using this method, 239 | // however, the underlying HTTP client might coalesce multiple commands 240 | // depending on several factors including the remote server configuration. 241 | func (c *Client) sendRequest(jReq *jsonRequest) { 242 | // Generate a request to the configured RPC server. 243 | protocol := "http" 244 | if c.config.EnableTLS { 245 | protocol = "https" 246 | } 247 | url := protocol + "://" + c.config.Host 248 | bodyReader := bytes.NewReader(jReq.marshalledJSON) 249 | httpReq, err := http.NewRequest("POST", url, bodyReader) 250 | if err != nil { 251 | jReq.responseChan <- &response{result: nil, err: err} 252 | return 253 | } 254 | httpReq.Close = true 255 | httpReq.Header.Set("Content-Type", "application/json") 256 | 257 | // Configure basic access authorization. 258 | httpReq.SetBasicAuth(c.config.User, c.config.Pass) 259 | 260 | log.Tracef("Sending command [%s] with id %d", jReq.method, jReq.id) 261 | c.sendPostRequest(httpReq, jReq) 262 | } 263 | 264 | // sendCmd sends the passed command to the associated server and returns a 265 | // response channel on which the reply will be delivered at some point in the 266 | // future. 267 | func (c *Client) sendCmd(cmd interface{}) chan *response { 268 | // Get the method associated with the command. 269 | method, err := btcjson.CmdMethod(cmd) 270 | if err != nil { 271 | return newFutureError(err) 272 | } 273 | 274 | // Marshal the command. 275 | id := c.NextID() 276 | marshalledJSON, err := btcjson.MarshalCmd(id, cmd) 277 | if err != nil { 278 | return newFutureError(err) 279 | } 280 | 281 | // Generate the request and send it along with a channel to respond on. 282 | responseChan := make(chan *response, 1) 283 | jReq := &jsonRequest{ 284 | id: id, 285 | method: method, 286 | cmd: cmd, 287 | marshalledJSON: marshalledJSON, 288 | responseChan: responseChan, 289 | } 290 | c.sendRequest(jReq) 291 | 292 | return responseChan 293 | } 294 | 295 | // sendCmdAndWait sends the passed command to the associated server, waits 296 | // for the reply, and returns the result from it. It will return the error 297 | // field in the reply if there is one. 298 | func (c *Client) sendCmdAndWait(cmd interface{}) (interface{}, error) { 299 | // Marshal the command to JSON-RPC, send it to the connected server, and 300 | // wait for a response on the returned channel. 301 | return receiveFuture(c.sendCmd(cmd)) 302 | } 303 | 304 | // doShutdown closes the shutdown channel and logs the shutdown unless shutdown 305 | // is already in progress. It will return false if the shutdown is not needed. 306 | // 307 | // This function is safe for concurrent access. 308 | func (c *Client) doShutdown() bool { 309 | // Ignore the shutdown request if the client is already in the process 310 | // of shutting down or already shutdown. 311 | select { 312 | case <-c.shutdown: 313 | return false 314 | default: 315 | } 316 | 317 | log.Tracef("Shutting down RPC client %s", c.config.Host) 318 | close(c.shutdown) 319 | return true 320 | } 321 | 322 | // Shutdown shuts down the client. 323 | func (c *Client) Shutdown() { 324 | // Do the shutdown under the request lock to prevent clients from 325 | // adding new requests while the client shutdown process is initiated. 326 | c.requestLock.Lock() 327 | defer c.requestLock.Unlock() 328 | 329 | // Ignore the shutdown request if the client is already in the process 330 | // of shutting down or already shutdown. 331 | if !c.doShutdown() { 332 | return 333 | } 334 | 335 | // Send the ErrClientShutdown error to any pending requests. 336 | for e := c.requestList.Front(); e != nil; e = e.Next() { 337 | req := e.Value.(*jsonRequest) 338 | req.responseChan <- &response{ 339 | result: nil, 340 | err: ErrClientShutdown, 341 | } 342 | } 343 | c.requestList.Init() 344 | } 345 | 346 | // start begins processing input and output messages. 347 | func (c *Client) start() { 348 | log.Tracef("Starting RPC client %s", c.config.Host) 349 | 350 | c.wg.Add(1) 351 | go c.sendPostHandler() 352 | } 353 | 354 | // WaitForShutdown blocks until the client goroutines are stopped and the 355 | // connection is closed. 356 | func (c *Client) WaitForShutdown() { 357 | c.wg.Wait() 358 | } 359 | 360 | // ConnConfig describes the connection configuration parameters for the client. 361 | // This 362 | type ConnConfig struct { 363 | // Host is the IP address and port of the RPC server you want to connect 364 | // to. 365 | Host string 366 | 367 | // User is the username to use to authenticate to the RPC server. 368 | User string 369 | 370 | // Pass is the passphrase to use to authenticate to the RPC server. 371 | Pass string 372 | 373 | // EnableTLS specifies whether transport layer security should be 374 | // enabled. It is recommended to always use TLS if the RPC server 375 | // supports it as otherwise your username and password is sent across 376 | // the wire in cleartext. 377 | EnableTLS bool 378 | 379 | // Certificates are the bytes for a PEM-encoded certificate chain used 380 | // for the TLS connection. It has no effect if the EnableTLS parameter 381 | // is false. 382 | Certificates []byte 383 | 384 | // Proxy specifies to connect through a SOCKS 5 proxy server. It may 385 | // be an empty string if a proxy is not required. 386 | Proxy string 387 | 388 | // ProxyUser is an optional username to use for the proxy server if it 389 | // requires authentication. It has no effect if the Proxy parameter 390 | // is not set. 391 | ProxyUser string 392 | 393 | // ProxyPass is an optional password to use for the proxy server if it 394 | // requires authentication. It has no effect if the Proxy parameter 395 | // is not set. 396 | ProxyPass string 397 | 398 | // EnableBCInfoHacks is an option provided to enable compatibility hacks 399 | // when connecting to blockchain.info RPC server 400 | EnableBCInfoHacks bool 401 | } 402 | 403 | // newHTTPClient returns a new http client that is configured according to the 404 | // proxy and TLS settings in the associated connection configuration. 405 | func newHTTPClient(config *ConnConfig) (*http.Client, error) { 406 | // Set proxy function if there is a proxy configured. 407 | var proxyFunc func(*http.Request) (*url.URL, error) 408 | if config.Proxy != "" { 409 | proxyURL, err := url.Parse(config.Proxy) 410 | if err != nil { 411 | return nil, err 412 | } 413 | proxyFunc = http.ProxyURL(proxyURL) 414 | } 415 | 416 | // Configure TLS if needed. 417 | var tlsConfig *tls.Config 418 | if config.EnableTLS { 419 | if len(config.Certificates) > 0 { 420 | pool := x509.NewCertPool() 421 | pool.AppendCertsFromPEM(config.Certificates) 422 | tlsConfig = &tls.Config{ 423 | RootCAs: pool, 424 | } 425 | } 426 | } 427 | 428 | client := http.Client{ 429 | Transport: &http.Transport{ 430 | Proxy: proxyFunc, 431 | TLSClientConfig: tlsConfig, 432 | }, 433 | } 434 | 435 | return &client, nil 436 | } 437 | 438 | // New creates a new RPC client based on the provided connection configuration 439 | // details. The notification handlers parameter may be nil if you are not 440 | // interested in receiving notifications and will be ignored if the 441 | // configuration is set to run in HTTP POST mode. 442 | func New(config *ConnConfig) (*Client, error) { 443 | httpClient, err := newHTTPClient(config) 444 | if err != nil { 445 | return nil, err 446 | } 447 | 448 | client := &Client{ 449 | config: config, 450 | httpClient: httpClient, 451 | requestList: list.New(), 452 | sendPostChan: make(chan *sendPostDetails, sendPostBufferSize), 453 | shutdown: make(chan struct{}), 454 | } 455 | 456 | log.Infof("Established connection to RPC server %s", config.Host) 457 | client.start() 458 | 459 | return client, nil 460 | } 461 | -------------------------------------------------------------------------------- /log.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package rpcclient 6 | 7 | import ( 8 | "github.com/btcsuite/btclog" 9 | ) 10 | 11 | // log is a logger that is initialized with no output filters. This 12 | // means the package will not perform any logging by default until the caller 13 | // requests it. 14 | var log btclog.Logger 15 | 16 | // The default amount of logging is none. 17 | func init() { 18 | DisableLog() 19 | } 20 | 21 | // DisableLog disables all library log output. Logging output is disabled 22 | // by default until UseLogger is called. 23 | func DisableLog() { 24 | log = btclog.Disabled 25 | } 26 | 27 | // UseLogger uses a specified Logger to output package logging info. 28 | func UseLogger(logger btclog.Logger) { 29 | log = logger 30 | } 31 | -------------------------------------------------------------------------------- /mining.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package rpcclient 6 | 7 | import ( 8 | "encoding/hex" 9 | "encoding/json" 10 | "errors" 11 | 12 | "github.com/btcsuite/btcd/chaincfg/chainhash" 13 | "github.com/btcsuite/btcutil" 14 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 15 | ) 16 | 17 | // FutureGenerateResult is a future promise to deliver the result of a 18 | // GenerateAsync RPC invocation (or an applicable error). 19 | type FutureGenerateResult chan *response 20 | 21 | // Receive waits for the response promised by the future and returns a list of 22 | // block hashes generated by the call. 23 | func (r FutureGenerateResult) Receive() ([]*chainhash.Hash, error) { 24 | res, err := receiveFuture(r) 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | // Unmarshal result as a list of strings. 30 | var result []string 31 | err = json.Unmarshal(res, &result) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | // Convert each block hash to a chainhash.Hash and store a pointer to 37 | // each. 38 | convertedResult := make([]*chainhash.Hash, len(result)) 39 | for i, hashString := range result { 40 | convertedResult[i], err = chainhash.NewHashFromStr(hashString) 41 | if err != nil { 42 | return nil, err 43 | } 44 | } 45 | 46 | return convertedResult, nil 47 | } 48 | 49 | // GenerateAsync returns an instance of a type that can be used to get 50 | // the result of the RPC at some future time by invoking the Receive function on 51 | // the returned instance. 52 | // 53 | // See Generate for the blocking version and more details. 54 | func (c *Client) GenerateAsync(numBlocks uint32) FutureGenerateResult { 55 | cmd := btcjson.NewGenerateCmd(numBlocks) 56 | return c.sendCmd(cmd) 57 | } 58 | 59 | // Generate generates numBlocks blocks and returns their hashes. 60 | func (c *Client) Generate(numBlocks uint32) ([]*chainhash.Hash, error) { 61 | return c.GenerateAsync(numBlocks).Receive() 62 | } 63 | 64 | // FutureGetGenerateResult is a future promise to deliver the result of a 65 | // GetGenerateAsync RPC invocation (or an applicable error). 66 | type FutureGetGenerateResult chan *response 67 | 68 | // Receive waits for the response promised by the future and returns true if the 69 | // server is set to mine, otherwise false. 70 | func (r FutureGetGenerateResult) Receive() (bool, error) { 71 | res, err := receiveFuture(r) 72 | if err != nil { 73 | return false, err 74 | } 75 | 76 | // Unmarshal result as a boolean. 77 | var result bool 78 | err = json.Unmarshal(res, &result) 79 | if err != nil { 80 | return false, err 81 | } 82 | 83 | return result, nil 84 | } 85 | 86 | // GetGenerateAsync returns an instance of a type that can be used to get 87 | // the result of the RPC at some future time by invoking the Receive function on 88 | // the returned instance. 89 | // 90 | // See GetGenerate for the blocking version and more details. 91 | func (c *Client) GetGenerateAsync() FutureGetGenerateResult { 92 | cmd := btcjson.NewGetGenerateCmd() 93 | return c.sendCmd(cmd) 94 | } 95 | 96 | // GetGenerate returns true if the server is set to mine, otherwise false. 97 | func (c *Client) GetGenerate() (bool, error) { 98 | return c.GetGenerateAsync().Receive() 99 | } 100 | 101 | // FutureSetGenerateResult is a future promise to deliver the result of a 102 | // SetGenerateAsync RPC invocation (or an applicable error). 103 | type FutureSetGenerateResult chan *response 104 | 105 | // Receive waits for the response promised by the future and returns an error if 106 | // any occurred when setting the server to generate coins (mine) or not. 107 | func (r FutureSetGenerateResult) Receive() error { 108 | _, err := receiveFuture(r) 109 | return err 110 | } 111 | 112 | // SetGenerateAsync returns an instance of a type that can be used to get the 113 | // result of the RPC at some future time by invoking the Receive function on the 114 | // returned instance. 115 | // 116 | // See SetGenerate for the blocking version and more details. 117 | func (c *Client) SetGenerateAsync(enable bool, numCPUs int) FutureSetGenerateResult { 118 | cmd := btcjson.NewSetGenerateCmd(enable, &numCPUs) 119 | return c.sendCmd(cmd) 120 | } 121 | 122 | // SetGenerate sets the server to generate coins (mine) or not. 123 | func (c *Client) SetGenerate(enable bool, numCPUs int) error { 124 | return c.SetGenerateAsync(enable, numCPUs).Receive() 125 | } 126 | 127 | // FutureGetHashesPerSecResult is a future promise to deliver the result of a 128 | // GetHashesPerSecAsync RPC invocation (or an applicable error). 129 | type FutureGetHashesPerSecResult chan *response 130 | 131 | // Receive waits for the response promised by the future and returns a recent 132 | // hashes per second performance measurement while generating coins (mining). 133 | // Zero is returned if the server is not mining. 134 | func (r FutureGetHashesPerSecResult) Receive() (int64, error) { 135 | res, err := receiveFuture(r) 136 | if err != nil { 137 | return -1, err 138 | } 139 | 140 | // Unmarshal result as an int64. 141 | var result int64 142 | err = json.Unmarshal(res, &result) 143 | if err != nil { 144 | return 0, err 145 | } 146 | 147 | return result, nil 148 | } 149 | 150 | // GetHashesPerSecAsync returns an instance of a type that can be used to get 151 | // the result of the RPC at some future time by invoking the Receive function on 152 | // the returned instance. 153 | // 154 | // See GetHashesPerSec for the blocking version and more details. 155 | func (c *Client) GetHashesPerSecAsync() FutureGetHashesPerSecResult { 156 | cmd := btcjson.NewGetHashesPerSecCmd() 157 | return c.sendCmd(cmd) 158 | } 159 | 160 | // GetHashesPerSec returns a recent hashes per second performance measurement 161 | // while generating coins (mining). Zero is returned if the server is not 162 | // mining. 163 | func (c *Client) GetHashesPerSec() (int64, error) { 164 | return c.GetHashesPerSecAsync().Receive() 165 | } 166 | 167 | // FutureGetMiningInfoResult is a future promise to deliver the result of a 168 | // GetMiningInfoAsync RPC invocation (or an applicable error). 169 | type FutureGetMiningInfoResult chan *response 170 | 171 | // Receive waits for the response promised by the future and returns the mining 172 | // information. 173 | func (r FutureGetMiningInfoResult) Receive() (*btcjson.GetMiningInfoResult, error) { 174 | res, err := receiveFuture(r) 175 | if err != nil { 176 | return nil, err 177 | } 178 | 179 | // Unmarshal result as a getmininginfo result object. 180 | var infoResult btcjson.GetMiningInfoResult 181 | err = json.Unmarshal(res, &infoResult) 182 | if err != nil { 183 | return nil, err 184 | } 185 | 186 | return &infoResult, nil 187 | } 188 | 189 | // GetMiningInfoAsync returns an instance of a type that can be used to get 190 | // the result of the RPC at some future time by invoking the Receive function on 191 | // the returned instance. 192 | // 193 | // See GetMiningInfo for the blocking version and more details. 194 | func (c *Client) GetMiningInfoAsync() FutureGetMiningInfoResult { 195 | cmd := btcjson.NewGetMiningInfoCmd() 196 | return c.sendCmd(cmd) 197 | } 198 | 199 | // GetMiningInfo returns mining information. 200 | func (c *Client) GetMiningInfo() (*btcjson.GetMiningInfoResult, error) { 201 | return c.GetMiningInfoAsync().Receive() 202 | } 203 | 204 | // FutureGetNetworkHashPS is a future promise to deliver the result of a 205 | // GetNetworkHashPSAsync RPC invocation (or an applicable error). 206 | type FutureGetNetworkHashPS chan *response 207 | 208 | // Receive waits for the response promised by the future and returns the 209 | // estimated network hashes per second for the block heights provided by the 210 | // parameters. 211 | func (r FutureGetNetworkHashPS) Receive() (int64, error) { 212 | res, err := receiveFuture(r) 213 | if err != nil { 214 | return -1, err 215 | } 216 | 217 | // Unmarshal result as an int64. 218 | var result int64 219 | err = json.Unmarshal(res, &result) 220 | if err != nil { 221 | return 0, err 222 | } 223 | 224 | return result, nil 225 | } 226 | 227 | // GetNetworkHashPSAsync returns an instance of a type that can be used to get 228 | // the result of the RPC at some future time by invoking the Receive function on 229 | // the returned instance. 230 | // 231 | // See GetNetworkHashPS for the blocking version and more details. 232 | func (c *Client) GetNetworkHashPSAsync() FutureGetNetworkHashPS { 233 | cmd := btcjson.NewGetNetworkHashPSCmd(nil, nil) 234 | return c.sendCmd(cmd) 235 | } 236 | 237 | // GetNetworkHashPS returns the estimated network hashes per second using the 238 | // default number of blocks and the most recent block height. 239 | // 240 | // See GetNetworkHashPS2 to override the number of blocks to use and 241 | // GetNetworkHashPS3 to override the height at which to calculate the estimate. 242 | func (c *Client) GetNetworkHashPS() (int64, error) { 243 | return c.GetNetworkHashPSAsync().Receive() 244 | } 245 | 246 | // GetNetworkHashPS2Async returns an instance of a type that can be used to get 247 | // the result of the RPC at some future time by invoking the Receive function on 248 | // the returned instance. 249 | // 250 | // See GetNetworkHashPS2 for the blocking version and more details. 251 | func (c *Client) GetNetworkHashPS2Async(blocks int) FutureGetNetworkHashPS { 252 | cmd := btcjson.NewGetNetworkHashPSCmd(&blocks, nil) 253 | return c.sendCmd(cmd) 254 | } 255 | 256 | // GetNetworkHashPS2 returns the estimated network hashes per second for the 257 | // specified previous number of blocks working backwards from the most recent 258 | // block height. The blocks parameter can also be -1 in which case the number 259 | // of blocks since the last difficulty change will be used. 260 | // 261 | // See GetNetworkHashPS to use defaults and GetNetworkHashPS3 to override the 262 | // height at which to calculate the estimate. 263 | func (c *Client) GetNetworkHashPS2(blocks int) (int64, error) { 264 | return c.GetNetworkHashPS2Async(blocks).Receive() 265 | } 266 | 267 | // GetNetworkHashPS3Async returns an instance of a type that can be used to get 268 | // the result of the RPC at some future time by invoking the Receive function on 269 | // the returned instance. 270 | // 271 | // See GetNetworkHashPS3 for the blocking version and more details. 272 | func (c *Client) GetNetworkHashPS3Async(blocks, height int) FutureGetNetworkHashPS { 273 | cmd := btcjson.NewGetNetworkHashPSCmd(&blocks, &height) 274 | return c.sendCmd(cmd) 275 | } 276 | 277 | // GetNetworkHashPS3 returns the estimated network hashes per second for the 278 | // specified previous number of blocks working backwards from the specified 279 | // block height. The blocks parameter can also be -1 in which case the number 280 | // of blocks since the last difficulty change will be used. 281 | // 282 | // See GetNetworkHashPS and GetNetworkHashPS2 to use defaults. 283 | func (c *Client) GetNetworkHashPS3(blocks, height int) (int64, error) { 284 | return c.GetNetworkHashPS3Async(blocks, height).Receive() 285 | } 286 | 287 | // FutureGetWork is a future promise to deliver the result of a 288 | // GetWorkAsync RPC invocation (or an applicable error). 289 | type FutureGetWork chan *response 290 | 291 | // Receive waits for the response promised by the future and returns the hash 292 | // data to work on. 293 | func (r FutureGetWork) Receive() (*btcjson.GetWorkResult, error) { 294 | res, err := receiveFuture(r) 295 | if err != nil { 296 | return nil, err 297 | } 298 | 299 | // Unmarshal result as a getwork result object. 300 | var result btcjson.GetWorkResult 301 | err = json.Unmarshal(res, &result) 302 | if err != nil { 303 | return nil, err 304 | } 305 | 306 | return &result, nil 307 | } 308 | 309 | // GetWorkAsync returns an instance of a type that can be used to get the result 310 | // of the RPC at some future time by invoking the Receive function on the 311 | // returned instance. 312 | // 313 | // See GetWork for the blocking version and more details. 314 | func (c *Client) GetWorkAsync() FutureGetWork { 315 | cmd := btcjson.NewGetWorkCmd(nil) 316 | return c.sendCmd(cmd) 317 | } 318 | 319 | // GetWork returns hash data to work on. 320 | // 321 | // See GetWorkSubmit to submit the found solution. 322 | func (c *Client) GetWork() (*btcjson.GetWorkResult, error) { 323 | return c.GetWorkAsync().Receive() 324 | } 325 | 326 | // FutureGetWorkSubmit is a future promise to deliver the result of a 327 | // GetWorkSubmitAsync RPC invocation (or an applicable error). 328 | type FutureGetWorkSubmit chan *response 329 | 330 | // Receive waits for the response promised by the future and returns whether 331 | // or not the submitted block header was accepted. 332 | func (r FutureGetWorkSubmit) Receive() (bool, error) { 333 | res, err := receiveFuture(r) 334 | if err != nil { 335 | return false, err 336 | } 337 | 338 | // Unmarshal result as a boolean. 339 | var accepted bool 340 | err = json.Unmarshal(res, &accepted) 341 | if err != nil { 342 | return false, err 343 | } 344 | 345 | return accepted, nil 346 | } 347 | 348 | // GetWorkSubmitAsync returns an instance of a type that can be used to get the 349 | // result of the RPC at some future time by invoking the Receive function on the 350 | // returned instance. 351 | // 352 | // See GetWorkSubmit for the blocking version and more details. 353 | func (c *Client) GetWorkSubmitAsync(data string) FutureGetWorkSubmit { 354 | cmd := btcjson.NewGetWorkCmd(&data) 355 | return c.sendCmd(cmd) 356 | } 357 | 358 | // GetWorkSubmit submits a block header which is a solution to previously 359 | // requested data and returns whether or not the solution was accepted. 360 | // 361 | // See GetWork to request data to work on. 362 | func (c *Client) GetWorkSubmit(data string) (bool, error) { 363 | return c.GetWorkSubmitAsync(data).Receive() 364 | } 365 | 366 | // FutureSubmitBlockResult is a future promise to deliver the result of a 367 | // SubmitBlockAsync RPC invocation (or an applicable error). 368 | type FutureSubmitBlockResult chan *response 369 | 370 | // Receive waits for the response promised by the future and returns an error if 371 | // any occurred when submitting the block. 372 | func (r FutureSubmitBlockResult) Receive() error { 373 | res, err := receiveFuture(r) 374 | if err != nil { 375 | return err 376 | } 377 | 378 | if string(res) != "null" { 379 | var result string 380 | err = json.Unmarshal(res, &result) 381 | if err != nil { 382 | return err 383 | } 384 | 385 | return errors.New(result) 386 | } 387 | 388 | return nil 389 | 390 | } 391 | 392 | // SubmitBlockAsync returns an instance of a type that can be used to get the 393 | // result of the RPC at some future time by invoking the Receive function on the 394 | // returned instance. 395 | // 396 | // See SubmitBlock for the blocking version and more details. 397 | func (c *Client) SubmitBlockAsync(block *btcutil.Block, options *btcjson.SubmitBlockOptions) FutureSubmitBlockResult { 398 | blockHex := "" 399 | if block != nil { 400 | blockBytes, err := block.Bytes() 401 | if err != nil { 402 | return newFutureError(err) 403 | } 404 | 405 | blockHex = hex.EncodeToString(blockBytes) 406 | } 407 | 408 | cmd := btcjson.NewSubmitBlockCmd(blockHex, options) 409 | return c.sendCmd(cmd) 410 | } 411 | 412 | // SubmitBlock attempts to submit a new block into the bitcoin network. 413 | func (c *Client) SubmitBlock(block *btcutil.Block, options *btcjson.SubmitBlockOptions) error { 414 | return c.SubmitBlockAsync(block, options).Receive() 415 | } 416 | 417 | // TODO(davec): Implement GetBlockTemplate 418 | -------------------------------------------------------------------------------- /net.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package rpcclient 6 | 7 | import ( 8 | "encoding/json" 9 | 10 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 11 | ) 12 | 13 | // AddNodeCommand enumerates the available commands that the AddNode function 14 | // accepts. 15 | type AddNodeCommand string 16 | 17 | // Constants used to indicate the command for the AddNode function. 18 | const ( 19 | // ANAdd indicates the specified host should be added as a persistent 20 | // peer. 21 | ANAdd AddNodeCommand = "add" 22 | 23 | // ANRemove indicates the specified peer should be removed. 24 | ANRemove AddNodeCommand = "remove" 25 | 26 | // ANOneTry indicates the specified host should try to connect once, 27 | // but it should not be made persistent. 28 | ANOneTry AddNodeCommand = "onetry" 29 | ) 30 | 31 | // String returns the AddNodeCommand in human-readable form. 32 | func (cmd AddNodeCommand) String() string { 33 | return string(cmd) 34 | } 35 | 36 | // FutureAddNodeResult is a future promise to deliver the result of an 37 | // AddNodeAsync RPC invocation (or an applicable error). 38 | type FutureAddNodeResult chan *response 39 | 40 | // Receive waits for the response promised by the future and returns an error if 41 | // any occurred when performing the specified command. 42 | func (r FutureAddNodeResult) Receive() error { 43 | _, err := receiveFuture(r) 44 | return err 45 | } 46 | 47 | // AddNodeAsync returns an instance of a type that can be used to get the result 48 | // of the RPC at some future time by invoking the Receive function on the 49 | // returned instance. 50 | // 51 | // See AddNode for the blocking version and more details. 52 | func (c *Client) AddNodeAsync(host string, command AddNodeCommand) FutureAddNodeResult { 53 | cmd := btcjson.NewAddNodeCmd(host, btcjson.AddNodeSubCmd(command)) 54 | return c.sendCmd(cmd) 55 | } 56 | 57 | // AddNode attempts to perform the passed command on the passed persistent peer. 58 | // For example, it can be used to add or a remove a persistent peer, or to do 59 | // a one time connection to a peer. 60 | // 61 | // It may not be used to remove non-persistent peers. 62 | func (c *Client) AddNode(host string, command AddNodeCommand) error { 63 | return c.AddNodeAsync(host, command).Receive() 64 | } 65 | 66 | // FutureGetAddedNodeInfoResult is a future promise to deliver the result of a 67 | // GetAddedNodeInfoAsync RPC invocation (or an applicable error). 68 | type FutureGetAddedNodeInfoResult chan *response 69 | 70 | // Receive waits for the response promised by the future and returns information 71 | // about manually added (persistent) peers. 72 | func (r FutureGetAddedNodeInfoResult) Receive() ([]btcjson.GetAddedNodeInfoResult, error) { 73 | res, err := receiveFuture(r) 74 | if err != nil { 75 | return nil, err 76 | } 77 | 78 | // Unmarshal as an array of getaddednodeinfo result objects. 79 | var nodeInfo []btcjson.GetAddedNodeInfoResult 80 | err = json.Unmarshal(res, &nodeInfo) 81 | if err != nil { 82 | return nil, err 83 | } 84 | 85 | return nodeInfo, nil 86 | } 87 | 88 | // GetAddedNodeInfoAsync returns an instance of a type that can be used to get 89 | // the result of the RPC at some future time by invoking the Receive function on 90 | // the returned instance. 91 | // 92 | // See GetAddedNodeInfo for the blocking version and more details. 93 | func (c *Client) GetAddedNodeInfoAsync(peer string) FutureGetAddedNodeInfoResult { 94 | cmd := btcjson.NewGetAddedNodeInfoCmd(true, &peer) 95 | return c.sendCmd(cmd) 96 | } 97 | 98 | // GetAddedNodeInfo returns information about manually added (persistent) peers. 99 | // 100 | // See GetAddedNodeInfoNoDNS to retrieve only a list of the added (persistent) 101 | // peers. 102 | func (c *Client) GetAddedNodeInfo(peer string) ([]btcjson.GetAddedNodeInfoResult, error) { 103 | return c.GetAddedNodeInfoAsync(peer).Receive() 104 | } 105 | 106 | // FutureGetAddedNodeInfoNoDNSResult is a future promise to deliver the result 107 | // of a GetAddedNodeInfoNoDNSAsync RPC invocation (or an applicable error). 108 | type FutureGetAddedNodeInfoNoDNSResult chan *response 109 | 110 | // Receive waits for the response promised by the future and returns a list of 111 | // manually added (persistent) peers. 112 | func (r FutureGetAddedNodeInfoNoDNSResult) Receive() ([]string, error) { 113 | res, err := receiveFuture(r) 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | // Unmarshal result as an array of strings. 119 | var nodes []string 120 | err = json.Unmarshal(res, &nodes) 121 | if err != nil { 122 | return nil, err 123 | } 124 | 125 | return nodes, nil 126 | } 127 | 128 | // GetAddedNodeInfoNoDNSAsync returns an instance of a type that can be used to 129 | // get the result of the RPC at some future time by invoking the Receive 130 | // function on the returned instance. 131 | // 132 | // See GetAddedNodeInfoNoDNS for the blocking version and more details. 133 | func (c *Client) GetAddedNodeInfoNoDNSAsync(peer string) FutureGetAddedNodeInfoNoDNSResult { 134 | cmd := btcjson.NewGetAddedNodeInfoCmd(false, &peer) 135 | return c.sendCmd(cmd) 136 | } 137 | 138 | // GetAddedNodeInfoNoDNS returns a list of manually added (persistent) peers. 139 | // This works by setting the dns flag to false in the underlying RPC. 140 | // 141 | // See GetAddedNodeInfo to obtain more information about each added (persistent) 142 | // peer. 143 | func (c *Client) GetAddedNodeInfoNoDNS(peer string) ([]string, error) { 144 | return c.GetAddedNodeInfoNoDNSAsync(peer).Receive() 145 | } 146 | 147 | // FutureGetConnectionCountResult is a future promise to deliver the result 148 | // of a GetConnectionCountAsync RPC invocation (or an applicable error). 149 | type FutureGetConnectionCountResult chan *response 150 | 151 | // Receive waits for the response promised by the future and returns the number 152 | // of active connections to other peers. 153 | func (r FutureGetConnectionCountResult) Receive() (int64, error) { 154 | res, err := receiveFuture(r) 155 | if err != nil { 156 | return 0, err 157 | } 158 | 159 | // Unmarshal result as an int64. 160 | var count int64 161 | err = json.Unmarshal(res, &count) 162 | if err != nil { 163 | return 0, err 164 | } 165 | 166 | return count, nil 167 | } 168 | 169 | // GetConnectionCountAsync returns an instance of a type that can be used to get 170 | // the result of the RPC at some future time by invoking the Receive function on 171 | // the returned instance. 172 | // 173 | // See GetConnectionCount for the blocking version and more details. 174 | func (c *Client) GetConnectionCountAsync() FutureGetConnectionCountResult { 175 | cmd := btcjson.NewGetConnectionCountCmd() 176 | return c.sendCmd(cmd) 177 | } 178 | 179 | // GetConnectionCount returns the number of active connections to other peers. 180 | func (c *Client) GetConnectionCount() (int64, error) { 181 | return c.GetConnectionCountAsync().Receive() 182 | } 183 | 184 | // FuturePingResult is a future promise to deliver the result of a PingAsync RPC 185 | // invocation (or an applicable error). 186 | type FuturePingResult chan *response 187 | 188 | // Receive waits for the response promised by the future and returns the result 189 | // of queueing a ping to be sent to each connected peer. 190 | func (r FuturePingResult) Receive() error { 191 | _, err := receiveFuture(r) 192 | return err 193 | } 194 | 195 | // PingAsync returns an instance of a type that can be used to get the result of 196 | // the RPC at some future time by invoking the Receive function on the returned 197 | // instance. 198 | // 199 | // See Ping for the blocking version and more details. 200 | func (c *Client) PingAsync() FuturePingResult { 201 | cmd := btcjson.NewPingCmd() 202 | return c.sendCmd(cmd) 203 | } 204 | 205 | // Ping queues a ping to be sent to each connected peer. 206 | // 207 | // Use the GetPeerInfo function and examine the PingTime and PingWait fields to 208 | // access the ping times. 209 | func (c *Client) Ping() error { 210 | return c.PingAsync().Receive() 211 | } 212 | 213 | // FutureGetPeerInfoResult is a future promise to deliver the result of a 214 | // GetPeerInfoAsync RPC invocation (or an applicable error). 215 | type FutureGetPeerInfoResult chan *response 216 | 217 | // Receive waits for the response promised by the future and returns data about 218 | // each connected network peer. 219 | func (r FutureGetPeerInfoResult) Receive() ([]btcjson.GetPeerInfoResult, error) { 220 | res, err := receiveFuture(r) 221 | if err != nil { 222 | return nil, err 223 | } 224 | 225 | // Unmarshal result as an array of getpeerinfo result objects. 226 | var peerInfo []btcjson.GetPeerInfoResult 227 | err = json.Unmarshal(res, &peerInfo) 228 | if err != nil { 229 | return nil, err 230 | } 231 | 232 | return peerInfo, nil 233 | } 234 | 235 | // GetPeerInfoAsync returns an instance of a type that can be used to get the 236 | // result of the RPC at some future time by invoking the Receive function on the 237 | // returned instance. 238 | // 239 | // See GetPeerInfo for the blocking version and more details. 240 | func (c *Client) GetPeerInfoAsync() FutureGetPeerInfoResult { 241 | cmd := btcjson.NewGetPeerInfoCmd() 242 | return c.sendCmd(cmd) 243 | } 244 | 245 | // GetPeerInfo returns data about each connected network peer. 246 | func (c *Client) GetPeerInfo() ([]btcjson.GetPeerInfoResult, error) { 247 | return c.GetPeerInfoAsync().Receive() 248 | } 249 | 250 | // FutureGetNetTotalsResult is a future promise to deliver the result of a 251 | // GetNetTotalsAsync RPC invocation (or an applicable error). 252 | type FutureGetNetTotalsResult chan *response 253 | 254 | // Receive waits for the response promised by the future and returns network 255 | // traffic statistics. 256 | func (r FutureGetNetTotalsResult) Receive() (*btcjson.GetNetTotalsResult, error) { 257 | res, err := receiveFuture(r) 258 | if err != nil { 259 | return nil, err 260 | } 261 | 262 | // Unmarshal result as a getnettotals result object. 263 | var totals btcjson.GetNetTotalsResult 264 | err = json.Unmarshal(res, &totals) 265 | if err != nil { 266 | return nil, err 267 | } 268 | 269 | return &totals, nil 270 | } 271 | 272 | // GetNetTotalsAsync returns an instance of a type that can be used to get the 273 | // result of the RPC at some future time by invoking the Receive function on the 274 | // returned instance. 275 | // 276 | // See GetNetTotals for the blocking version and more details. 277 | func (c *Client) GetNetTotalsAsync() FutureGetNetTotalsResult { 278 | cmd := btcjson.NewGetNetTotalsCmd() 279 | return c.sendCmd(cmd) 280 | } 281 | 282 | // GetNetTotals returns network traffic statistics. 283 | func (c *Client) GetNetTotals() (*btcjson.GetNetTotalsResult, error) { 284 | return c.GetNetTotalsAsync().Receive() 285 | } 286 | 287 | // FutureGetTxOutSetInfoResult is a future promise to deliver the result of a 288 | // GetTxOutSetInfoAsync RPC invocation (or an applicable error). 289 | type FutureGetTxOutSetInfoResult chan *response 290 | 291 | // Receive waits for the response promised by the future and returns network 292 | // traffic statistics. 293 | func (r FutureGetTxOutSetInfoResult) Receive() (*btcjson.GetTxOutSetInfoResult, error) { 294 | res, err := receiveFuture(r) 295 | if err != nil { 296 | return nil, err 297 | } 298 | // Unmarshal result as a gettxoutsetinfo result object. 299 | var txOutSetInfo btcjson.GetTxOutSetInfoResult 300 | err = json.Unmarshal(res, &txOutSetInfo) 301 | if err != nil { 302 | return nil, err 303 | } 304 | return &txOutSetInfo, nil 305 | } 306 | 307 | // GetTxOutSetInfoAsync returns an instance of a type that can be used to get the 308 | // result of the RPC at some future time by invoking the Receive function on the 309 | // returned instance. 310 | // 311 | // See GetTxOutSetInfo for the blocking version and more details. 312 | func (c *Client) GetTxOutSetInfoAsync() FutureGetTxOutSetInfoResult { 313 | cmd := btcjson.NewGetTxOutSetInfoCmd() 314 | return c.sendCmd(cmd) 315 | } 316 | 317 | // GetTxOutSetInfo returns statistics about the database of unspent transaction outputs 318 | func (c *Client) GetTxOutSetInfo() (*btcjson.GetTxOutSetInfoResult, error) { 319 | return c.GetTxOutSetInfoAsync().Receive() 320 | } 321 | -------------------------------------------------------------------------------- /rawrequest.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2014-2017 The btcsuite developers 2 | // Use of this source code is governed by an ISC 3 | // license that can be found in the LICENSE file. 4 | 5 | package rpcclient 6 | 7 | import ( 8 | "encoding/json" 9 | "errors" 10 | 11 | "github.com/stevenroose/go-bitcoin-core-rpc/btcjson" 12 | ) 13 | 14 | // FutureRawResult is a future promise to deliver the result of a RawRequest RPC 15 | // invocation (or an applicable error). 16 | type FutureRawResult chan *response 17 | 18 | // Receive waits for the response promised by the future and returns the raw 19 | // response, or an error if the request was unsuccessful. 20 | func (r FutureRawResult) Receive() (json.RawMessage, error) { 21 | return receiveFuture(r) 22 | } 23 | 24 | // RawRequestAsync returns an instance of a type that can be used to get the 25 | // result of a custom RPC request at some future time by invoking the Receive 26 | // function on the returned instance. 27 | // 28 | // See RawRequest for the blocking version and more details. 29 | func (c *Client) RawRequestAsync(method string, params []json.RawMessage) FutureRawResult { 30 | // Method may not be empty. 31 | if method == "" { 32 | return newFutureError(errors.New("no method")) 33 | } 34 | 35 | // Marshal parameters as "[]" instead of "null" when no parameters 36 | // are passed. 37 | if params == nil { 38 | params = []json.RawMessage{} 39 | } 40 | 41 | // Create a raw JSON-RPC request using the provided method and params 42 | // and marshal it. This is done rather than using the sendCmd function 43 | // since that relies on marshalling registered btcjson commands rather 44 | // than custom commands. 45 | id := c.NextID() 46 | rawRequest := &btcjson.Request{ 47 | Jsonrpc: "1.0", 48 | ID: id, 49 | Method: method, 50 | Params: params, 51 | } 52 | marshalledJSON, err := json.Marshal(rawRequest) 53 | if err != nil { 54 | return newFutureError(err) 55 | } 56 | 57 | // Generate the request and send it along with a channel to respond on. 58 | responseChan := make(chan *response, 1) 59 | jReq := &jsonRequest{ 60 | id: id, 61 | method: method, 62 | cmd: nil, 63 | marshalledJSON: marshalledJSON, 64 | responseChan: responseChan, 65 | } 66 | c.sendRequest(jReq) 67 | 68 | return responseChan 69 | } 70 | 71 | // RawRequest allows the caller to send a raw or custom request to the server. 72 | // This method may be used to send and receive requests and responses for 73 | // requests that are not handled by this client package, or to proxy partially 74 | // unmarshaled requests to another JSON-RPC server if a request cannot be 75 | // handled directly. 76 | func (c *Client) RawRequest(method string, params []json.RawMessage) (json.RawMessage, error) { 77 | return c.RawRequestAsync(method, params).Receive() 78 | } 79 | --------------------------------------------------------------------------------