btcjson: Replace btcjson with version 2.

This commit removes the old and deprecated btcjsonv1 package, moves the
new version 2 package into its place, and updates all imports accordingly.
This commit is contained in:
Dave Collins 2015-04-05 23:15:49 -05:00
parent 1e98e23d1f
commit d8a4423b90
63 changed files with 166 additions and 16401 deletions

View file

@ -7,12 +7,11 @@ btcjson
Package btcjson implements concrete types for marshalling to and from the
bitcoin JSON-RPC API. A comprehensive suite of tests is provided to ensure
proper functionality. Package btcjson is licensed under the copyfree ISC
license.
proper functionality.
Although this package was primarily written for btcd, it has intentionally been
designed so it can be used as a standalone package for any projects needing to
marshal to and from bitcoin JSON-RPC requests and responses.
Although this package was primarily written for the btcsuite, it has
intentionally been designed so it can be used as a standalone package for any
projects needing to marshal to and from bitcoin JSON-RPC requests and responses.
Note that although it's possible to use this package directly to implement an
RPC client, it is not recommended since it is only intended as an infrastructure
@ -23,42 +22,6 @@ management, websocket support, automatic notification re-registration on
reconnect, and conversion from the raw underlying RPC types (strings, floats,
ints, etc) to higher-level types with many nice and useful properties.
## JSON RPC
Bitcoin provides an extensive API call list to control the chain and wallet
servers through JSON-RPC. These can be used to get information from the server
or to cause the server to perform some action.
The general form of the commands are:
```JSON
{"jsonrpc": "1.0", "id":"test", "method": "getinfo", "params": []}
```
btcjson provides code to easily create these commands from go (as some of the
commands can be fairly complex), to send the commands to a running bitcoin RPC
server, and to handle the replies (putting them in useful Go data structures).
## Sample Use
```Go
// Create a new command.
cmd, err := btcjson.NewGetBlockCountCmd()
if err != nil {
// Handle error
}
// Marshal the command to a JSON-RPC formatted byte slice.
marshalled, err := btcjson.MarshalCmd(id, cmd)
if err != nil {
// Handle error
}
// At this point marshalled contains the raw bytes that are ready to send
// to the RPC server to issue the command.
fmt.Printf("%s\n", marshalled)
```
## Documentation
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)]
@ -78,6 +41,26 @@ http://localhost:6060/pkg/github.com/btcsuite/btcd/btcjson
$ go get github.com/btcsuite/btcd/btcjson
```
## Examples
* [Marshal Command]
(http://godoc.org/github.com/btcsuite/btcd/btcjson#example-MarshalCmd)
Demonstrates how to create and marshal a command into a JSON-RPC request.
* [Unmarshal Command]
(http://godoc.org/github.com/btcsuite/btcd/btcjson#example-UnmarshalCmd)
Demonstrates how to unmarshal a JSON-RPC request and then unmarshal the
concrete request into a concrete command.
* [Marshal Response]
(http://godoc.org/github.com/btcsuite/btcd/btcjson#example-MarshalResponse)
Demonstrates how to marshal a JSON-RPC response.
* [Unmarshal Response]
(http://godoc.org/github.com/btcsuite/btcd/btcjson#example-package--UnmarshalResponse)
Demonstrates how to unmarshal a JSON-RPC response and then unmarshal the
result field in the response to a concrete type.
## GPG Verification Key
All official release tags are signed by Conformal so users can ensure the code

View file

@ -11,7 +11,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestBtcdExtCmds tests all of the btcd extended commands marshal and unmarshal

View file

@ -11,7 +11,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestBtcWalletExtCmds tests all of the btcwallet extended commands marshal and

View file

@ -1,88 +0,0 @@
btcws
=====
[![Build Status](https://travis-ci.org/btcsuite/btcd.png?branch=master)]
(https://travis-ci.org/btcsuite/btcws)
Package btcws implements extensions to the standard bitcoind JSON-RPC
API for the btcd suite of programs (btcd, btcwallet, and btcgui).
Importing this package registers all implemented custom requests with
btcjson (using btcjson.RegisterCustomCmd).
## Sample Use
```Go
// Client Side
import (
"golang.org/x/net/websocket"
"github.com/btcsuite/btcd/btcjson/btcws"
)
// Create rescan command.
id := 0
addrs := map[string]struct{}{
"17XhEvq9Nahdj7Xe1nv6oRe1tEmaHUuynH": struct{},
}
cmd, err := btcws.NewRescanCmd(id, 270000, addrs)
// Set up a handler for a reply with id 0.
AddReplyHandler(id, func(reply map[string]interface{}) {
// Deal with reply.
})
// JSON marshal and send rescan request to websocket connection.
websocket.JSON.Send(btcdWSConn, cmd)
// Server Side
import (
"golang.org/x/net/websocket"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcjson/btcws"
)
// Get marshaled request.
var b []byte
err := websocket.Message.Receive(clientWSConn, &b)
// Parse marshaled command.
cmd, err := btcjson.ParseMarshaledCmd(b)
// If this is a rescan command, handle and reply.
rcmd, ok := cmd.(*btcws.RescanCmd)
if ok {
// Do stuff
var reply []byte
err := websocket.Message.Send(clientWSConn, reply)
}
```
## Installation
```bash
$ go get github.com/btcsuite/btcd/btcjson/btcws
```
## GPG Verification Key
All official release tags are signed by Conformal so users can ensure the code
has not been tampered with and is coming from Conformal. To verify the
signature perform the following:
- Download the public key from the Conformal website at
https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt
- Import the public key into your GPG keyring:
```bash
gpg --import GIT-GPG-KEY-conformal.txt
```
- Verify the release tag with the following command where `TAG_NAME` is a
placeholder for the specific tag:
```bash
git tag -v TAG_NAME
```
## License
Package btcws is licensed under the liberal ISC License.

File diff suppressed because it is too large Load diff

View file

@ -1,364 +0,0 @@
// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
// this has to be in the real package so we can mock up structs
package btcws
import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson"
"github.com/davecgh/go-spew/spew"
)
var testAccount = "account"
var cmdtests = []struct {
name string
f func() (btcjson.Cmd, error)
result btcjson.Cmd // after marshal and unmarshal
}{
{
name: "createencryptedwallet",
f: func() (btcjson.Cmd, error) {
return NewCreateEncryptedWalletCmd(
float64(1),
"banana"), nil
},
result: &CreateEncryptedWalletCmd{
id: float64(1),
Passphrase: "banana",
},
},
{
name: "createnewaccount",
f: func() (btcjson.Cmd, error) {
return NewCreateNewAccountCmd(
float64(1),
"account"), nil
},
result: &CreateNewAccountCmd{
id: float64(1),
Account: "account",
},
},
{
name: "getbestblock",
f: func() (btcjson.Cmd, error) {
return NewGetBestBlockCmd(float64(1)), nil
},
result: &GetBestBlockCmd{
id: float64(1),
},
},
{
name: "getcurrentnet",
f: func() (btcjson.Cmd, error) {
return NewGetCurrentNetCmd(float64(1)), nil
},
result: &GetCurrentNetCmd{
id: float64(1),
},
},
{
name: "getunconfirmedbalance no optargs",
f: func() (btcjson.Cmd, error) {
return NewGetUnconfirmedBalanceCmd(float64(1))
},
result: &GetUnconfirmedBalanceCmd{
id: float64(1),
Account: "",
},
},
{
name: "getunconfirmedbalance one optarg",
f: func() (btcjson.Cmd, error) {
return NewGetUnconfirmedBalanceCmd(float64(1),
testAccount)
},
result: &GetUnconfirmedBalanceCmd{
id: float64(1),
Account: testAccount,
},
},
{
name: "listaddresstransactions no optargs",
f: func() (btcjson.Cmd, error) {
addrs := []string{
"17XhEvq9Nahdj7Xe1nv6oRe1tEmaHUuynH",
}
return NewListAddressTransactionsCmd(
float64(1),
addrs)
},
result: &ListAddressTransactionsCmd{
id: float64(1),
Account: "",
Addresses: []string{
"17XhEvq9Nahdj7Xe1nv6oRe1tEmaHUuynH",
},
},
},
{
name: "listaddresstransactions one optarg",
f: func() (btcjson.Cmd, error) {
addrs := []string{
"17XhEvq9Nahdj7Xe1nv6oRe1tEmaHUuynH",
}
return NewListAddressTransactionsCmd(
float64(1),
addrs,
testAccount)
},
result: &ListAddressTransactionsCmd{
id: float64(1),
Account: testAccount,
Addresses: []string{
"17XhEvq9Nahdj7Xe1nv6oRe1tEmaHUuynH",
},
},
},
{
name: "listalltransactions no optargs",
f: func() (btcjson.Cmd, error) {
return NewListAllTransactionsCmd(float64(1))
},
result: &ListAllTransactionsCmd{
id: float64(1),
Account: nil,
},
},
{
name: "listalltransactions one optarg",
f: func() (btcjson.Cmd, error) {
return NewListAllTransactionsCmd(
float64(1),
testAccount)
},
result: &ListAllTransactionsCmd{
id: float64(1),
Account: &testAccount,
},
},
{
name: "notifyreceived",
f: func() (btcjson.Cmd, error) {
addrs := []string{
"17XhEvq9Nahdj7Xe1nv6oRe1tEmaHUuynH",
}
return NewNotifyReceivedCmd(
float64(1),
addrs), nil
},
result: &NotifyReceivedCmd{
id: float64(1),
Addresses: []string{
"17XhEvq9Nahdj7Xe1nv6oRe1tEmaHUuynH",
},
},
},
{
name: "notifynewtransactions",
f: func() (btcjson.Cmd, error) {
return NewNotifyNewTransactionsCmd(
float64(1),
true)
},
result: &NotifyNewTransactionsCmd{
id: float64(1),
Verbose: true,
},
},
{
name: "notifyspent",
f: func() (btcjson.Cmd, error) {
ops := []OutPoint{
{
Hash: "000102030405060708091011121314" +
"1516171819202122232425262728" +
"293031",
Index: 1,
},
}
return NewNotifySpentCmd(float64(1), ops), nil
},
result: &NotifySpentCmd{
id: float64(1),
OutPoints: []OutPoint{
{
Hash: "000102030405060708091011121314" +
"1516171819202122232425262728" +
"293031",
Index: 1,
},
},
},
},
{
name: "renameaccount",
f: func() (btcjson.Cmd, error) {
return NewRenameAccountCmd(
float64(1),
"old",
"new"), nil
},
result: &RenameAccountCmd{
id: float64(1),
OldAccount: "old",
NewAccount: "new",
},
},
{
name: "rescan no optargs",
f: func() (btcjson.Cmd, error) {
addrs := []string{"17XhEvq9Nahdj7Xe1nv6oRe1tEmaHUuynH"}
ops := []OutPoint{
{
Hash: "000102030405060708091011121314" +
"1516171819202122232425262728" +
"293031",
Index: 1,
},
}
return NewRescanCmd(
float64(1),
"0000000000000002a775aec59dc6a9e4bb1c025cf1b8c2195dd9dc3998c827c5",
addrs,
ops)
},
result: &RescanCmd{
id: float64(1),
BeginBlock: "0000000000000002a775aec59dc6a9e4bb1c025cf1b8c2195dd9dc3998c827c5",
Addresses: []string{"17XhEvq9Nahdj7Xe1nv6oRe1tEmaHUuynH"},
OutPoints: []OutPoint{
{
Hash: "000102030405060708091011121314" +
"1516171819202122232425262728" +
"293031",
Index: 1,
},
},
EndBlock: "",
},
},
{
name: "rescan one optarg",
f: func() (btcjson.Cmd, error) {
addrs := []string{"17XhEvq9Nahdj7Xe1nv6oRe1tEmaHUuynH"}
ops := []OutPoint{
{
Hash: "000102030405060708091011121314" +
"1516171819202122232425262728" +
"293031",
Index: 1,
},
}
return NewRescanCmd(
float64(1),
"0000000000000002a775aec59dc6a9e4bb1c025cf1b8c2195dd9dc3998c827c5",
addrs,
ops,
"0000000000000001c091ada69f444dc0282ecaabe4808ddbb2532e5555db0c03")
},
result: &RescanCmd{
id: float64(1),
BeginBlock: "0000000000000002a775aec59dc6a9e4bb1c025cf1b8c2195dd9dc3998c827c5",
Addresses: []string{"17XhEvq9Nahdj7Xe1nv6oRe1tEmaHUuynH"},
OutPoints: []OutPoint{
{
Hash: "000102030405060708091011121314" +
"1516171819202122232425262728" +
"293031",
Index: 1,
},
},
EndBlock: "0000000000000001c091ada69f444dc0282ecaabe4808ddbb2532e5555db0c03",
},
},
{
name: "walletislocked no optargs",
f: func() (btcjson.Cmd, error) {
return NewWalletIsLockedCmd(float64(1))
},
result: &WalletIsLockedCmd{
id: float64(1),
Account: "",
},
},
{
name: "walletislocked one optarg",
f: func() (btcjson.Cmd, error) {
return NewWalletIsLockedCmd(
float64(1),
testAccount)
},
result: &WalletIsLockedCmd{
id: float64(1),
Account: testAccount,
},
},
}
func TestCmds(t *testing.T) {
for _, test := range cmdtests {
c, err := test.f()
if err != nil {
t.Errorf("%s: failed to run func: %v",
test.name, err)
continue
}
mc, err := c.MarshalJSON()
if err != nil {
t.Errorf("%s: failed to marshal cmd: %v",
test.name, err)
continue
}
c2, err := btcjson.ParseMarshaledCmd(mc)
if err != nil {
t.Errorf("%s: failed to ummarshal cmd: %v",
test.name, err)
continue
}
if !reflect.DeepEqual(test.result, c2) {
t.Errorf("%s: unmarshal not as expected. "+
"got %v wanted %v", test.name, spew.Sdump(c2),
spew.Sdump(test.result))
}
if !reflect.DeepEqual(c, c2) {
t.Errorf("%s: unmarshal not as we started with. "+
"got %v wanted %v", test.name, spew.Sdump(c2),
spew.Sdump(c))
}
// id from Id func must match result.
if c.Id() != test.result.Id() {
t.Errorf("%s: Id returned incorrect id. "+
"got %v wanted %v", test.name, c.Id(),
test.result.Id())
}
// method from Method func must match result.
if c.Method() != test.result.Method() {
t.Errorf("%s: Method returned incorrect method. "+
"got %v wanted %v", test.name, c.Method(),
test.result.Method())
}
// Read marshaled command back into c. Should still
// match result.
if err := c.UnmarshalJSON(mc); err != nil {
t.Errorf("%s: error while unmarshalling: %v", test.name,
err)
}
if !reflect.DeepEqual(test.result, c) {
t.Errorf("%s: unmarshal not as expected. "+
"got %v wanted %v", test.name, spew.Sdump(c),
spew.Sdump(test.result))
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,290 +0,0 @@
// Copyright (c) 2013 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcws_test
import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/btcjson/btcws"
"github.com/davecgh/go-spew/spew"
)
var ntfntests = []struct {
name string
f func() btcjson.Cmd
result btcjson.Cmd // after marshal and unmarshal
}{
{
name: "accountbalance",
f: func() btcjson.Cmd {
return btcws.NewAccountBalanceNtfn("abcde", 1.2345, true)
},
result: &btcws.AccountBalanceNtfn{
Account: "abcde",
Balance: 1.2345,
Confirmed: true,
},
},
{
name: "blockconnected",
f: func() btcjson.Cmd {
return btcws.NewBlockConnectedNtfn(
"000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70",
153469)
},
result: &btcws.BlockConnectedNtfn{
Hash: "000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70",
Height: 153469,
},
},
{
name: "blockdisconnected",
f: func() btcjson.Cmd {
return btcws.NewBlockDisconnectedNtfn(
"000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70",
153469)
},
result: &btcws.BlockDisconnectedNtfn{
Hash: "000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70",
Height: 153469,
},
},
{
name: "btcdconnected",
f: func() btcjson.Cmd {
return btcws.NewBtcdConnectedNtfn(true)
},
result: &btcws.BtcdConnectedNtfn{
Connected: true,
},
},
{
name: "recvtx no block",
f: func() btcjson.Cmd {
return btcws.NewRecvTxNtfn("lalala the hex tx", nil)
},
result: &btcws.RecvTxNtfn{
HexTx: "lalala the hex tx",
Block: nil,
},
},
{
name: "recvtx with block",
f: func() btcjson.Cmd {
block := &btcws.BlockDetails{
Height: 153469,
Hash: "000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70",
Index: 1,
Time: 1386944019,
}
return btcws.NewRecvTxNtfn("lalala the hex tx", block)
},
result: &btcws.RecvTxNtfn{
HexTx: "lalala the hex tx",
Block: &btcws.BlockDetails{
Height: 153469,
Hash: "000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70",
Index: 1,
Time: 1386944019,
},
},
},
{
name: "redeemingtx",
f: func() btcjson.Cmd {
return btcws.NewRedeemingTxNtfn("lalala the hex tx", nil)
},
result: &btcws.RedeemingTxNtfn{
HexTx: "lalala the hex tx",
Block: nil,
},
},
{
name: "redeemingtx with block",
f: func() btcjson.Cmd {
block := &btcws.BlockDetails{
Height: 153469,
Hash: "000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70",
Index: 1,
Time: 1386944019,
}
return btcws.NewRedeemingTxNtfn("lalala the hex tx", block)
},
result: &btcws.RedeemingTxNtfn{
HexTx: "lalala the hex tx",
Block: &btcws.BlockDetails{
Height: 153469,
Hash: "000000004811dda1c320ad5d0ea184a20a53acd92292c5f1cb926c3ee82abf70",
Index: 1,
Time: 1386944019,
},
},
},
{
name: "rescanfinished",
f: func() btcjson.Cmd {
return btcws.NewRescanFinishedNtfn(
"00000000b8980ec1fe96bc1b4425788ddc88dd36699521a448ebca2020b38699",
12345, 1240784732)
},
result: &btcws.RescanFinishedNtfn{
Hash: "00000000b8980ec1fe96bc1b4425788ddc88dd36699521a448ebca2020b38699",
Height: 12345,
Time: 1240784732,
},
},
{
name: "rescanprogress",
f: func() btcjson.Cmd {
return btcws.NewRescanProgressNtfn(
"00000000b8980ec1fe96bc1b4425788ddc88dd36699521a448ebca2020b38699",
12345, 1240784732)
},
result: &btcws.RescanProgressNtfn{
Hash: "00000000b8980ec1fe96bc1b4425788ddc88dd36699521a448ebca2020b38699",
Height: 12345,
Time: 1240784732,
},
},
{
name: "newtx",
f: func() btcjson.Cmd {
details := &btcjson.ListTransactionsResult{
Account: "original",
Address: "mnSsMBY8j4AhQzbR6XqawND7NPTECVdtLd",
Category: "receive",
Amount: 100,
Fee: 0.0,
Confirmations: 6707,
Generated: false,
BlockHash: "000000000b20bf5fe8e25b19f9ec340744cda321a17ade12af9838530a75098b",
BlockIndex: 2,
BlockTime: 1397079345,
TxID: "cb082a63b29f446551829d03fa8bac02d3825a18994d5feec564f14101fc5fad",
WalletConflicts: []string{},
Time: 123123123,
TimeReceived: 1397079169,
Comment: "comment",
OtherAccount: "",
}
return btcws.NewTxNtfn("abcde", details)
},
result: &btcws.TxNtfn{
Account: "abcde",
Details: &btcjson.ListTransactionsResult{
Account: "original",
Address: "mnSsMBY8j4AhQzbR6XqawND7NPTECVdtLd",
Category: "receive",
Amount: 100,
Fee: 0.0,
Confirmations: 6707,
Generated: false,
BlockHash: "000000000b20bf5fe8e25b19f9ec340744cda321a17ade12af9838530a75098b",
BlockIndex: 2,
BlockTime: 1397079345,
TxID: "cb082a63b29f446551829d03fa8bac02d3825a18994d5feec564f14101fc5fad",
WalletConflicts: []string{},
Time: 123123123,
TimeReceived: 1397079169,
Comment: "comment",
OtherAccount: "",
},
},
},
{
name: "walletlockstate",
f: func() btcjson.Cmd {
return btcws.NewWalletLockStateNtfn("abcde", true)
},
result: &btcws.WalletLockStateNtfn{
Account: "abcde",
Locked: true,
},
},
{
name: "txaccepted",
f: func() btcjson.Cmd {
return btcws.NewTxAcceptedNtfn(
"062f2b5f7d28c787e0f3aee382132241cd590efb7b83bd2c7f506de5aa4ef275",
34567765)
},
result: &btcws.TxAcceptedNtfn{
TxID: "062f2b5f7d28c787e0f3aee382132241cd590efb7b83bd2c7f506de5aa4ef275",
Amount: 34567765,
},
},
{
name: "txacceptedverbose",
f: func() btcjson.Cmd {
return btcws.NewTxAcceptedVerboseNtfn(&btcjson.TxRawResult{
Hex: "01000000010cdf900074a3622499a2f28f44a94476f27a8900a2bdd60e042754b6cab09741000000008a473044022012e11012fad1eb21ba1c82deb8da98778b08e714b72f281293064528343fae0502204294d7520f469f9673087a55395de0ce0e9074dce236db9fe7f30013b5fd00b90141047b6ff7832b4a763666e5481a0bd9eedb656d9f882d215c16fe9563d7b191cd67b2a41601a853a9f9d92773ae6f912ef451a089148e510623759cf55c408efdefffffffff02f4063f00000000001976a914b269e0ceec5d5b5e192cf580ae42341e0f79b0b588aca8c84b02000000001976a91439233c0d43a1411e547c60bad8985bae3530b6af88ac00000000",
Txid: "0cfeb968fb5d0f6b9a2a1de37c0607a1964dd3e335f203377cec90e03b20869e",
Version: 0x1,
LockTime: 0x0,
})
},
result: &btcws.TxAcceptedVerboseNtfn{
RawTx: &btcjson.TxRawResult{
Hex: "01000000010cdf900074a3622499a2f28f44a94476f27a8900a2bdd60e042754b6cab09741000000008a473044022012e11012fad1eb21ba1c82deb8da98778b08e714b72f281293064528343fae0502204294d7520f469f9673087a55395de0ce0e9074dce236db9fe7f30013b5fd00b90141047b6ff7832b4a763666e5481a0bd9eedb656d9f882d215c16fe9563d7b191cd67b2a41601a853a9f9d92773ae6f912ef451a089148e510623759cf55c408efdefffffffff02f4063f00000000001976a914b269e0ceec5d5b5e192cf580ae42341e0f79b0b588aca8c84b02000000001976a91439233c0d43a1411e547c60bad8985bae3530b6af88ac00000000",
Txid: "0cfeb968fb5d0f6b9a2a1de37c0607a1964dd3e335f203377cec90e03b20869e",
Version: 0x1,
LockTime: 0x0,
},
},
},
}
func TestNtfns(t *testing.T) {
for _, test := range ntfntests {
// create notification.
n := test.f()
// verify that id is nil.
if n.Id() != nil {
t.Errorf("%s: notification should not have non-nil id %v",
test.name, n.Id())
continue
}
mn, err := n.MarshalJSON()
if err != nil {
t.Errorf("%s: failed to marshal notification: %v",
test.name, err)
continue
}
n2, err := btcjson.ParseMarshaledCmd(mn)
if err != nil {
t.Errorf("%s: failed to ummarshal cmd: %v",
test.name, err)
continue
}
if !reflect.DeepEqual(test.result, n2) {
t.Errorf("%s: unmarshal not as expected. "+
"got %v wanted %v", test.name, spew.Sdump(n2),
spew.Sdump(test.result))
}
if !reflect.DeepEqual(n, n2) {
t.Errorf("%s: unmarshal not as we started with. "+
"got %v wanted %v", test.name, spew.Sdump(n2),
spew.Sdump(n))
}
// Read marshaled notification back into n. Should still
// match result.
if err := n.UnmarshalJSON(mn); err != nil {
t.Errorf("%s: unmarshal failed: %v", test.name, err)
continue
}
if !reflect.DeepEqual(test.result, n) {
t.Errorf("%s: unmarshal not as expected. "+
"got %v wanted %v", test.name, spew.Sdump(n),
spew.Sdump(test.result))
}
}
}

View file

@ -11,7 +11,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestChainSvrCmds tests all of the chain server commands marshal and unmarshal

View file

@ -8,7 +8,7 @@ import (
"encoding/json"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestChainSvrCustomResults ensures any results that have custom marshalling

View file

@ -11,7 +11,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestChainSvrWsCmds tests all of the chain server websocket-specific commands

View file

@ -11,7 +11,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestChainSvrWsNtfns tests all of the chain server websocket-specific

View file

@ -1,828 +0,0 @@
// Copyright (c) 2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcjson
import (
"errors"
)
// defaultHelpStrings contains the help text for all commands that are supported
// by btcjson by default.
var defaultHelpStrings = map[string]string{
"addmultisigaddress": `addmultisigaddress nrequired ["key",...] ("account" )
Add a multisignature address to the wallet where 'nrequired' signatures are
required for spending. Each key is an address of publickey. Optionally, account
may be provided to assign the address to that account.`,
"addnode": `addnode "node" "{add|remove|onetry}"
Add or remove a node from the list of hosts we connect to. If mode is "onetry"
then the host will be connected to once only, otherwise the node will be retried
upon disconnection.`,
"backupwallet": `backupwallet "destination"
Safely copies the wallet file to the destination provided, either a directory or
a filename.`,
"createmultisig": `createmultisig nrequired ["key", ...]
Creates a multi-signature address with m keys where "nrequired" signatures are
required from those m. A JSON object is returned containing the address and
redemption script:
{
"address":"address", # the value of the new address.
redeemScript":"script" # The stringified hex-encoded redemption script.
}`,
"createrawtransaction": `createrawtransaction [{"txid":"id", "vout":n},...] {"address":amount,...}
Creates a transaction spending the given inputs and outputting to the
given addresses. The return is a hex encoded string of the raw
transaction with *unsigned* inputs. The transaction is not stored in any
wallet.`,
// TODO(oga) this should be external since it is nonstandard.
"debuglevel": `debuglevel "levelspec"
Dynamically changes the debug logging level. Levelspec must be either a debug
level, one of the following:
trace,
debug,
info,
warn,
error,
critical.
Alternatively levelspec may be a specification of the form:
<subsystem>=<level>,<subsystem2>=<level2>
Where the valid subsystem names are:
AMGR,
ADXR,
BCDB,
BMGR,
BTCD,
CHAN,
DISC,
PEER,
RPCS,
SCRP,
SRVR,
TXMP.
Finally the keyword "show" will return a list of the available subsystems.
The command returns a string which will be "Done." if the command was sucessful,
or the list of subsystems if "show" was specified.`,
"decoderawtransaction": `decoderawtransaction "hexstring"
Decodes the seralized, hex-encoded transaction in hexstring and returns a JSON
object representing it:
{
"hex":"hex", # String of the hex encoded transaction provided.
"txid":"id", # The sha id of the transaction as a hex string
"version":n, # The version of the transaction as a number.
"locktime":t, # Locktime of the tx (number).
"vin": [ # Array of objects for inputs.
{
"txid":"id", # Txid that is spent.
"vout":n, # Output number.
"scriptSig": {
"asm":"asm", # Disasembled script as a string.
"hex":"hex", # Hex string of script.
},
"sequence":n, # Sequence number of script.
}
],
"vout": [ # Array of objects for outputs.
{
"value":x.xxx, # Value in BTC.
"n":n, # Numeric index.
"scriptPubkey": { # Object representing script.
"asm":"asm", # Disassembled script as string.
"hex":"hex", # Hex string of script.
"reqSigs":n, # Number of required signatures.
"type":"type" # Type as string, e.g. pubkeyhash.
},
}
]
"blockhash":"hash", # The block hash. as a string.
"confirmations":n # Number of confirmations in blockchain.
"time":t, # Transaction time in seconds since the epoch.
"blocktime":t, # Block time in seconds since the epoch.
}`,
"decodescript": `decodescript "hex"
Decodes the hex encoded script passed as a string and returns a JSON object:
{
"asm":"asm", # disassembled string of the script.
"hex":"hex", # hex string of the script.
"type":"type", # type of script as a string.
"reqSigs":n, # number of required signatures.
"addresses": [ # JSON array of address strings.
"address", # bitcoin address as a string.
],
"p2sh","address" # script address as a string.
}`,
"dumpprivkey": `dumpprivkey "bitcoinaddress"
Returns the private key corresponding to the provided address in a format
compatible with importprivkey.`,
"dumpwallet": `dumpwallet "filename"
Dumps all wallet keys into "filename" in a human readable format.`,
"encryptwallet": `encryptwallet "passphrase"
Encrypts the wallet with "passphrase". This command is for the initial
encryption of an otherwise unencrypted wallet, changing a passphrase
should use walletpassphrasechange.`,
"estimatefee": `estimatefee "numblocks"
Estimates the approximate fee per kilobyte needed for a transaction to
get confirmed within 'numblocks' blocks.`,
"estimatepriority": `estimatepriority "numblocks"
Estimates the approximate priority a zero-fee transaction needs to get
confirmed within 'numblocks' blocks.`,
"getaccount": `getaccount "address"
Returns the account associated with the given "address" as a string.`,
"getaccountaddress": `getaccountaddress "account"
Returns the current address used for receiving payments in this given account.`,
"getaddednodeinfo": `getaddednodeinfo dns ( "node" )
Returns a list of JSON objects with information about the local list of
permanent nodes. If dns is false, only a list of permanent nodes will
be provided, otherwise connected information will also be provided. If
node is not provided then all nodes will be detailed. The JSON return
format is as follows:
[
{
"addednode":"1.2.3.4", # node ip address as a string
"connected":true|false # boolean connectionstate.
"addresses": [
"address":"1.2.3.4:5678" # Bitcoin server host and port as a string.
"connected":"inbound", # The string "inbound" or "outbound".
],
},
...
]`,
"getaddressesbyaccount": `getaddressesbyaccount "account"
Returns the list of addresses for the given account:
[
"address", # Bitcoin address associated with the given account.
...
]`,
"getbalance": `getbalance ("account" "minconf")
Returns the balance for an account. If "account" is not specified this is the
total balance for the server. if "minconf" is provided then only transactions
with at least "minconf" confirmations will be counted for the balance. The
result is a JSON numeric type.`,
"getbestblockhash": `getbestblockhash
Returns the hash of the last block in the longest blockchain known to
the server as a hex encoded string.`,
"getblock": `getblock "hash" ( verbose verbosetx=false)
Returns data about the block with hash "hash". If verbose is false a
string of hex-encoded data for the block is returned. If verbose is true
a JSON object is provided with the following:
{
"hash":"hash", # The block hash (same as argument).
"confirmations":n, # Number of confirmations as numeric.
"size":n, # Block size as numeric.
"height":n, # Block height as numeric.
"version":n, # Block version as numeric.
"merkelroot":"...", # The merkle root of the block.
"tx" : [ # the transactions in the block as an array of strings.
"transactionid",
...
],
"time":t, # The block time in seconds since the epoch.
"nonce":n, # The nonce of the block as a number.
"bits":"1d00ffff", # the compact representation of the difficulty as bits.
"difficulty":n, # the difficulty of the block as a number.
"previousblockhash":"hash", # hash of the previous block as a string.
"nextblockhash":""hash", # hash of the next block as a string.
}
If "verbosetx" is true, the returned object will contain a "rawtx" member
instead of the "tx" member; It will contain objects representing the
transactions in the block in format used by getrawtransaction.
Please note that verbosetx is a btcd/btcjson extension.`,
"getblockcount": `getblockcount
Returns a numeric for the number of blocks in the longest block chain.`,
"getblockhash": `getblockhash index
Returns the hash of the block (as a string) at the given index in the
current blockchain.`,
"getblocktemplate": `getblocktemplate ( jsonrequestobject )
Returns a block template for external mining purposes. The optional
request object follows the following format:
{
"mode":"template" # Optional, "template" or omitted.
"capabilities": [ # List of strings, optional.
"support", # Client side features supported. one of:
... # "longpoll", "coinbasetxn", "coinbasevalue",
... # "proposal", "serverlist", "workid".
]
}
The result object is of the following format:
{
"version":n, # Numeric block version.
"previousblockhash":"string" # Hash of the current tip of the blocktrain.
"transactions":[
"data" # String of hex-encoded serialized transaction.
"hash" # Hex encoded hash of the tx.
"depends": [ # Array of numbers representing the transactions
n, # that must be included in the final block if
..., # this one is. A 1 based index into the
..., # transactions list.
]
"fee":n # Numeric transaction fee in satoshi. This is calculated by the diffrence between the sum of inputs and outputs. For coinbase transaction this is a negative number of total collected block fees. If not present fee is unknown; clients must not assume that there is no fee in this case.
"sigops":n # Number of total signature operations calculated for purposes of block limits. If not present the count is unknown but clients must not assume it is zero.
"required":true|false # If provided and true this transaction *must* be in the final block.
],
"coinbaseaux": { # Object of data to be included in coinbase's scriptSig.
"flags":"flags" # String of flags.
}
"coinbasevalue" # Numeric value in satoshi for maximum allowable input to coinbase transaction, including transaction fees and mining aware.
"coinbasetxn":{} # Object contining information for coinbase transaction.
"target":"target", # The hash target as a string.
"mintime":t, # Minimum timestamp appropriate for next block in seconds since the epoch.
"mutable":[ # Array of ways the template may be modified.
"value" # e.g. "time", "transactions", "prevblock", etc
]
"noncerange":"00000000ffffffff" # Wtring representing the range of valid nonces.
"sigopliit" # Numeric limit for max sigops in block.
"sizelimit" # Numeric limit of block size.
"curtime":t # Current timestamp in seconds since the epoch.
"bits":"xxx", # Compressed target for next block as string.
"height":n, # Numeric height of the next block.
}`,
"getconnectioncount": `getconnectioncount
Returns the number of connections to other nodes currently active as a JSON
number.`,
"getdifficulty": `getdifficulty
Returns the proof-of-work difficulty as a JSON number. The result is a
multiple of the minimum difficulty.`,
"getgenerate": `getgenerate
Returns JSON boolean whether or not the server is set to "mine" coins or not.`,
"gethashespersec": `gethashespersec
Returns a JSON number representing a recent measurement of hashes-per-second
during mining.`,
"getinfo": `getinfo
Returns a JSON object containing state information about the service:
{
"version":n, # Numeric server version.
"protocolversion":n, # Numeric protocol version.
"walletversion":n, # Numeric wallet version.
"balance":n, # Total balance in the wallet as a number.
"blocks":n, # Numeric detailing current number of blocks.
"timeoffset":n, # Numeric server time offset.
"proxy":"host:port" # Optional string detailing the proxy in use.
"difficulty":n, # Current blockchain difficulty as a number.
"testnet":true|false # Boolean if the server is testnet.
"keypoololdest":t, # Oldest timstamp for pre generated keys. (in seconds since the epoch).
"keypoolsize":n, # Numeric size of the wallet keypool.
"paytxfee":n, # Numeric transaction fee that has been set.
"unlocked_until":t, # Numeric time the wallet is unlocked for in seconds since epoch.
"errors":"..." # Any error messages as a string.
}`,
"getmininginfo": `getmininginfo
Returns a JSON object containing information related to mining:
{
"blocks":n, # Numeric current block
"currentblocksize":n, # Numeric last block size.
currentblocktx":n, # Numeric last block transaction
"difficulty":n, # Numeric current difficulty.
"errors":"...", # Current error string.
}`,
"getnettotals": `getnettotals
Returns JSON object containing network traffic statistics:
{
"totalbytesrecv":n, # Numeric total bytes received.
"totalbytessent":n, # Numeric total bytes sent.
"timemilis",t, # Total numeric of milliseconds since epoch.
}`,
"getnetworkhashps": `getnetworkhashps ( blocks=120 height=-1 )
Returns the estimated network hash rate per second based on the last
"blocks" blocks. If "blocks" is -1 then the number of blocks since the
last difficulty change will be used. If "height" is set then the
calculation will be carried out for the given block height instead of
the block tip. A JSON number is returned with the hashes per second
estimate.`,
"getnewaddress": `getnewaddress ( "account" )
Returns a string for a new Bitcoin address for receiving payments. In the case
that "account" is specified then the address will be for "account", else the
default account will be used.`,
"getpeerinfo": `getpeerinfo
Returns a list of JSON objects containing information about each connected
network node. The objects have the following format:
[
{
"id":n, # Unique node ID.
"addr":"host:port" # IP and port of the peer as a string.
"addrlocal":"ip:port" # Local address as a string.
"services":"00000001", # Services bitmask as a string.
"lastsend":t, # Time in seconds since epoch since last send.
"lastrecv":t, # Time in seconds since epoch since last received message.
"bytessent":n, # Total number of bytes sent.
"bytesrecv":n, # Total number of bytes received.
"conntime":t, # Connection time in seconds since epoch.
"timeoffset":n, # Peer time offset.
"pingtime":n, # Ping time
"pingwait":n, # Ping wait.
"version":n, # The numeric peer version.
"subver":"/btcd:0.1/" # The peer useragent string.
"inbound":true|false, # True or false whether peer is inbound.
"startingheight":n, # Numeric block heght of peer at connect time.
"banscore":n, # The numeric ban score.
"syncnode":true|false, # Boolean if the peer is the current sync node.
}
]`,
"getrawchangeaddress": `getrawchangeaddress
Returns a string containing a new Bitcoin addres for receiving change.
This rpc call is for use with raw transactions only.`,
"getrawmempool": `getrawmempool ( verbose )
Returns the contents of the transaction memory pool as a JSON array. If
verbose is false just the transaction ids will be returned as strings.
If it is true then objects in the following format will be returned:
[
"transactionid": {
"size":n, # Numeric transaction size in bytes.
"fee":n, # Numeric transqaction fee in btc.
"time":t, # Time transaction entered pool in seconds since the epoch.
"height":n, # Numeric block height when the transaction entered pool.
"startingpriority:n, # Numeric transaction priority when it entered the pool.
"currentpriority":n, # Numeric transaction priority.
"depends":[ # Unconfirmed transactions used as inputs for this one. As an array of strings.
"transactionid", # Parent transaction id.
]
},
...
]`,
"getrawtransaction": `getrawtransaction "txid" ( verbose )
Returns raw data related to "txid". If verbose is false, a string containing
hex-encoded serialized data for txid. If verbose is true a JSON object with the
following information about txid is returned:
{
"hex":"data", # String of serialized, hex encoded data for txid.
"txid":"id", # String containing the transaction id (same as "txid" parameter)
"version":n # Numeric tx version number.
"locktime":t, # Transaction locktime.
"vin":[ # Array of objects representing transaction inputs.
{
"txid":"id", # Spent transaction id as a string.
"vout""n, # Spent transaction output no.
"scriptSig":{ # Signature script as an object.
"asm":"asm", # Disassembled script string.
"hex":"hex", # Hex serialized string.
},
"sequence":n, # Script sequence number.
},
...
],
vout:[ # Array of objects representing transaction outputs.
{
"value":n, # Numeric value of output in btc.
"n", n, # Numeric output index.
"scriptPubKey":{ # Object representing pubkey script.
"asm":"asm" # Disassembled string of script.
"hex":"hex" # Hex serialized string.
"reqSigs":n, # Number of required signatures.
"type":"pubkey", # Type of scirpt. e.g. pubkeyhash" or "pubkey".
"addresses":[ # Array of address strings.
"address", # Bitcoin address.
...
],
}
}
],
"blockhash":"hash" # Hash of the block the transaction is part of.
"confirmations":n, # Number of numeric confirmations of block.
"time":t, # Transaction time in seconds since the epoch.
"blocktime":t, # Block time in seconds since the epoch.
}`,
"getreceivedbyaccount": `getreceivedbyaccount "account" ( minconf=1 )
Returns the total amount of BTC received by addresses related to "account".
Only transactions with at least "minconf" confirmations are considered.`,
"getreceivedbyaddress": `getreceivedbyaddress "address" ( minconf=1 )
Returns the total amount of BTC received by the given address. Only transactions
with "minconf" confirmations will be used for the total.`,
"gettransaction": `gettransaction "txid"
Returns a JSON object containing detailed information about the in-wallet
transaction "txid". The object follows the following format:
{
"amount":n, # Transaction amount in BTC.
"confirmations":n, # Number of confirmations for transaction.
"blockhash":"hash", # Hash of block transaction is part of.
"blockindex":n, # Index of block transaction is part of.
"blocktime":t, # Time of block transaction is part of.
"txid":"id", # Transaction id.
"time":t, # Transaction time in seconds since epoch.
"timereceived":t, # Time transaction was received in seconds since epoch.
"details":[
{
"account":"name", # The acount name involvedi n the transaction. "" means the default.
"address":"address", # The address involved in the transaction as a string.
"category":"send|receive", # Category - either send or receive.
"amount":n, # numeric amount in BTC.
}
...
]
}`,
"gettxout": `gettxout "txid" n ( includemempool )
Returns an object containing detauls about an unspent transaction output
the "n"th output of "txid":
{
"bestblock":"hash", # Block has containing transaction.
"confirmations":n, # Number of confirmations for block.
"value":n # Transaction value in BTC.
scriptPubkey" {
"asm":"asm", # Disassembled string of script.
"hex":"hex", # String script serialized and hex encoded.
"reqSigs" # Numeric required signatures.
"type":"pubkeyhash" # Type of transaction. e.g. pubkeyhas
"addresses":[ # Array of strings containing addresses.
"address",
...
]
},
}`,
"gettxoutsetinfo": `gettxoutsetinfo
Returns an object containing startstics about the unspent transaction
output set:
{
"height":n, # Numeric current block height.
"bestblock":"hex" # Hex string of best block hash.
"transactions":n, # Numeric count of transactions.
"txouts":n # Numeric count of transaction outputs.
"bytes_serialized":n # Numeric serialized size.
"hash_serialized" # String of serialized hash.
"total_amount":n, # Numeric total amount in BTC.
}`,
"getwork": `getwork ( "data" )
If "data" is present it is a hex encoded block datastruture that has been byte
reversed, if this is the case then the server will try to solve the
block and return true upon success, false upon failure. If data is not
specified the following object is returned to describe the work to be
solved:
{
"midstate":"xxx", # String of precomputed hash state for the first half of the data (deprecated).
"data":"...", # Block data as string.
"hash1":"..." # Hash buffer for second hash as string. (deprecated).
"target":"...", # Byte reversed hash target.
}`,
"help": `help ( "command" )
With no arguemnts, lists the supported commands. If "command" is
specified then help text for that command is returned as a string.`,
"importprivkey": `importprivkey "privkey" ( "label" rescan=true )
Adds a private key (in the same format as dumpprivkey) to the wallet. If label
is provided this is used as a label for the key. If rescan is true then the
blockchain will be scanned for transaction.`,
"importwallet": `importwallet "filename"
Imports keys from the wallet dump file in "filename".`,
"invalidateblock": `invalidateblock "hash"
Mark block specified by "hash" as invalid.`,
"keypoolrefill": `keypoolrefill ( newsize=100 )
Refills the wallet pregenerated key pool to a size of "newsize"`,
"listaccounts": `listaccounts ( minconf=1)
Returns a JSON object mapping account names to account balances:
{
"accountname": n # Account name to numeric balance in BTC.
...
}`,
"listaddressgroupings": `listaddressgroupings
Returns a JSON array of array of addreses which have had their relation to each
other made public by common use as inputs or in the change address. The data
takes the following format:
[
[
[
"address", # Bitcoin address.
amount, # Amount in BTC.
"account" # Optional account name string.
],
...
],
...
]`,
"listlockunspent": `listlockunspent
Returns a JSON array of objects detailing transaction outputs that are
temporarily unspendable due to being processed by the lockunspent call.
[
{
"txid":"txid" # Id of locked transaction as a string.
"vout":n, # Numeric index of locked output.
},
...
]`,
"listreceivedbyaccount": `listreceivedbyaccount ( minconf=1 includeempty=false )
Returns a JSON array containing objects for each account detailing their
balances. Only transaction with at least "minconf" confirmations will be
included. If "includeempty" is true then accounts who have received no payments
will also be included, else they will be elided. The format is as follows:
[
{
"account":"name", # Name of the receiving account.
"amount":n, # Total received amount in BTC.
"confirmations":n, # Total confirmations for most recent transaction.
}
]`,
"listreceivedbyaddress": `listreceivedbyaddress ( minconf=1 includeempty=false )
Returns a JSON array containing objects for each address detailing their
balances. Only transaction with at least "minconf" confirmations will be
included. If "includeempty" is true then adresses who have received no payments
will also be included, else they will be elided. The format is as follows:
[
{
"account":"name", # Name of the receiving account.
"amount":n, # Total received amount in BTC.
"confirmations":n, # Total confirmations for most recent transaction.
}
]`,
"listsinceblock": `listsinceblock ("blockhash" minconf=1)
Gets all wallet transactions in block since "blockhash", if blockhash is
omitted then all transactions are provided. If present the only transactions
with "minconf" confirmations are listed.
{
"transactions":[
"account" # String of account related to the transaction.
"address" # String of related address of transaction.
"category":"send|receive" # String detailing whether transaction was a send or receive of funds.
"amount":n, # Numeric value of transaction. Negative if transaction category was "send"
"fee":n, # Numeric value of transaction fee in BTC.
"confirmations":n, # Number of transaction confirmations
"blockhash":"hash" # String of hash of block transaction is part of.
"blockindex":n, # Numeric index of block transaction is part of.
"blocktime":t, # Block time in seconds since the epoch.
"txid":"id", # Transaction id.
"time":t, # Transaction time in second since the epoch.
"timereceived":t, # Time transaction received in seconds since the epoch.
"comment":"...", # String of the comment associated with the transaction.
"to":"...", # String of "to" comment of the transaction.
]
"lastblock":"lastblockhash" # Hash of the last block as a string.
}`,
"listtransactions": `listtransactions ( "account" count=10 from=0 )
Returns up to "count" most recent wallet transactions for "account" (or all
accounts if none specified) skipping the first "from" transactions.
{
"transactions":[
"account" # String of account related to the transaction.
"address" # String of related address of transaction.
"category":"send|receive|move" # String detailing whether transaction was a send or receive of funds.Move is a local move between accounts and doesnt touch the blockchain.
"amount":n, # Numeric value of transaction. Negative if transaction category was "send"
"fee":n, # Numeric value of transaction fee in BTC.
"confirmations":n, # Number of transaction confirmations
"blockhash":"hash" # String of hash of block transaction is part of.
"blockindex":n, # Numeric index of block transaction is part of.
"blocktime":t, # Block time in seconds since the epoch.
"txid":"id", # Transaction id.
"time":t, # Transaction time in second since the epoch.
"timereceived":t, # Time transaction received in seconds since the epoch.
"comment":"...", # String of the comment associated with the transaction.
"to":"...", # String of "to" comment of the transaction.
]
"lastblock":"lastblockhash" # Hash of the last block as a string.
}`,
"listunspent": `listunspent (minconf=1 maxconf=9999999 ["address",...]
Returns a JSON array of objects representing unspent transaction outputs with
between minconf and maxconf confirmations (inclusive). If the array of addresses
is present then only txouts paid to the addresses listed will be considered. The
objects take the following format:
[
{
"txid":"id", # The transaction id as a string.
"vout":n, # The output number of the tx.
"address":"add", # String of the transaction address.
"account":"acc", # The associated account, "" for default.
"scriptPubkey":"key" # The pubkeyscript as a string.
"amount":n, # The value of the transaction in BTC.
"confirmations":n, # The numer of confirmations.
},
...
]`,
"lockunspent": `lockunspent unlock [{"txid":id", "vout":n},...]
Changes the llist of temporarily unspendable transaction outputs. The
transacion outputs in the list of objects provided will be marked as
locked (if unlock is false) or unlocked (unlock is true). A locked
transaction will not be chosen automatically to be spent when a
tranaction is created. Boolean is returned whether the command succeeded.`,
"move": `move "fromaccount" "toaccount" amount ( minconf=1 "comment" )
Moves a specifies amount from "fromaccount" to "toaccount". Only funds
with minconf confirmations are used. If comment is present this comment
will be stored in the wallet with the transaction. Boolean is returned
to denode success.`,
"ping": `ping
Queues a ping to be sent to each connected peer. Ping times are provided in
getpeerinfo.`,
"reconsiderblock": `reconsiderblock "hash"
Remove invalid mark from block specified by "hash" so it is considered again.`,
"searchrawtransactions": `searchrawtransactions "address" (verbose=1 skip=0 count=100)
Returns raw tx data related to credits or debits to "address". Skip indicates
the number of leading transactions to leave out of the final result. Count
represents the max number of transactions to return. If verbose is false, a
string containing hex-encoded serialized data for txid. If verbose is true a
JSON object with the following information about txid is returned:
{
"hex":"data", # String of serialized, hex encoded data for txid.
"txid":"id", # String containing the transaction id (same as "txid" parameter)
"version":n # Numeric tx version number.
"locktime":t, # Transaction locktime.
"vin":[ # Array of objects representing transaction inputs.
{
"txid":"id", # Spent transaction id as a string.
"vout""n, # Spent transaction output no.
"scriptSig":{ # Signature script as an object.
"asm":"asm", # Disassembled script string.
"hex":"hex", # Hex serialized string.
},
"sequence":n, # Script sequence number.
},
...
],
vout:[ # Array of objects representing transaction outputs.
{
"value":n, # Numeric value of output in btc.
"n", n, # Numeric output index.
"scriptPubKey":{ # Object representing pubkey script.
"asm":"asm" # Disassembled string of script.
"hex":"hex" # Hex serialized string.
"reqSigs":n, # Number of required signatures.
"type":"pubkey", # Type of scirpt. e.g. pubkeyhash" or "pubkey".
"addresses":[ # Array of address strings.
"address", # Bitcoin address.
...
],
}
}
],
"blockhash":"hash" # Hash of the block the transaction is part of.
"confirmations":n, # Number of numeric confirmations of block.
"time":t, # Transaction time in seconds since the epoch.
"blocktime":t, # Block time in seconds since the epoch.
}`,
"sendfrom": `sendfrom "fromaccount" "tobitcoinaddress" amount ( minconf=1 "comment" "comment-to" )
Sends "amount" (rounded to the nearest 0.00000001) to
"tobitcoindaddress" from "fromaccount". Only funds with at least
"minconf" confirmations will be considered. If "comment" is present this
will be store the purpose of the transaction. If "comment-to" is present
this comment will be recorded locally to store the recipient of the
transaction.`,
"sendmany": `sendmany "fromaccount" {"address":amount,...} ( minconf=1 "comment" )
Sends funds to multiple recipients. Funds from "fromaccount" are send to the
address/amount pairs specified. Only funds with minconf confirmations are
considered. "comment" if present is recorded locally as the purpose of the
transaction.`,
"sendrawtransaction": `sendrawtransaction "hexstring" (allowhighfees=false)
Submits the hex-encoded transaction in "hexstring" to the bitcoin network via
the local node. If allowhighfees is true then high fees will be allowed.`,
"sendtoaddress": `sendtoaddress "bitcoindaddress" amount ( "comment" "comment-to")
Send "amount" BTC (rounded to the nearest 0.00000001) to "bitcoinaddress". If
comment is set, it will be stored locally to describe the purpose of the
transaction. If comment-to" is set it will be stored locally to describe the
recipient of the transaction. A string containing the transaction id is
returned if successful.`,
"setaccount": `setaccount "address" "account"
Sets the account associated with "address" to "account".`,
"setgenerate": `setgenerate generate ( genproclimit )
Sets the current mining state to "generate". Up to "genproclimit" processors
will be used, if genproclimit is -1 then it is unlimited.`,
"settxfee": `settxfee amount
Sets the current transaction fee.`,
"signmessage": `signmessage "address" "message"
Returns a signature for "message" with the private key of "address"`,
"signrawtransaction": `signrawtransaction "hexstring" ( prevtxs ["privatekey",...] sighashtype="ALL" )
Signs the inputs for the serialized and hex encoded transaction in
"hexstring". "prevtxs" optionally is an is an array of objects
representing the previous tx outputs that are spent here but may not yet
be in the blockchain, it takes the following format:
[
{
"txid":id:, # String of transaction id.
"vout":n, # Output number.
"scriptPubKey":"hex", # Hex encoded string of pubkey script from transaction.
"redemScript":"hex" # Hex encoded redemption script.
},
...
]
If the third argument is provided these base58-encoded private keys in
the list will be the only keys used to sign the transaction. sighashtype
optionally denoes the signature hash type to be used. is must be one of the
following:
"ALL",
"NONE",
"SINGLE",
"ALL|ANYONECANPAY",
"NONE|ANYONECANPAY",
"SINGLE|ANYONECANPAY".
The return value takes the following format:
{
"hex":"value" # Hex string of raw transactino with signatures applied.
"complete":n, # If the transaction has a complete set of signatures. 0 if false.
}`,
"stop": `stop
Stop the server.`,
"submitblock": `submitblock "data" ( optionalparameterobject )
Will attempt to submit the block serialized in "data" to the bitcoin network.
optionalparametersobject takes the following format:
{
"workid":"id" # If getblocktemplate provided a workid it must be included with submissions.
}`,
"validateaddress": `validateaddress "address
Returns a JSON objects containing information about the provided "address".
Format:
{
"isvalid":true|false, # If the address is valid. If false this is the only property returned.
"address:"address", # The address that was validated.
"ismine":true|false, # If the address belongs to the server.
"isscript:true|false, # If the address is a script address.
"pubkey":"pk", # The hex value of the public key associated with the address.
"iscompressed":true|false, # If the address is compresssed.
"account":"account", # The related account. "" is the default.
}`,
"verifychain": `verifychain ( checklevel=3 numblocks=288 )
Verifies the stored blockchain database. "checklevel" denotes how thorough the
check is and "numblocks" how many blocks from the tip will be verified.`,
"verifymessage": `verifymessage "bitcoinaddress" "signature" "message"
Verifies the signature "signature" for "message" from the key related
to "bitcoinaddress". The return is true or false if the signature
verified correctly.`,
"walletlock": `walletlock
Removes any encryption key for the wallet from memory, unlocking the wallet.
In order to use wallet functionality again the wallet must be unlocked via
walletpassphrase.`,
"walletpassphrase": `walletpassphrase "passphrase" timeout
Decrypts the wallet for "timeout" seconds with "passphrase". This is required
before using wallet functionality.`,
"walletpassphrasechange": `walletpassphrasechange "oldpassphrase" "newpassphrase"
Changes the wallet passphrase from "oldpassphrase" to "newpassphrase".`,
}
// GetHelpString returns a string containing help text for "cmdName". If
// cmdName is unknown to btcjson - either via the default command list or those
// registered using RegisterCustomCmd - an error will be returned.
func GetHelpString(cmdName string) (string, error) {
helpstr := ""
if help, ok := defaultHelpStrings[cmdName]; ok {
return help, nil
}
if c, ok := customCmds[cmdName]; ok {
return c.helpString, nil
}
return helpstr, errors.New("invalid command specified")
}

View file

@ -8,7 +8,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestCmdMethod tests the CmdMethod function to ensure it retuns the expected

View file

@ -10,7 +10,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestAssignField tests the assignField function handles supported combinations

View file

@ -1,116 +1,146 @@
// Copyright (c) 2013-2014 Conformal Systems LLC.
// Copyright (c) 2015 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
/*
Package btcjson implements the bitcoin JSON-RPC API.
Package btcjson provides primitives for working with the bitcoin JSON-RPC API.
A complete description of the JSON-RPC protocol as used by bitcoin can
be found on the official wiki at
https://en.bitcoin.it/wiki/API_reference_%28JSON-RPC%29 with a list of
all the supported calls at
https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_Calls_list.
Overview
This package provides data structures and code for marshalling and
unmarshalling json for communicating with a running instance of btcd
or bitcoind/bitcoin-qt. It also provides code for sending those
messages. It does not provide any code for the client to actually deal
with the messages. Although it is meant primarily for btcd, it is
possible to use this code elsewhere for interacting with a bitcoin
client programatically.
When communicating via the JSON-RPC protocol, all of the commands need to be
marshalled to and from the the wire in the appropriate format. This package
provides data structures and primitives to ease this process.
Protocol
In addition, it also provides some additional features such as custom command
registration, command categorization, and reflection-based help generation.
All messages to bitcoin are of the form:
JSON-RPC Protocol Overview
{"jsonrpc":"1.0","id":"SOMEID","method":"SOMEMETHOD","params":SOMEPARAMS}
This information is not necessary in order to use this package, but it does
provide some intuition into what the marshalling and unmarshalling that is
discussed below is doing under the hood.
The params field can vary in what it contains depending on the
different method (or command) being sent.
As defined by the JSON-RPC spec, there are effectively two forms of messages on
the wire:
Replies will vary in form for the different commands. The basic form is:
- Request Objects
{"jsonrpc":"1.0","id":"SOMEID","method":"SOMEMETHOD","params":[SOMEPARAMS]}
NOTE: Notifications are the same format except the id field is null.
{"result":SOMETHING,"error":null,"id":"SOMEID"}
- Response Objects
{"result":SOMETHING,"error":null,"id":"SOMEID"}
{"result":null,"error":{"code":SOMEINT,"message":SOMESTRING},"id":"SOMEID"}
The result field can be as simple as an int, or a complex structure
containing many nested fields. For cases where we have already worked
out the possible types of reply, result is unmarshalled into a
structure that matches the command. For others, an interface is
returned. An interface is not as convenient as one needs to do a type
assertion first before using the value, but it means we can handle
arbitrary replies.
For requests, the params field can vary in what it contains depending on the
method (a.k.a. command) being sent. Each parameter can be as simple as an int
or a complex structure containing many nested fields. The id field is used to
identify a request and will be included in the associated response.
The error field is null when there is no error. When there is an
error it will return a numeric error code as well as a message
describing the error.
When working with asynchronous transports, such as websockets, spontaneous
notifications are also possible. As indicated, they are the same as a request
object, except they have the id field set to null. Therefore, servers will
ignore requests with the id field set to null, while clients can choose to
consume or ignore them.
id is simply the id of the requester.
Unfortunately, the original Bitcoin JSON-RPC API (and hence anything compatible
with it) doesn't always follow the spec and will sometimes return an error
string in the result field with a null error for certain commands. However,
for the most part, the error field will be set as described on failure.
RPC Server Authentication
Marshalling and Unmarshalling
All RPC calls must be authenticated with a username and password. The specifics
on how to configure the RPC server varies depending on the specific bitcoin
implementation. For bitcoind, this is accomplished by setting rpcuser and
rpcpassword in the file ~/.bitcoin/bitcoin.conf. For btcd, this is accomplished
by setting rpcuser and rpcpass in the file ~/.btcd/btcd.conf. The default local
address of bitcoind is 127.0.0.1:8332 and the default local address of btcd is
127.0.0.1:8334
Based upon the discussion above, it should be easy to see how the types of this
package map into the required parts of the protocol
Usage
- Request Objects (type Request)
- Commands (type <Foo>Cmd)
- Notifications (type <Foo>Ntfn)
- Response Objects (type Response)
- Result (type <Foo>Result)
The general pattern to use this package consists of generating a message (see
the full list on the official bitcoin wiki), sending the message, and handling
the result after asserting its type.
To simplify the marshalling of the requests and responses, the MarshalCmd and
MarshalResponse functions are provided. They return the raw bytes ready to be
sent across the wire.
For commands where the reply structure is known, such as getinfo, one can
directly access the fields in the Reply structure by type asserting the
reply to the appropriate concrete type.
Unmarshalling a received Request object is a two step process:
1) Unmarshal the raw bytes into a Request struct instance via json.Unmarshal
2) Use UnmarshalCmd on the Result field of the unmarshalled Request to create
a concrete command or notification instance with all struct fields set
accordingly
// Create a getinfo command.
id := 1
cmd, err := btcjson.NewGetInfoCmd(id)
if err != nil {
// Log and handle error.
}
This approach is used since it provides the caller with access to the additional
fields in the request that are not part of the command such as the ID.
// Send the message to server using the appropriate username and
// password.
reply, err := btcjson.RpcSend(user, password, server, cmd)
if err != nil {
fmt.Println(err)
// Log and handle error.
}
Unmarshalling a received Response object is also a two step process:
1) Unmarhsal the raw bytes into a Response struct instance via json.Unmarshal
2) Depending on the ID, unmarshal the Result field of the unmarshalled
Response to create a concrete type instance
// Ensure there is a result and type assert it to a btcjson.InfoResult.
if reply.Result != nil {
if info, ok := reply.Result.(*btcjson.InfoResult); ok {
fmt.Println("balance =", info.Balance)
}
}
As above, this approach is used since it provides the caller with access to the
fields in the response such as the ID and Error.
For other commands where this package does not yet provide a concrete
implementation for the reply, such as getrawmempool, the reply uses a generic
interface so one can access individual items as follows:
Command Creation
// Create a getrawmempool command.
id := 1
cmd, err := btcjson.NewGetRawMempoolCmd(id)
if err != nil {
// Log and handle error.
}
This package provides two approaches for creating a new command. This first,
and preferred, method is to use one of the New<Foo>Cmd functions. This allows
static compile-time checking to help ensure the parameters stay in sync with
the struct definitions.
// Send the message to server using the appropriate username and
// password.
reply, err := btcjson.RpcSend(user, password, server, cmd)
if err != nil {
// Log and handle error.
}
The second approach is the NewCmd function which takes a method (command) name
and variable arguments. The function includes full checking to ensure the
parameters are accurate according to provided method, however these checks are,
obviously, run-time which means any mistakes won't be found until the code is
actually executed. However, it is quite useful for user-supplied commands
that are intentionally dynamic.
// Ensure there is a result and type assert it to a string slice.
if reply.Result != nil {
if mempool, ok := reply.Result.([]string); ok {
fmt.Println("num mempool entries =", len(mempool))
}
}
Custom Command Registration
The command handling of this package is built around the concept of registered
commands. This is true for the wide variety of commands already provided by the
package, but it also means caller can easily provide custom commands with all
of the same functionality as the built-in commands. Use the RegisterCmd
function for this purpose.
A list of all registered methods can be obtained with the RegisteredCmdMethods
function.
Command Inspection
All registered commands are registered with flags that identify information such
as whether the command applies to a chain server, wallet server, or is a
notification along with the method name to use. These flags can be obtained
with the MethodUsageFlags flags, and the method can be obtained with the
CmdMethod function.
Help Generation
To facilitate providing consistent help to users of the RPC server, this package
exposes the GenerateHelp and function which uses reflection on registered
commands or notifications, as well as the provided expected result types, to
generate the final help text.
In addition, the MethodUsageText function is provided to generate consistent
one-line usage for registered commands and notifications using reflection.
Errors
There are 2 distinct type of errors supported by this package:
- General errors related to marshalling or unmarshalling or improper use of
the package (type Error)
- RPC errors which are intended to be returned across the wire as a part of
the JSON-RPC response (type RPCError)
The first category of errors (type Error) typically indicates a programmer error
and can be avoided by properly using the API. Errors of this type will be
returned from the various functions available in this package. They identify
issues such as unsupported field types, attempts to register malformed commands,
and attempting to create a new command with an improper number of parameters.
The specific reason for the error can be detected by type asserting it to a
*btcjson.Error and accessing the ErrorCode field.
The second category of errors (type RPCError), on the other hand, are useful for
returning errors to RPC clients. Consequently, they are used in the previously
described Response type.
*/
package btcjson

View file

@ -7,7 +7,7 @@ package btcjson_test
import (
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestErrorCodeStringer tests the stringized output for the ErrorCode type.

View file

@ -8,7 +8,7 @@ import (
"encoding/json"
"fmt"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// This example demonstrates how to create and marshal a command into a JSON-RPC

View file

@ -8,7 +8,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestHelpReflectInternals ensures the various help functions which deal with

View file

@ -8,7 +8,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestHelpers tests the various helper functions which create pointers to

View file

@ -1,59 +0,0 @@
// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcjson
import (
"encoding/json"
"testing"
)
/*
This test file is part of the btcjson package rather than than the
btcjson_test package so it can bridge access to the internals to properly test
cases which are either not possible or can't reliably be tested via the public
interface. The functions are only exported while the tests are being run.
*/
// TestJsonWIthArgs tests jsonWithArgs to ensure that it can generate a json
// command as well as sending it something that cannot be marshalled to json
// (a channel) to test the failure paths.
func TestJsonWithArgs(t *testing.T) {
cmd := "list"
var args interface{}
_, err := jsonWithArgs(cmd, "test", args)
if err != nil {
t.Errorf("Could not make json with no args. %v", err)
}
channel := make(chan int)
_, err = jsonWithArgs(cmd, "test", channel)
if _, ok := err.(*json.UnsupportedTypeError); !ok {
t.Errorf("Message with channel should fail. %v", err)
}
var comp complex128
_, err = jsonWithArgs(cmd, "test", comp)
if _, ok := err.(*json.UnsupportedTypeError); !ok {
t.Errorf("Message with complex part should fail. %v", err)
}
return
}
// TestJsonRPCSend tests jsonRPCSend which actually send the rpc command.
// This currently a negative test only until we setup a fake http server to
// test the actually connection code.
func TestJsonRPCSend(t *testing.T) {
// Just negative test right now.
user := "something"
password := "something"
server := "invalid"
var message []byte
_, err := jsonRPCSend(user, password, server, message, false, nil, false)
if err == nil {
t.Errorf("Should fail when it cannot connect.")
}
return
}

View file

@ -1,821 +0,0 @@
// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcjson
import (
"encoding/json"
"errors"
"fmt"
"net/http"
)
// BadStatusCode describes a HTTP error when a response has non-200 status code
type BadStatusCode int
func (e BadStatusCode) Error() string {
status := int(e)
return fmt.Sprintf("http bad status: %d %s", status, http.StatusText(status))
}
// ErrIncorrectArgTypes describes an error where the wrong argument types
// are present.
var ErrIncorrectArgTypes = errors.New("incorrect arguement types")
// Message contains a message to be sent to the bitcoin client.
type Message struct {
Jsonrpc string `json:"jsonrpc"`
Id interface{} `json:"id,omitempty"`
Method string `json:"method"`
Params interface{} `json:"params"`
}
// Reply is the general form of the reply from the bitcoin client.
// The form of the Result part varies from one command to the next so it
// is currently implemented as an interface.
type Reply struct {
Result interface{} `json:"result"`
Error *Error `json:"error"`
// This has to be a pointer for go to put a null in it when empty.
Id *interface{} `json:"id"`
}
// Error models the error field of the json returned by a bitcoin client. When
// there is no error, this should be a nil pointer to produce the null in the
// json that bitcoind produces.
type Error struct {
Code int `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}
// Guarantee Error satisifies the builtin error interface
var _, _ error = Error{}, &Error{}
// Error returns a string describing the btcjson error. This
// satisifies the builtin error interface.
func (e Error) Error() string {
return fmt.Sprintf("%d: %s", e.Code, e.Message)
}
// jsonWithArgs takes a command, an id, and an interface which contains an
// array of the arguments for that command. It knows NOTHING about the commands
// so all error checking of the arguments must happen before it is called.
func jsonWithArgs(command string, id interface{}, args interface{}) ([]byte, error) {
rawMessage := Message{"1.0", id, command, args}
finalMessage, err := json.Marshal(rawMessage)
if err != nil {
return nil, err
}
return finalMessage, nil
}
// CreateMessage takes a string and the optional arguments for it. Then,
// if it is a recognized bitcoin json message, generates the json message ready
// to send off to the daemon or server.
// It is capable of handeling all of the commands from the standard client,
// described at:
// https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_Calls_list
//
// WARNING: This method is deprecated and may be removed in a future version.
// Do NOT use this as it does not work for all commands. Instead, use one of
// the New<command>Cmd functions to create a specific command.
func CreateMessage(message string, args ...interface{}) ([]byte, error) {
finalMessage, err := CreateMessageWithId(message, "btcd", args...)
return finalMessage, err
}
// CreateMessageWithId takes a string, an id, and the optional arguments for
// it. Then, if it is a recognized bitcoin json message, generates the json
// message ready to send off to the daemon or server. It is capable of handling
// all of the commands from the standard client, described at:
// https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_Calls_list
//
// WARNING: This method is deprecated and may be removed in a future version.
// Do NOT use this as it does not work for all commands. Instead, use one of
// the New<command>Cmd functions to create a specific command.
func CreateMessageWithId(message string, id interface{}, args ...interface{}) ([]byte, error) {
var finalMessage []byte
var err error
// Different commands have different required and optional arguments.
// Need to handle them based on that.
switch message {
// No args
case "getblockcount", "getblocknumber", "getconnectioncount",
"getdifficulty", "getgenerate", "gethashespersec", "getinfo",
"getmininginfo", "getpeerinfo", "getrawmempool",
"keypoolrefill", "listaddressgroupings", "listlockunspent",
"stop", "walletlock", "getbestblockhash", "getblockchaininfo",
"getnetworkinfo":
if len(args) > 0 {
err = fmt.Errorf("too many arguments for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One optional int
case "listaccounts":
if len(args) > 1 {
err = fmt.Errorf("too many arguments for %s", message)
return finalMessage, err
}
if len(args) == 1 {
_, ok := args[0].(int)
if !ok {
err = fmt.Errorf("argument must be int for %s", message)
return finalMessage, err
}
}
finalMessage, err = jsonWithArgs(message, id, args)
// One required int
case "getblockhash", "estimatefee", "estimatepriority":
if len(args) != 1 {
err = fmt.Errorf("missing argument for %s", message)
return finalMessage, err
}
_, ok := args[0].(int)
if !ok {
err = fmt.Errorf("argument must be int for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One required float
case "settxfee":
if len(args) != 1 {
err = fmt.Errorf("missing argument for %s", message)
return finalMessage, err
}
_, ok := args[0].(float64)
if !ok {
err = fmt.Errorf("argument must be float64 for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One optional string
case "getmemorypool", "getnewaddress", "getwork", "help",
"getrawchangeaddress":
if len(args) > 1 {
err = fmt.Errorf("too many arguments for %s", message)
return finalMessage, err
}
if len(args) == 1 {
_, ok := args[0].(string)
if !ok {
err = fmt.Errorf("optional argument must be string for %s", message)
return finalMessage, err
}
}
finalMessage, err = jsonWithArgs(message, id, args)
// One required string
case "backupwallet", "decoderawtransaction", "dumpprivkey",
"encryptwallet", "getaccount", "getaccountaddress",
"getaddressesbyaccount", "getblock",
"gettransaction", "sendrawtransaction", "validateaddress",
"invalidateblock", "reconsiderblock":
if len(args) != 1 {
err = fmt.Errorf("%s requires one argument", message)
return finalMessage, err
}
_, ok := args[0].(string)
if !ok {
err = fmt.Errorf("argument must be string for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// Two required strings
case "setaccount", "signmessage", "walletpassphrasechange", "addnode":
if len(args) != 2 {
err = fmt.Errorf("missing arguments for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(string)
_, ok2 := args[1].(string)
if !ok1 || !ok2 {
err = fmt.Errorf("arguments must be string for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One required string, one required int
case "walletpassphrase":
if len(args) != 2 {
err = fmt.Errorf("missing arguments for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(string)
_, ok2 := args[1].(int)
if !ok1 || !ok2 {
err = fmt.Errorf("arguments must be string and int for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// Three required strings
case "verifymessage":
if len(args) != 3 {
err = fmt.Errorf("three arguments required for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(string)
_, ok2 := args[1].(string)
_, ok3 := args[2].(string)
if !ok1 || !ok2 || !ok3 {
err = fmt.Errorf("arguments must be string for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One required bool, one optional string
case "getaddednodeinfo":
if len(args) > 2 || len(args) == 0 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(bool)
ok2 := true
if len(args) == 2 {
_, ok2 = args[1].(string)
}
if !ok1 || !ok2 {
err = fmt.Errorf("arguments must be bool and optionally string for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One required bool, one optional int
case "setgenerate":
if len(args) > 2 || len(args) == 0 {
err = fmt.Errorf("wrong number of argument for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(bool)
ok2 := true
if len(args) == 2 {
_, ok2 = args[1].(int)
}
if !ok1 || !ok2 {
err = fmt.Errorf("arguments must be bool and optionally int for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One optional string, one optional int
case "getbalance", "getreceivedbyaccount":
if len(args) > 2 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
ok1 := true
ok2 := true
if len(args) >= 1 {
_, ok1 = args[0].(string)
}
if len(args) == 2 {
_, ok2 = args[1].(int)
}
if !ok1 || !ok2 {
err = fmt.Errorf("optional arguments must be string and int for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One required string, one optional int
case "getrawtransaction", "getreceivedbyaddress":
if len(args) > 2 || len(args) == 0 {
err = fmt.Errorf("wrong number of argument for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(string)
ok2 := true
if len(args) == 2 {
_, ok2 = args[1].(int)
}
if !ok1 || !ok2 {
err = fmt.Errorf("arguments must be string and optionally int for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One required string, one optional string
// Strictly, the optional arg for submit block is an object, but
// bitcoind ignores it for now, so best to just allow string until
// support for it is complete.
case "submitblock":
if len(args) > 2 || len(args) == 0 {
err = fmt.Errorf("wrong number of argument for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(string)
ok2 := true
if len(args) == 2 {
_, ok2 = args[1].(string)
}
if !ok1 || !ok2 {
err = fmt.Errorf("arguments must be string and optionally string for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One optional int, one optional bool
case "listreceivedbyaccount", "listreceivedbyaddress":
if len(args) > 2 {
err = fmt.Errorf("wrong number of argument for %s", message)
return finalMessage, err
}
ok1 := true
ok2 := true
if len(args) >= 1 {
_, ok1 = args[0].(int)
}
if len(args) == 2 {
_, ok2 = args[1].(bool)
}
if !ok1 || !ok2 {
err = fmt.Errorf("optional arguments must be int and bool for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One optional string, two optional ints
case "listtransactions":
if len(args) > 3 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
ok1 := true
ok2 := true
ok3 := true
if len(args) >= 1 {
_, ok1 = args[0].(string)
}
if len(args) > 1 {
_, ok2 = args[1].(int)
}
if len(args) == 3 {
_, ok3 = args[2].(int)
}
if !ok1 || !ok2 || !ok3 {
err = fmt.Errorf("optional arguments must be string and up to two ints for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One required string, one optional string, one optional bool
case "importprivkey":
if len(args) > 3 || len(args) == 0 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(string)
ok2 := true
ok3 := true
if len(args) > 1 {
_, ok2 = args[1].(string)
}
if len(args) == 3 {
_, ok3 = args[2].(bool)
}
if !ok1 || !ok2 || !ok3 {
err = fmt.Errorf("arguments must be string and optionally string and bool for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// Two optional ints
case "listunspent":
if len(args) > 2 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
ok1 := true
ok2 := true
if len(args) >= 1 {
_, ok1 = args[0].(int)
}
if len(args) == 2 {
_, ok2 = args[1].(int)
}
if !ok1 || !ok2 {
err = fmt.Errorf("optional arguments must be ints for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// Two optional strings
case "listsinceblock":
if len(args) > 2 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
ok1 := true
ok2 := true
if len(args) >= 1 {
_, ok1 = args[0].(string)
}
if len(args) == 2 {
_, ok2 = args[1].(string)
}
if !ok1 || !ok2 {
err = fmt.Errorf("optional arguments must be strings for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// Two required strings, one required float, one optional int,
// two optional strings.
case "sendfrom":
if len(args) > 6 || len(args) < 3 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(string)
_, ok2 := args[1].(string)
_, ok3 := args[2].(float64)
ok4 := true
ok5 := true
ok6 := true
if len(args) >= 4 {
_, ok4 = args[3].(int)
}
if len(args) >= 5 {
_, ok5 = args[4].(string)
}
if len(args) == 6 {
_, ok6 = args[5].(string)
}
if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 || !ok6 {
err = fmt.Errorf("arguments must be string, string, float64 and optionally int and two strings for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// Two required strings, one required float, one optional int,
// one optional string.
case "move":
if len(args) > 5 || len(args) < 3 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(string)
_, ok2 := args[1].(string)
_, ok3 := args[2].(float64)
ok4 := true
ok5 := true
if len(args) >= 4 {
_, ok4 = args[3].(int)
}
if len(args) == 5 {
_, ok5 = args[4].(string)
}
if !ok1 || !ok2 || !ok3 || !ok4 || !ok5 {
err = fmt.Errorf("arguments must be string, string, float64 and optionally int and string for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// One required strings, one required float, two optional strings
case "sendtoaddress":
if len(args) > 4 || len(args) < 2 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(string)
_, ok2 := args[1].(float64)
ok3 := true
ok4 := true
if len(args) >= 3 {
_, ok3 = args[2].(string)
}
if len(args) == 4 {
_, ok4 = args[3].(string)
}
if !ok1 || !ok2 || !ok3 || !ok4 {
err = fmt.Errorf("arguments must be string, float64 and optionally two strings for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// required int, required pair of keys (string), optional string
case "addmultisignaddress":
if len(args) > 4 || len(args) < 3 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(int)
_, ok2 := args[1].(string)
_, ok3 := args[2].(string)
ok4 := true
if len(args) == 4 {
_, ok4 = args[2].(string)
}
if !ok1 || !ok2 || !ok3 || !ok4 {
err = fmt.Errorf("arguments must be int, two string and optionally one for %s", message)
return finalMessage, err
}
finalMessage, err = jsonWithArgs(message, id, args)
// Must be a set of string, int, string, float (any number of those).
case "createrawtransaction":
if len(args)%4 != 0 || len(args) == 0 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
type vlist struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
}
vList := make([]vlist, len(args)/4)
addresses := make(map[string]float64)
for i := 0; i < len(args)/4; i++ {
txid, ok1 := args[(i*4)+0].(string)
vout, ok2 := args[(i*4)+1].(uint32)
add, ok3 := args[(i*4)+2].(string)
amt, ok4 := args[(i*4)+3].(float64)
if !ok1 || !ok2 || !ok3 || !ok4 {
return finalMessage, ErrIncorrectArgTypes
}
vList[i].Txid = txid
vList[i].Vout = vout
addresses[add] = amt
}
finalMessage, err = jsonWithArgs(message, id, []interface{}{vList, addresses})
// string, string/float pairs, optional int, and string
case "sendmany":
if len(args) < 3 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
var minconf int
var comment string
_, ok1 := args[0].(string)
if !ok1 {
return finalMessage, ErrIncorrectArgTypes
}
addresses := make(map[string]float64)
for i := 1; i < len(args); i += 2 {
add, ok1 := args[i].(string)
if ok1 {
if len(args) > i+1 {
amt, ok2 := args[i+1].(float64)
if !ok2 {
return finalMessage, ErrIncorrectArgTypes
}
// Put a single pair into addresses
addresses[add] = amt
} else {
comment = add
}
} else {
if _, ok := args[i].(int); ok {
minconf = args[i].(int)
}
if len(args)-1 > i {
if _, ok := args[i+1].(string); ok {
comment = args[i+1].(string)
}
}
}
}
finalMessage, err = jsonWithArgs(message, id, []interface{}{args[0].(string), addresses, minconf, comment})
// bool and an array of stuff
case "lockunspent":
if len(args) < 2 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(bool)
if !ok1 {
return finalMessage, ErrIncorrectArgTypes
}
finalMessage, err = jsonWithArgs(message, id, args)
// one required string (hex) and optional sets of one string, one int,
// and one string along with another optional string.
case "signrawtransaction":
if len(args) < 1 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(string)
if !ok1 {
return finalMessage, ErrIncorrectArgTypes
}
type txlist struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
ScriptPubKey string `json:"scriptPubKey"`
}
txList := make([]txlist, 1)
if len(args) > 1 {
txid, ok2 := args[1].(string)
vout, ok3 := args[2].(uint32)
spkey, ok4 := args[3].(string)
if !ok1 || !ok2 || !ok3 || !ok4 {
return finalMessage, ErrIncorrectArgTypes
}
txList[0].Txid = txid
txList[0].Vout = vout
txList[0].ScriptPubKey = spkey
}
/*
pkeyList := make([]string, (len(args)-1)/4)
for i := 0; i < len(args)/4; i += 1 {
fmt.Println(args[(i*4)+4])
txid, ok1 := args[(i*4)+1].(string)
vout, ok2 := args[(i*4)+2].(int)
spkey, ok3 := args[(i*4)+3].(string)
pkey, ok4 := args[(i*4)+4].(string)
if !ok1 || !ok2 || !ok3 || !ok4 {
return finalMessage, ErrIncorrectArgTypes
}
txList[i].Txid = txid
txList[i].Vout = vout
txList[i].ScriptPubKey = spkey
pkeyList[i] = pkey
}
*/
finalMessage, err = jsonWithArgs(message, id, []interface{}{args[0].(string), txList})
// one required string (address), one optional bool, two optional ints.
case "searchrawtransactions":
if len(args) > 4 || len(args) == 0 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(string)
ok2 := true
ok3 := true
ok4 := true
if len(args) >= 2 {
_, ok2 = args[1].(int)
}
if len(args) >= 3 {
_, ok3 = args[2].(int)
}
if len(args) == 4 {
_, ok4 = args[3].(int)
}
if !ok1 || !ok2 || !ok3 || !ok4 {
err = fmt.Errorf("arguments must be string, one optional "+
"bool, and two optional ints for %s", message)
return finalMessage, err
}
// Any other message
default:
err = fmt.Errorf("not a valid command: %s", message)
}
return finalMessage, err
}
// JSONGetMethod takes a message and tries to find the bitcoin command that it
// is in reply to so it can be processed further.
func JSONGetMethod(message []byte) (string, error) {
var obj struct {
Method string `json:"method"`
}
err := json.Unmarshal(message, &obj)
return obj.Method, err
}
// TlsRpcCommand takes a message generated from one of the routines above
// along with the login/server information and any relavent PEM encoded
// certificates chains. It sends the command via https and returns a go struct
// with the result.
func TlsRpcCommand(user string, password string, server string, message []byte,
certificates []byte, skipverify bool) (Reply, error) {
return rpcCommand(user, password, server, message, true, certificates,
skipverify)
}
// RpcCommand takes a message generated from one of the routines above
// along with the login/server info, sends it, and gets a reply, returning
// a go struct with the result.
func RpcCommand(user string, password string, server string, message []byte) (Reply, error) {
return rpcCommand(user, password, server, message, false, nil, false)
}
func rpcCommand(user string, password string, server string, message []byte,
https bool, certificates []byte, skipverify bool) (Reply, error) {
var result Reply
method, err := JSONGetMethod(message)
if err != nil {
return result, err
}
body, err := rpcRawCommand(user, password, server, message, https,
certificates, skipverify)
if err != nil {
err := fmt.Errorf("error getting json reply: %v", err)
return result, err
}
result, err = ReadResultCmd(method, body)
if err != nil {
err := fmt.Errorf("error reading json message: %v", err)
return result, err
}
return result, err
}
// TlsRpcRawCommand takes a message generated from one of the routines above
// along with the login,server info and PEM encoded certificate chains for the
// server sends it, and gets a reply, returning
// the raw []byte response for use with ReadResultCmd.
func TlsRpcRawCommand(user string, password string, server string,
message []byte, certificates []byte, skipverify bool) ([]byte, error) {
return rpcRawCommand(user, password, server, message, true,
certificates, skipverify)
}
// RpcRawCommand takes a message generated from one of the routines above
// along with the login/server info, sends it, and gets a reply, returning
// the raw []byte response for use with ReadResultCmd.
func RpcRawCommand(user string, password string, server string, message []byte) ([]byte, error) {
return rpcRawCommand(user, password, server, message, false, nil, false)
}
// rpcRawCommand is a helper function for the above two functions.
func rpcRawCommand(user string, password string, server string,
message []byte, https bool, certificates []byte, skipverify bool) ([]byte, error) {
var result []byte
var msg interface{}
err := json.Unmarshal(message, &msg)
if err != nil {
err := fmt.Errorf("error, message does not appear to be valid json: %v", err)
return result, err
}
resp, err := jsonRPCSend(user, password, server, message, https,
certificates, skipverify)
if err != nil {
err := fmt.Errorf("error sending json message: " + err.Error())
return result, err
}
if resp.StatusCode != http.StatusOK {
return nil, BadStatusCode(resp.StatusCode)
}
result, err = GetRaw(resp.Body)
if err != nil {
err := fmt.Errorf("error getting json reply: %v", err)
return result, err
}
return result, err
}
// RpcSend sends the passed command to the provided server using the provided
// authentication details, waits for a reply, and returns a Go struct with the
// result.
func RpcSend(user string, password string, server string, cmd Cmd) (Reply, error) {
msg, err := cmd.MarshalJSON()
if err != nil {
return Reply{}, err
}
return RpcCommand(user, password, server, msg)
}
// TlsRpcSend sends the passed command to the provided server using the provided
// authentication details and PEM encoded certificate chain, waits for a reply,
// and returns a Go struct with the result.
func TlsRpcSend(user string, password string, server string, cmd Cmd,
certificates []byte, skipVerify bool) (Reply, error) {
msg, err := cmd.MarshalJSON()
if err != nil {
return Reply{}, err
}
return TlsRpcCommand(user, password, server, msg, certificates,
skipVerify)
}
// IsValidIdType checks that the Id field (which can go in any of the json
// messages) is valid. json rpc 1.0 allows any (json) type, but we still need
// to prevent values that cannot be marshalled from going in. json rpc 2.0
// (which bitcoind follows for some parts) only allows string, number, or null,
// so we restrict to that list. Ths is only necessary if you manually marshal
// a message. The normal btcjson functions only use string ids.
func IsValidIdType(id interface{}) bool {
switch id.(type) {
case int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64,
float32, float64,
string,
nil:
return true
default:
return false
}
}
// JSONToAmount Safely converts a floating point value to an int.
// Clearly not all floating point numbers can be converted to ints (there
// is no one-to-one mapping), but bitcoin's json api returns most numbers as
// floats which are not safe to use when handling money. Since bitcoins can
// only be divided in a limited way, this methods works for the amounts returned
// by the json api. It is not for general use.
// This follows the method described at:
// https://en.bitcoin.it/wiki/Proper_Money_Handling_%28JSON-RPC%29
func JSONToAmount(jsonAmount float64) (int64, error) {
var amount int64
var err error
if jsonAmount > 1.797693134862315708145274237317043567981e+300 {
err := fmt.Errorf("error %v is too large to convert", jsonAmount)
return amount, err
}
if jsonAmount < -1.797693134862315708145274237317043567981e+300 {
err := fmt.Errorf("error %v is too small to convert", jsonAmount)
return amount, err
}
tempVal := 1e8 * jsonAmount
// So we round properly. float won't == 0 and if it did, that
// would be converted fine anyway.
if tempVal < 0 {
tempVal = tempVal - 0.5
}
if tempVal > 0 {
tempVal = tempVal + 0.5
}
// Then just rely on the integer truncating
amount = int64(tempVal)
return amount, err
}

View file

@ -1,395 +0,0 @@
// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcjson_test
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"testing"
"github.com/btcsuite/btcd/btcjson"
)
// cmdtests is a table of all the possible commands and a list of inputs,
// some of which should work, some of which should not (indicated by the
// pass variable). This mainly checks the type and number of the arguments,
// it does not actually check to make sure the values are correct (i.e., that
// addresses are reasonable) as the bitcoin client must be able to deal with
// that.
var cmdtests = []struct {
cmd string
args []interface{}
pass bool
}{
{"createmultisig", nil, false},
{"createmultisig", []interface{}{1}, false},
{"getinfo", nil, true},
{"getinfo", []interface{}{1}, false},
{"listaccounts", nil, true},
{"listaccounts", []interface{}{1}, true},
{"listaccounts", []interface{}{"test"}, false},
{"listaccounts", []interface{}{1, 2}, false},
{"estimatefee", nil, false},
{"estimatefee", []interface{}{1}, true},
{"estimatefee", []interface{}{1, 2}, false},
{"estimatefee", []interface{}{1.3}, false},
{"estimatepriority", nil, false},
{"estimatepriority", []interface{}{1}, true},
{"estimatepriority", []interface{}{1, 2}, false},
{"estimatepriority", []interface{}{1.3}, false},
{"getblockhash", nil, false},
{"getblockhash", []interface{}{1}, true},
{"getblockhash", []interface{}{1, 2}, false},
{"getblockhash", []interface{}{1.1}, false},
{"settxfee", nil, false},
{"settxfee", []interface{}{1.0}, true},
{"settxfee", []interface{}{1.0, 2.0}, false},
{"settxfee", []interface{}{1}, false},
{"getmemorypool", nil, true},
{"getmemorypool", []interface{}{"test"}, true},
{"getmemorypool", []interface{}{1}, false},
{"getmemorypool", []interface{}{"test", 2}, false},
{"backupwallet", nil, false},
{"backupwallet", []interface{}{1, 2}, false},
{"backupwallet", []interface{}{1}, false},
{"backupwallet", []interface{}{"testpath"}, true},
{"invalidateblock", nil, false},
{"invalidateblock", []interface{}{1, 2}, false},
{"invalidateblock", []interface{}{1}, false},
{"invalidateblock", []interface{}{"testhash"}, true},
{"reconsiderblock", nil, false},
{"reconsiderblock", []interface{}{1, 2}, false},
{"reconsiderblock", []interface{}{1}, false},
{"reconsiderblock", []interface{}{"testhash"}, true},
{"setaccount", nil, false},
{"setaccount", []interface{}{1}, false},
{"setaccount", []interface{}{1, 2, 3}, false},
{"setaccount", []interface{}{1, "test"}, false},
{"setaccount", []interface{}{"test", "test"}, true},
{"verifymessage", nil, false},
{"verifymessage", []interface{}{1}, false},
{"verifymessage", []interface{}{1, 2}, false},
{"verifymessage", []interface{}{1, 2, 3, 4}, false},
{"verifymessage", []interface{}{"test", "test", "test"}, true},
{"verifymessage", []interface{}{"test", "test", 1}, false},
{"getaddednodeinfo", nil, false},
{"getaddednodeinfo", []interface{}{1}, false},
{"getaddednodeinfo", []interface{}{true}, true},
{"getaddednodeinfo", []interface{}{true, 1}, false},
{"getaddednodeinfo", []interface{}{true, "test"}, true},
{"setgenerate", nil, false},
{"setgenerate", []interface{}{1, 2, 3}, false},
{"setgenerate", []interface{}{true}, true},
{"setgenerate", []interface{}{true, 1}, true},
{"setgenerate", []interface{}{true, 1.1}, false},
{"setgenerate", []interface{}{"true", 1}, false},
{"getbalance", nil, true},
{"getbalance", []interface{}{"test"}, true},
{"getbalance", []interface{}{"test", 1}, true},
{"getbalance", []interface{}{"test", 1.0}, false},
{"getbalance", []interface{}{1, 1}, false},
{"getbalance", []interface{}{"test", 1, 2}, false},
{"getbalance", []interface{}{1}, false},
{"addnode", nil, false},
{"addnode", []interface{}{1, 2, 3}, false},
{"addnode", []interface{}{"test", "test"}, true},
{"addnode", []interface{}{1}, false},
{"addnode", []interface{}{"test", 1}, false},
{"addnode", []interface{}{"test", 1.0}, false},
{"listreceivedbyaccount", nil, true},
{"listreceivedbyaccount", []interface{}{1, 2, 3}, false},
{"listreceivedbyaccount", []interface{}{1}, true},
{"listreceivedbyaccount", []interface{}{1.0}, false},
{"listreceivedbyaccount", []interface{}{1, false}, true},
{"listreceivedbyaccount", []interface{}{1, "false"}, false},
{"listtransactions", nil, true},
{"listtransactions", []interface{}{"test"}, true},
{"listtransactions", []interface{}{"test", 1}, true},
{"listtransactions", []interface{}{"test", 1, 2}, true},
{"listtransactions", []interface{}{"test", 1, 2, 3}, false},
{"listtransactions", []interface{}{1}, false},
{"listtransactions", []interface{}{"test", 1.0}, false},
{"listtransactions", []interface{}{"test", 1, "test"}, false},
{"importprivkey", nil, false},
{"importprivkey", []interface{}{"test"}, true},
{"importprivkey", []interface{}{1}, false},
{"importprivkey", []interface{}{"test", "test"}, true},
{"importprivkey", []interface{}{"test", "test", true}, true},
{"importprivkey", []interface{}{"test", "test", true, 1}, false},
{"importprivkey", []interface{}{"test", 1.0, true}, false},
{"importprivkey", []interface{}{"test", "test", "true"}, false},
{"listunspent", nil, true},
{"listunspent", []interface{}{1}, true},
{"listunspent", []interface{}{1, 2}, true},
{"listunspent", []interface{}{1, 2, 3}, false},
{"listunspent", []interface{}{1.0}, false},
{"listunspent", []interface{}{1, 2.0}, false},
{"sendfrom", nil, false},
{"sendfrom", []interface{}{"test"}, false},
{"sendfrom", []interface{}{"test", "test"}, false},
{"sendfrom", []interface{}{"test", "test", 1.0}, true},
{"sendfrom", []interface{}{"test", 1, 1.0}, false},
{"sendfrom", []interface{}{1, "test", 1.0}, false},
{"sendfrom", []interface{}{"test", "test", 1}, false},
{"sendfrom", []interface{}{"test", "test", 1.0, 1}, true},
{"sendfrom", []interface{}{"test", "test", 1.0, 1, "test"}, true},
{"sendfrom", []interface{}{"test", "test", 1.0, 1, "test", "test"}, true},
{"move", nil, false},
{"move", []interface{}{1, 2, 3, 4, 5, 6}, false},
{"move", []interface{}{1, 2}, false},
{"move", []interface{}{"test", "test", 1.0}, true},
{"move", []interface{}{"test", "test", 1.0, 1, "test"}, true},
{"move", []interface{}{"test", "test", 1.0, 1}, true},
{"move", []interface{}{1, "test", 1.0}, false},
{"move", []interface{}{"test", 1, 1.0}, false},
{"move", []interface{}{"test", "test", 1}, false},
{"move", []interface{}{"test", "test", 1.0, 1.0, "test"}, false},
{"move", []interface{}{"test", "test", 1.0, 1, true}, false},
{"sendtoaddress", nil, false},
{"sendtoaddress", []interface{}{"test"}, false},
{"sendtoaddress", []interface{}{"test", 1.0}, true},
{"sendtoaddress", []interface{}{"test", 1.0, "test"}, true},
{"sendtoaddress", []interface{}{"test", 1.0, "test", "test"}, true},
{"sendtoaddress", []interface{}{1, 1.0, "test", "test"}, false},
{"sendtoaddress", []interface{}{"test", 1, "test", "test"}, false},
{"sendtoaddress", []interface{}{"test", 1.0, 1.0, "test"}, false},
{"sendtoaddress", []interface{}{"test", 1.0, "test", 1.0}, false},
{"sendtoaddress", []interface{}{"test", 1.0, "test", "test", 1}, false},
{"addmultisignaddress", []interface{}{1, "test", "test"}, true},
{"addmultisignaddress", []interface{}{1, "test"}, false},
{"addmultisignaddress", []interface{}{1, 1.0, "test"}, false},
{"addmultisignaddress", []interface{}{1, "test", "test", "test"}, true},
{"createrawtransaction", []interface{}{"in1", uint32(0), "a1", 1.0}, true},
{"createrawtransaction", []interface{}{"in1", "out1", "a1", 1.0, "test"}, false},
{"createrawtransaction", []interface{}{}, false},
{"createrawtransaction", []interface{}{"in1", 1.0, "a1", 1.0}, false},
{"sendmany", []interface{}{"in1", "out1", 1.0, 1, "comment"}, true},
{"sendmany", []interface{}{"in1", "out1", 1.0, "comment"}, true},
{"sendmany", []interface{}{"in1", "out1"}, false},
{"sendmany", []interface{}{true, "out1", 1.0, 1, "comment"}, false},
{"sendmany", []interface{}{"in1", "out1", "test", 1, "comment"}, false},
{"lockunspent", []interface{}{true, "something"}, true},
{"lockunspent", []interface{}{true}, false},
{"lockunspent", []interface{}{1.0, "something"}, false},
{"signrawtransaction", []interface{}{"hexstring", "test", uint32(1), "test"}, true},
{"signrawtransaction", []interface{}{"hexstring", "test", "test2", "test3", "test4"}, false},
{"signrawtransaction", []interface{}{"hexstring", "test", "test2", "test3"}, false},
{"signrawtransaction", []interface{}{1.2, "test", "test2", "test3", "test4"}, false},
{"signrawtransaction", []interface{}{"hexstring", 1, "test2", "test3", "test4"}, false},
{"signrawtransaction", []interface{}{"hexstring", "test", "test2", 3, "test4"}, false},
{"searchrawtransactions", []interface{}{"someaddr"}, true},
{"searchrawtransactions", []interface{}{"someaddr", 1}, true},
{"searchrawtransactions", []interface{}{"someaddr", 0, 1}, true},
{"searchrawtransactions", []interface{}{"someaddr", 1, 5, 500}, true},
{"searchrawtransactions", []interface{}{"someaddr", 1, 5, "test"}, false},
{"searchrawtransactions", []interface{}{}, false},
{"listsinceblock", []interface{}{"test", "test"}, true},
{"listsinceblock", []interface{}{"test", "test", "test"}, false},
{"listsinceblock", []interface{}{"test"}, true},
{"listsinceblock", []interface{}{}, true},
{"listsinceblock", []interface{}{1, "test"}, false},
{"walletpassphrase", []interface{}{"test", 1}, true},
{"walletpassphrase", []interface{}{"test"}, false},
{"walletpassphrase", []interface{}{"test", "test"}, false},
{"getrawchangeaddress", []interface{}{}, true},
{"getrawchangeaddress", []interface{}{"something"}, true},
{"getrawchangeaddress", []interface{}{"something", "test"}, false},
{"getbestblockhash", []interface{}{}, true},
{"getbestblockhash", []interface{}{"something"}, false},
{"getblockchaininfo", []interface{}{}, true},
{"getblockchaininfo", []interface{}{"something"}, false},
{"getnetworkinfo", []interface{}{}, true},
{"getnetworkinfo", []interface{}{"something"}, false},
{"submitblock", []interface{}{}, false},
{"submitblock", []interface{}{"something"}, true},
{"submitblock", []interface{}{"something", "something else"}, true},
{"submitblock", []interface{}{"something", "something else", "more"}, false},
{"submitblock", []interface{}{"something", 1}, false},
{"fakecommand", nil, false},
}
// TestRpcCreateMessage tests CreateMessage using the table of messages
// in cmdtests.
func TestRpcCreateMessage(t *testing.T) {
var err error
for i, tt := range cmdtests {
if tt.args == nil {
_, err = btcjson.CreateMessage(tt.cmd)
} else {
_, err = btcjson.CreateMessage(tt.cmd, tt.args...)
}
if tt.pass {
if err != nil {
t.Errorf("Could not create command %d: %s %v.", i, tt.cmd, err)
}
} else {
if err == nil {
t.Errorf("Should create command. %d: %s", i, tt.cmd)
}
}
}
return
}
// TestRpcCommand tests RpcCommand by generating some commands and
// trying to send them off.
func TestRpcCommand(t *testing.T) {
user := "something"
pass := "something"
server := "invalid"
var msg []byte
_, err := btcjson.RpcCommand(user, pass, server, msg)
if err == nil {
t.Errorf("Should fail.")
}
msg, err = btcjson.CreateMessage("getinfo")
if err != nil {
t.Errorf("Cannot create valid json message")
}
_, err = btcjson.RpcCommand(user, pass, server, msg)
if err == nil {
t.Errorf("Should not connect to server.")
}
badMsg := []byte("{\"jsonrpc\":\"1.0\",\"id\":\"btcd\",\"method\":\"\"}")
_, err = btcjson.RpcCommand(user, pass, server, badMsg)
if err == nil {
t.Errorf("Cannot have no method in msg..")
}
return
}
// FailingReadClose is a type used for testing so we can get something that
// fails past Go's type system.
type FailingReadCloser struct{}
func (f *FailingReadCloser) Close() error {
return io.ErrUnexpectedEOF
}
func (f *FailingReadCloser) Read(p []byte) (n int, err error) {
return 0, io.ErrUnexpectedEOF
}
// TestRpcReply tests JsonGetRaw by sending both a good and a bad buffer
// to it.
func TestRpcReply(t *testing.T) {
buffer := new(bytes.Buffer)
buffer2 := ioutil.NopCloser(buffer)
_, err := btcjson.GetRaw(buffer2)
if err != nil {
t.Errorf("Error reading rpc reply.")
}
failBuf := &FailingReadCloser{}
_, err = btcjson.GetRaw(failBuf)
if err == nil {
t.Errorf("Error, this should fail.")
}
return
}
var idtests = []struct {
testID []interface{}
pass bool
}{
{[]interface{}{"string test"}, true},
{[]interface{}{1}, true},
{[]interface{}{1.0}, true},
{[]interface{}{nil}, true},
{[]interface{}{make(chan int)}, false},
}
// TestIsValidIdType tests that IsValidIdType allows (and disallows the correct
// types).
func TestIsValidIdType(t *testing.T) {
for _, tt := range idtests {
res := btcjson.IsValidIdType(tt.testID[0])
if res != tt.pass {
t.Errorf("Incorrect type result %v.", tt)
}
}
return
}
var floattests = []struct {
in float64
out int64
pass bool
}{
{1.0, 100000000, true},
{-1.0, -100000000, true},
{0.0, 0, true},
{0.00000001, 1, true},
{-0.00000001, -1, true},
{-1.0e307, 0, false},
{1.0e307, 0, false},
}
// TestJSONtoAmount tests that JSONtoAmount returns the proper values.
func TestJSONtoAmount(t *testing.T) {
for _, tt := range floattests {
res, err := btcjson.JSONToAmount(tt.in)
if tt.pass {
if res != tt.out || err != nil {
t.Errorf("Should not fail: %v", tt.in)
}
} else {
if err == nil {
t.Errorf("Should not pass: %v", tt.in)
}
}
}
return
}
// TestErrorInterface tests that the Error type satisifies the builtin
// error interface and tests that the error string is created in the form
// "code: message".
func TestErrorInterface(t *testing.T) {
codes := []int{
-1,
0,
1,
}
messages := []string{
"parse error",
"error getting field",
"method not found",
}
// Create an Error and check that both Error and *Error can be used
// as an error.
var jsonError btcjson.Error
var iface interface{} = jsonError
var ifacep interface{} = &jsonError
if _, ok := iface.(error); !ok {
t.Error("cannot type assert Error as error")
return
}
if _, ok := ifacep.(error); !ok {
t.Error("cannot type assert *Error as error")
return
}
// Verify jsonError is converted to the expected string using a few
// combinations of codes and messages.
for _, code := range codes {
for _, message := range messages {
// Create Error
jsonError := btcjson.Error{
Code: code,
Message: message,
}
exp := fmt.Sprintf("%d: %s", jsonError.Code, jsonError.Message)
res := fmt.Sprintf("%v", jsonError)
if exp != res {
t.Errorf("error string '%s' differs from expected '%v'", res, exp)
}
}
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,175 +0,0 @@
// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcjson
// Standard JSON-RPC 2.0 errors
var (
ErrInvalidRequest = Error{
Code: -32600,
Message: "Invalid request",
}
ErrMethodNotFound = Error{
Code: -32601,
Message: "Method not found",
}
ErrInvalidParams = Error{
Code: -32602,
Message: "Invalid paramaters",
}
ErrInternal = Error{
Code: -32603,
Message: "Internal error",
}
ErrParse = Error{
Code: -32700,
Message: "Parse error",
}
)
// General application defined JSON errors
var (
ErrMisc = Error{
Code: -1,
Message: "Miscellaneous error",
}
ErrForbiddenBySafeMode = Error{
Code: -2,
Message: "Server is in safe mode, and command is not allowed in safe mode",
}
ErrType = Error{
Code: -3,
Message: "Unexpected type was passed as parameter",
}
ErrInvalidAddressOrKey = Error{
Code: -5,
Message: "Invalid address or key",
}
ErrOutOfMemory = Error{
Code: -7,
Message: "Ran out of memory during operation",
}
ErrInvalidParameter = Error{
Code: -8,
Message: "Invalid, missing or duplicate parameter",
}
ErrDatabase = Error{
Code: -20,
Message: "Database error",
}
ErrDeserialization = Error{
Code: -22,
Message: "Error parsing or validating structure in raw format",
}
)
// Peer-to-peer client errors
var (
ErrClientNotConnected = Error{
Code: -9,
Message: "Bitcoin is not connected",
}
ErrClientInInitialDownload = Error{
Code: -10,
Message: "Bitcoin is downloading blocks...",
}
)
// Wallet JSON errors
var (
ErrWallet = Error{
Code: -4,
Message: "Unspecified problem with wallet",
}
ErrWalletInsufficientFunds = Error{
Code: -6,
Message: "Not enough funds in wallet or account",
}
ErrWalletInvalidAccountName = Error{
Code: -11,
Message: "Invalid account name",
}
ErrWalletKeypoolRanOut = Error{
Code: -12,
Message: "Keypool ran out, call keypoolrefill first",
}
ErrWalletUnlockNeeded = Error{
Code: -13,
Message: "Enter the wallet passphrase with walletpassphrase first",
}
ErrWalletPassphraseIncorrect = Error{
Code: -14,
Message: "The wallet passphrase entered was incorrect",
}
ErrWalletWrongEncState = Error{
Code: -15,
Message: "Command given in wrong wallet encryption state",
}
ErrWalletEncryptionFailed = Error{
Code: -16,
Message: "Failed to encrypt the wallet",
}
ErrWalletAlreadyUnlocked = Error{
Code: -17,
Message: "Wallet is already unlocked",
}
)
// Specific Errors related to commands. These are the ones a user of the rpc
// server are most likely to see. Generally, the codes should match one of the
// more general errors above.
var (
ErrBlockNotFound = Error{
Code: -5,
Message: "Block not found",
}
ErrBlockCount = Error{
Code: -5,
Message: "Error getting block count",
}
ErrBestBlockHash = Error{
Code: -5,
Message: "Error getting best block hash",
}
ErrDifficulty = Error{
Code: -5,
Message: "Error getting difficulty",
}
ErrOutOfRange = Error{
Code: -1,
Message: "Block number out of range",
}
ErrNoTxInfo = Error{
Code: -5,
Message: "No information available about transaction",
}
ErrNoNewestBlockInfo = Error{
Code: -5,
Message: "No information about newest block",
}
ErrInvalidTxVout = Error{
Code: -5,
Message: "Ouput index number (vout) does not exist for transaction.",
}
ErrRawTxString = Error{
Code: -32602,
Message: "Raw tx is not a string",
}
ErrDecodeHexString = Error{
Code: -22,
Message: "Unable to decode hex string",
}
)
// Errors that are specific to btcd.
var (
ErrNoWallet = Error{
Code: -1,
Message: "This implementation does not implement wallet commands",
}
ErrUnimplemented = Error{
Code: -1,
Message: "Command unimplemented",
}
)

View file

@ -1,79 +0,0 @@
// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcjson
import (
"bytes"
// Need to import this size it registers hash we need.
_ "crypto/sha512"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/url"
"strings"
)
// MarshallAndSend takes the reply structure, marshalls it to json, and
// sends it back to the io.Writer (most likely an http.ResponseWriter).
// returning a log message and an error if there is one.
func MarshallAndSend(rawReply Reply, w io.Writer) (string, error) {
finalReply, err := json.Marshal(rawReply)
if err != nil {
msg := fmt.Sprintf("[RPCS] Error Marshalling reply: %v", err)
return msg, err
}
fmt.Fprintf(w, "%s\n", finalReply)
msg := fmt.Sprintf("[RPCS] reply: %v", rawReply)
return msg, nil
}
// jsonRPCSend connects to the daemon with the specified username, password,
// and ip/port and then send the supplied message. This uses net/http rather
// than net/rpc/jsonrpc since that one doesn't support http connections and is
// therefore useless.
func jsonRPCSend(user string, password string, server string, message []byte,
https bool, certificates []byte, skipverify bool) (*http.Response, error) {
client := &http.Client{}
protocol := "http"
if https {
pool := x509.NewCertPool()
pool.AppendCertsFromPEM(certificates)
config := &tls.Config{
InsecureSkipVerify: skipverify,
RootCAs: pool,
}
transport := &http.Transport{TLSClientConfig: config}
client.Transport = transport
protocol = "https"
}
credentials := url.UserPassword(user, password).String()
resp, err := client.Post(protocol+"://"+credentials+"@"+server,
"application/json", bytes.NewReader(message))
if err != nil {
// We do not want to log the username/password in the errors.
replaceStr := "<username>:<password>"
str := strings.Replace(err.Error(), credentials, replaceStr, -1)
err = fmt.Errorf("%v", str)
}
return resp, err
}
// GetRaw should be called after JsonRpcSend. It reads and returns
// the reply (which you can then call ReadResultCmd() on) and closes the
// connection.
func GetRaw(resp io.ReadCloser) ([]byte, error) {
body, err := ioutil.ReadAll(resp)
resp.Close()
if err != nil {
err = fmt.Errorf("error reading json reply: %v", err)
return body, err
}
return body, nil
}

View file

@ -1,56 +0,0 @@
// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcjson_test
import (
"bytes"
"fmt"
"testing"
"github.com/btcsuite/btcd/btcjson"
)
// TestMarshallAndSend tests the MarshallAndSend function to make sure it can
// create a json message to write to the io.Writerr and to make sure
// it fails properly in cases where it cannot generate json.
func TestMarshallAndSend(t *testing.T) {
jsonError := btcjson.Error{
Code: -32700,
Message: "Parse error",
}
// json.Marshal cannot handle channels so this is a good way to get a
// marshal failure.
badRes := make(chan interface{})
rawReply := btcjson.Reply{
Result: badRes,
Error: &jsonError,
Id: nil,
}
var w bytes.Buffer
msg, err := btcjson.MarshallAndSend(rawReply, &w)
if fmt.Sprintf("%s", err) != "json: unsupported type: chan interface {}" {
t.Error("Should not be able to unmarshall channel")
}
// Use something simple so we can compare the reply.
rawReply = btcjson.Reply{
Result: nil,
Error: nil,
Id: nil,
}
msg, err = btcjson.MarshallAndSend(rawReply, &w)
if msg != "[RPCS] reply: {<nil> <nil> <nil>}" {
t.Error("Incorrect reply:", msg)
}
expBuf := "{\"result\":null,\"error\":null,\"id\":null}\n"
if w.String() != expBuf {
t.Error("Incorrect data in buffer:", w.String())
}
return
}

View file

@ -1,750 +0,0 @@
// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcjson
import (
"bytes"
"encoding/json"
"fmt"
)
// BlockResult models the data from the getblock command when the verbose flag
// is set. When the verbose flag is not set, getblock return a hex-encoded
// string.
type BlockResult struct {
Hash string `json:"hash"`
Confirmations uint64 `json:"confirmations"`
Size int32 `json:"size"`
Height int64 `json:"height"`
Version int32 `json:"version"`
MerkleRoot string `json:"merkleroot"`
Tx []string `json:"tx,omitempty"`
RawTx []TxRawResult `json:"rawtx,omitempty"`
Time int64 `json:"time"`
Nonce uint32 `json:"nonce"`
Bits string `json:"bits"`
Difficulty float64 `json:"difficulty"`
PreviousHash string `json:"previousblockhash"`
NextHash string `json:"nextblockhash"`
}
// CreateMultiSigResult models the data returned from the createmultisig command.
type CreateMultiSigResult struct {
Address string `json:"address"`
RedeemScript string `json:"redeemScript"`
}
// DecodeScriptResult models the data returned from the decodescript command.
type DecodeScriptResult struct {
Asm string `json:"asm"`
ReqSigs int32 `json:"reqSigs,omitempty"`
Type string `json:"type"`
Addresses []string `json:"addresses,omitempty"`
P2sh string `json:"p2sh"`
}
// GetAddedNodeInfoResultAddr models the data of the addresses portion of the
// getaddednodeinfo command.
type GetAddedNodeInfoResultAddr struct {
Address string `json:"address"`
Connected string `json:"connected"`
}
// GetAddedNodeInfoResult models the data from the getaddednodeinfo command.
type GetAddedNodeInfoResult struct {
AddedNode string `json:"addednode"`
Connected *bool `json:"connected,omitempty"`
Addresses *[]GetAddedNodeInfoResultAddr `json:"addresses,omitempty"`
}
// GetBlockChainInfoResult models the data returned from the getblockchaininfo
// command.
type GetBlockChainInfoResult struct {
Chain string `json:"chain"`
Blocks int32 `json:"blocks"`
BestBlockHash string `json:"bestblockhash"`
Difficulty float64 `json:"difficulty"`
VerificationProgress float64 `json:"verificationprogress"`
ChainWork string `json:"chainwork"`
}
// GetBlockTemplateResultTx models the transactions field of the
// getblocktemplate command.
type GetBlockTemplateResultTx struct {
Data string `json:"data"`
Hash string `json:"hash"`
Depends []int64 `json:"depends"`
Fee int64 `json:"fee"`
SigOps int64 `json:"sigops"`
}
// GetBlockTemplateResultAux models the coinbaseaux field of the
// getblocktemplate command.
type GetBlockTemplateResultAux struct {
Flags string `json:"flags"`
}
// GetBlockTemplateResult models the data returned from the getblocktemplate
// command.
type GetBlockTemplateResult struct {
// Base fields from BIP 0022. CoinbaseAux is optional. One of
// CoinbaseTxn or CoinbaseValue must be specified, but not both.
Bits string `json:"bits"`
CurTime int64 `json:"curtime"`
Height int64 `json:"height"`
PreviousHash string `json:"previousblockhash"`
SigOpLimit int64 `json:"sigoplimit,omitempty"`
SizeLimit int64 `json:"sizelimit,omitempty"`
Transactions []GetBlockTemplateResultTx `json:"transactions"`
Version int32 `json:"version"`
CoinbaseAux *GetBlockTemplateResultAux `json:"coinbaseaux,omitempty"`
CoinbaseTxn *GetBlockTemplateResultTx `json:"coinbasetxn,omitempty"`
CoinbaseValue *int64 `json:"coinbasevalue,omitempty"`
WorkID string `json:"workid,omitempty"`
// Optional long polling from BIP 0022.
LongPollID string `json:"longpollid,omitempty"`
LongPollURI string `json:"longpolluri,omitempty"`
SubmitOld *bool `json:"submitold,omitempty"`
// Basic pool extension from BIP 0023.
Target string `json:"target,omitempty"`
Expires int64 `json:"expires,omitempty"`
// Mutations from BIP 0023.
MaxTime int64 `json:"maxtime,omitempty"`
MinTime int64 `json:"mintime,omitempty"`
Mutable []string `json:"mutable,omitempty"`
NonceRange string `json:"noncerange,omitempty"`
// Block proposal from BIP 0023.
Capabilities []string `json:"capabilities,omitempty"`
RejectReasion string `json:"reject-reason,omitempty"`
}
// GetNetworkInfoResult models the data returned from the getnetworkinfo command.
type GetNetworkInfoResult struct {
Version int32 `json:"version"`
ProtocolVersion int32 `json:"protocolversion"`
TimeOffset int64 `json:"timeoffset"`
Connections int32 `json:"connections"`
Networks []NetworksResult `json:"networks"`
RelayFee float64 `json:"relayfee"`
LocalAddresses []LocalAddressesResult `json:"localaddresses"`
}
// GetPeerInfoResult models the data returned from the getpeerinfo command.
type GetPeerInfoResult struct {
ID string `json:"id"`
Addr string `json:"addr"`
AddrLocal string `json:"addrlocal,omitempty"`
Services string `json:"services"`
LastSend int64 `json:"lastsend"`
LastRecv int64 `json:"lastrecv"`
BytesSent uint64 `json:"bytessent"`
BytesRecv uint64 `json:"bytesrecv"`
ConnTime int64 `json:"conntime"`
TimeOffset int64 `json:"timeoffset"`
PingTime float64 `json:"pingtime"`
PingWait float64 `json:"pingwait,omitempty"`
Version uint32 `json:"version"`
SubVer string `json:"subver"`
Inbound bool `json:"inbound"`
StartingHeight int32 `json:"startingheight"`
CurrentHeight int32 `json:"currentheight,omitempty"`
BanScore int32 `json:"banscore,omitempty"`
SyncNode bool `json:"syncnode"`
}
// GetRawMempoolResult models the data returned from the getrawmempool command.
type GetRawMempoolResult struct {
Size int32 `json:"size"`
Fee float64 `json:"fee"`
Time int64 `json:"time"`
Height int64 `json:"height"`
StartingPriority float64 `json:"startingpriority"`
CurrentPriority float64 `json:"currentpriority"`
Depends []string `json:"depends"`
}
// GetTransactionDetailsResult models the details data from the gettransaction command.
type GetTransactionDetailsResult struct {
Account string `json:"account"`
Address string `json:"address,omitempty"`
Category string `json:"category"`
Amount float64 `json:"amount"`
Fee float64 `json:"fee,omitempty"`
}
// GetTransactionResult models the data from the gettransaction command.
type GetTransactionResult struct {
Amount float64 `json:"amount"`
Fee float64 `json:"fee,omitempty"`
Confirmations int64 `json:"confirmations"`
BlockHash string `json:"blockhash"`
BlockIndex int64 `json:"blockindex"`
BlockTime int64 `json:"blocktime"`
TxID string `json:"txid"`
WalletConflicts []string `json:"walletconflicts"`
Time int64 `json:"time"`
TimeReceived int64 `json:"timereceived"`
Details []GetTransactionDetailsResult `json:"details"`
Hex string `json:"hex"`
}
// GetTxOutResult models the data from the gettxout command.
type GetTxOutResult struct {
BestBlock string `json:"bestblock"`
Confirmations int64 `json:"confirmations"`
Value float64 `json:"value"`
ScriptPubKey ScriptPubKeyResult `json:"scriptPubKey"`
Version int32 `json:"version"`
Coinbase bool `json:"coinbase"`
}
// GetNetTotalsResult models the data returned from the getnettotals command.
type GetNetTotalsResult struct {
TotalBytesRecv uint64 `json:"totalbytesrecv"`
TotalBytesSent uint64 `json:"totalbytessent"`
TimeMillis int64 `json:"timemillis"`
}
// ScriptSig models a signature script. It is defined seperately since it only
// applies to non-coinbase. Therefore the field in the Vin structure needs
// to be a pointer.
type ScriptSig struct {
Asm string `json:"asm"`
Hex string `json:"hex"`
}
// Vin models parts of the tx data. It is defined seperately since both
// getrawtransaction, sendrawtransaction, and decoderawtransaction use the
// same structure.
type Vin struct {
Coinbase string `json:"coinbase"`
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
ScriptSig *ScriptSig `json:"scriptSig"`
Sequence uint32 `json:"sequence"`
}
// IsCoinBase returns a bool to show if a Vin is a Coinbase one or not.
func (v *Vin) IsCoinBase() bool {
return len(v.Coinbase) > 0
}
// MarshalJSON provides a custom Marshal method for Vin.
func (v *Vin) MarshalJSON() ([]byte, error) {
if v.IsCoinBase() {
coinbaseStruct := struct {
Coinbase string `json:"coinbase"`
Sequence uint32 `json:"sequence"`
}{
Coinbase: v.Coinbase,
Sequence: v.Sequence,
}
return json.Marshal(coinbaseStruct)
}
txStruct := struct {
Txid string `json:"txid"`
Vout uint32 `json:"vout"`
ScriptSig *ScriptSig `json:"scriptSig"`
Sequence uint32 `json:"sequence"`
}{
Txid: v.Txid,
Vout: v.Vout,
ScriptSig: v.ScriptSig,
Sequence: v.Sequence,
}
return json.Marshal(txStruct)
}
// ScriptPubKeyResult models the scriptPubKey data of a tx script. It is
// defined separately since it is used by multiple commands.
type ScriptPubKeyResult struct {
Asm string `json:"asm"`
Hex string `json:"hex,omitempty"`
ReqSigs int32 `json:"reqSigs,omitempty"`
Type string `json:"type"`
Addresses []string `json:"addresses,omitempty"`
}
// Vout models parts of the tx data. It is defined seperately since both
// getrawtransaction, sendrawtransaction, and decoderawtransaction use the same
// structure.
type Vout struct {
Value float64 `json:"value"`
N uint32 `json:"n"`
ScriptPubKey ScriptPubKeyResult `json:"scriptPubKey"`
}
// GetMiningInfoResult models the data from the getmininginfo command.
type GetMiningInfoResult struct {
Blocks int64 `json:"blocks"`
CurrentBlockSize uint64 `json:"currentblocksize"`
CurrentBlockTx uint64 `json:"currentblocktx"`
Difficulty float64 `json:"difficulty"`
Errors string `json:"errors"`
Generate bool `json:"generate"`
GenProcLimit int32 `json:"genproclimit"`
HashesPerSec int64 `json:"hashespersec"`
NetworkHashPS int64 `json:"networkhashps"`
PooledTx uint64 `json:"pooledtx"`
TestNet bool `json:"testnet"`
}
// GetWorkResult models the data from the getwork command.
type GetWorkResult struct {
Data string `json:"data"`
Hash1 string `json:"hash1"`
Midstate string `json:"midstate"`
Target string `json:"target"`
}
// InfoResult contains the data returned by the getinfo command.
type InfoResult struct {
Version int32 `json:"version"`
ProtocolVersion int32 `json:"protocolversion"`
WalletVersion int32 `json:"walletversion,omitempty"`
Balance float64 `json:"balance,omitempty"`
Blocks int32 `json:"blocks"`
TimeOffset int64 `json:"timeoffset"`
Connections int32 `json:"connections"`
Proxy string `json:"proxy"`
Difficulty float64 `json:"difficulty"`
TestNet bool `json:"testnet"`
KeypoolOldest int64 `json:"keypoololdest,omitempty"`
KeypoolSize int32 `json:"keypoolsize,omitempty"`
UnlockedUntil int64 `json:"unlocked_until,omitempty"`
PaytxFee float64 `json:"paytxfee,omitempty"`
RelayFee float64 `json:"relayfee"`
Errors string `json:"errors"`
}
// ListTransactionsResult models the data from the listtransactions command.
type ListTransactionsResult struct {
Account string `json:"account"`
Address string `json:"address,omitempty"`
Category string `json:"category"`
Amount float64 `json:"amount"`
Fee float64 `json:"fee"`
Confirmations int64 `json:"confirmations"`
Generated bool `json:"generated,omitempty"`
BlockHash string `json:"blockhash,omitempty"`
BlockIndex int64 `json:"blockindex,omitempty"`
BlockTime int64 `json:"blocktime,omitempty"`
TxID string `json:"txid"`
WalletConflicts []string `json:"walletconflicts"`
Time int64 `json:"time"`
TimeReceived int64 `json:"timereceived"`
Comment string `json:"comment,omitempty"`
OtherAccount string `json:"otheraccount"`
}
// LocalAddressesResult models the localaddresses data from the getnetworkinfo command.
type LocalAddressesResult struct {
Address string `json:"address"`
Port uint16 `json:"port"`
Score int32 `json:"score"`
}
// ListReceivedByAccountResult models the data from the listreceivedbyaccount
// command.
type ListReceivedByAccountResult struct {
Account string `json:"account"`
Amount float64 `json:"amount"`
Confirmations uint64 `json:"confirmations"`
}
// ListReceivedByAddressResult models the data from the listreceivedbyaddress
// command.
type ListReceivedByAddressResult struct {
Account string `json:"account"`
Address string `json:"address"`
Amount float64 `json:"amount"`
Confirmations uint64 `json:"confirmations"`
TxIDs []string `json:"txids,omitempty"`
InvolvesWatchonly bool `json:"involvesWatchonly,omitempty"`
}
// ListSinceBlockResult models the data from the listsinceblock command.
type ListSinceBlockResult struct {
Transactions []ListTransactionsResult `json:"transactions"`
LastBlock string `json:"lastblock"`
}
// ListUnspentResult models a successful response from the listunspent request.
type ListUnspentResult struct {
TxId string `json:"txid"`
Vout uint32 `json:"vout"`
Address string `json:"address"`
Account string `json:"account"`
ScriptPubKey string `json:"scriptPubKey"`
RedeemScript string `json:"redeemScript,omitempty"`
Amount float64 `json:"amount"`
Confirmations int64 `json:"confirmations"`
}
// NetworksResult models the networks data from the getnetworkinfo command.
type NetworksResult struct {
Name string `json:"name"`
Limited bool `json:"limited"`
Reachable bool `json:"reachable"`
Proxy string `json:"proxy"`
}
// SignRawTransactionResult models the data from the signrawtransaction
// command.
type SignRawTransactionResult struct {
Hex string `json:"hex"`
Complete bool `json:"complete"`
}
// TxRawResult models the data from the getrawtransaction and sendrawtransaction
// commands
type TxRawResult struct {
Hex string `json:"hex"`
Txid string `json:"txid"`
Version int32 `json:"version"`
LockTime uint32 `json:"locktime"`
Vin []Vin `json:"vin"`
Vout []Vout `json:"vout"`
BlockHash string `json:"blockhash,omitempty"`
Confirmations uint64 `json:"confirmations"`
Time int64 `json:"time,omitempty"`
Blocktime int64 `json:"blocktime,omitempty"`
}
// TxRawDecodeResult models the data from the decoderawtransaction command.
type TxRawDecodeResult struct {
Txid string `json:"txid"`
Version int32 `json:"version"`
Locktime uint32 `json:"locktime"`
Vin []Vin `json:"vin"`
Vout []Vout `json:"vout"`
}
// ValidateAddressResult models the data from the validateaddress command.
type ValidateAddressResult struct {
IsValid bool `json:"isvalid"`
Address string `json:"address,omitempty"`
IsMine bool `json:"ismine,omitempty"`
IsWatchOnly bool `json:"iswatchonly,omitempty"`
IsScript bool `json:"isscript,omitempty"`
PubKey string `json:"pubkey,omitempty"`
IsCompressed bool `json:"iscompressed,omitempty"`
Account string `json:"account,omitempty"`
Addresses []string `json:"addresses,omitempty"`
Hex string `json:"hex,omitempty"`
Script string `json:"script,omitempty"`
SigsRequired int32 `json:"sigsrequired,omitempty"`
}
// ReadResultCmd unmarshalls the json reply with data struct for specific
// commands or an interface if it is not a command where we already have a
// struct ready.
func ReadResultCmd(cmd string, message []byte) (Reply, error) {
var result Reply
var err error
var objmap map[string]json.RawMessage
err = json.Unmarshal(message, &objmap)
if err != nil {
return result, err
}
// Take care of the parts that are the same for all replies.
var jsonErr Error
var id interface{}
err = json.Unmarshal(objmap["error"], &jsonErr)
if err != nil {
err = fmt.Errorf("error unmarshalling json reply: %v", err)
return result, err
}
err = json.Unmarshal(objmap["id"], &id)
if err != nil {
err = fmt.Errorf("error unmarshalling json reply: %v", err)
return result, err
}
// If it is a command where we have already worked out the reply,
// generate put the results in the proper structure.
// We handle the error condition after the switch statement.
switch cmd {
case "createmultisig":
var res *CreateMultiSigResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "decodescript":
var res *DecodeScriptResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "getaddednodeinfo":
// getaddednodeinfo can either return a JSON object or a
// slice of strings depending on the verbose flag. Choose the
// right form accordingly.
if bytes.IndexByte(objmap["result"], '{') > -1 {
var res []GetAddedNodeInfoResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
} else {
var res []string
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
}
case "getinfo":
var res *InfoResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "getblock":
// getblock can either return a JSON object or a hex-encoded
// string depending on the verbose flag. Choose the right form
// accordingly.
if bytes.IndexByte(objmap["result"], '{') > -1 {
var res *BlockResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
} else {
var res string
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
}
case "getblockchaininfo":
var res *GetBlockChainInfoResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "getnettotals":
var res *GetNetTotalsResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "getnetworkhashps":
var res int64
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "getpeerinfo":
var res []GetPeerInfoResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "getrawtransaction":
// getrawtransaction can either return a JSON object or a
// hex-encoded string depending on the verbose flag. Choose the
// right form accordingly.
if bytes.IndexByte(objmap["result"], '{') > -1 {
var res *TxRawResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
} else {
var res string
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
}
case "decoderawtransaction":
var res *TxRawDecodeResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "getaddressesbyaccount":
var res []string
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "getmininginfo":
var res *GetMiningInfoResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "getnetworkinfo":
var res *GetNetworkInfoResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
if res != nil && res.LocalAddresses == nil {
res.LocalAddresses = []LocalAddressesResult{}
}
result.Result = res
}
case "getrawmempool":
// getrawmempool can either return a map of JSON objects or
// an array of strings depending on the verbose flag. Choose
// the right form accordingly.
if bytes.IndexByte(objmap["result"], '{') > -1 {
var res map[string]GetRawMempoolResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
} else {
var res []string
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
}
case "gettransaction":
var res *GetTransactionResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "gettxout":
var res *GetTxOutResult
err = json.Unmarshal(objmap["result"], &res)
if res != nil && err == nil {
if res.ScriptPubKey.Addresses == nil {
res.ScriptPubKey.Addresses = []string{}
}
result.Result = res
}
case "getwork":
// getwork can either return a JSON object or a boolean
// depending on whether or not data was provided. Choose the
// right form accordingly.
if bytes.IndexByte(objmap["result"], '{') > -1 {
var res *GetWorkResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
} else {
var res bool
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
}
case "validateaddress":
var res *ValidateAddressResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "signrawtransaction":
var res *SignRawTransactionResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "listaccounts":
var res map[string]float64
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "listreceivedbyaccount":
var res []ListReceivedByAccountResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "listreceivedbyaddress":
var res []ListReceivedByAddressResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "listsinceblock":
var res *ListSinceBlockResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
if res.Transactions == nil {
res.Transactions = []ListTransactionsResult{}
}
result.Result = res
}
case "listtransactions":
var res []ListTransactionsResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "listunspent":
var res []ListUnspentResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
case "searchrawtransactions":
// searchrawtransactions can either return a list of JSON objects
// or a list of hex-encoded strings depending on the verbose flag.
// Choose the right form accordingly.
if bytes.IndexByte(objmap["result"], '{') > -1 {
var res []*TxRawResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
} else {
var res []string
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
}
// For commands that return a single item (or no items), we get it with
// the correct concrete type for free (but treat them separately
// for clarity).
case "getblockcount", "getbalance", "getblockhash", "getgenerate",
"getconnectioncount", "getdifficulty", "gethashespersec",
"setgenerate", "stop", "settxfee", "getaccount",
"getnewaddress", "sendtoaddress", "createrawtransaction",
"sendrawtransaction", "getbestblockhash", "getrawchangeaddress",
"sendfrom", "sendmany", "addmultisigaddress", "getunconfirmedbalance",
"getaccountaddress", "estimatefee", "estimatepriority":
err = json.Unmarshal(message, &result)
default:
// None of the standard Bitcoin RPC methods matched. Try
// registered custom command reply parsers.
if c, ok := customCmds[cmd]; ok && c.replyParser != nil {
var res interface{}
res, err = c.replyParser(objmap["result"])
if err == nil {
result.Result = res
}
} else {
// For anything else put it in an interface. All the
// data is still there, just a little less convenient
// to deal with.
err = json.Unmarshal(message, &result)
}
}
if err != nil {
err = fmt.Errorf("error unmarshalling json reply: %v", err)
return result, err
}
// Only want the error field when there is an actual error to report.
if jsonErr.Code != 0 {
result.Error = &jsonErr
}
result.Id = &id
return result, err
}

View file

@ -1,99 +0,0 @@
// Copyright (c) 2013-2014 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package btcjson_test
import (
"bytes"
"encoding/json"
"testing"
"github.com/btcsuite/btcd/btcjson"
)
var resulttests = []struct {
cmd string
msg []byte
comp bool
pass bool
}{
// Generate a fake message to make sure we can encode and decode it and
// get the same thing back.
{"getblockcount",
[]byte(`{"result":226790,"error":{"code":1,"message":"No Error"},"id":"btcd"}`),
true, true},
// Generate a fake message to make sure we don't make a command from it.
{"anycommand", []byte(`{"result":"test","id":1}`), false, false},
{"anycommand", []byte(`{some junk}`), false, false},
{"anycommand", []byte(`{"error":null,"result":null,"id":"test"}`), false, true},
{"createmultisig", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
{"createmultisig", []byte(`{"error":null,"id":1,"result":{"address":"something","redeemScript":"else"}}`), false, true},
{"decodescript", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
{"decodescript", []byte(`{"error":null,"id":1,"result":{"Asm":"something"}}`), false, true},
{"getinfo", []byte(`{"error":null,"result":null,"id":"test"}`), false, true},
{"getinfo", []byte(`{"error":null,"result":null}`), false, false},
{"getinfo", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
{"getblock", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
{"getblock", []byte(`{"result":{"hash":"000000","confirmations":16007,"size":325648},"error":null,"id":1}`), false, true},
{"getblockchaininfo", []byte(`{"result":{"chain":"hex","blocks":250000,"bestblockhash":"hash"},"error":null,"id":1}`), false, true},
{"getblockchaininfo", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
{"getnetworkinfo", []byte(`{"result":{"version":100,"protocolversion":70002},"error":null,"id":1}`), false, true},
{"getnetworkinfo", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
{"getrawtransaction", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
{"getrawtransaction", []byte(`{"error":null,"id":1,"result":{"hex":"somejunk","version":1}}`), false, true},
{"gettransaction", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
{"gettransaction", []byte(`{"error":null,"id":1,"result":{"Amount":0.0}}`), false, true},
{"decoderawtransaction", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
{"decoderawtransaction", []byte(`{"error":null,"id":1,"result":{"Txid":"something"}}`), false, true},
{"getaddressesbyaccount", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
{"getaddressesbyaccount", []byte(`{"error":null,"id":1,"result":["test"]}`), false, true},
{"getmininginfo", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
{"getmininginfo", []byte(`{"error":null,"id":1,"result":{"generate":true}}`), false, true},
{"gettxout", []byte(`{"error":null,"id":1,"result":{"bestblock":"a","value":1.0}}`), false, true},
{"listreceivedbyaddress", []byte(`{"error":null,"id":1,"result":[{"a"}]}`), false, false},
{"listreceivedbyaddress", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, true},
{"listsinceblock", []byte(`{"error":null,"id":1,"result":[{"a":"b"}]}`), false, false},
{"listsinceblock", []byte(`{"error":null,"id":1,"result":{"lastblock":"something"}}`), false, true},
{"validateaddress", []byte(`{"error":null,"id":1,"result":{"isvalid":false}}`), false, true},
{"validateaddress", []byte(`{"error":null,"id":1,"result":{false}}`), false, false},
{"signrawtransaction", []byte(`{"error":null,"id":1,"result":{"hex":"something","complete":false}}`), false, true},
{"signrawtransaction", []byte(`{"error":null,"id":1,"result":{false}}`), false, false},
{"listunspent", []byte(`{"error":null,"id":1,"result":[{"txid":"something"}]}`), false, true},
{"listunspent", []byte(`{"error":null,"id":1,"result":[{"txid"}]}`), false, false},
{"searchrawtransactions", []byte(`{"error":null,"id":1,"result":{"a":"b"}}`), false, false},
{"searchrawtransactions", []byte(`{"error":null,"id":1,"result":["sometxhex"]}`), false, true},
{"searchrawtransactions", []byte(`{"error":null,"id":1,"result":[{"hex":"somejunk","version":1}]}`), false, true},
}
// TestReadResultCmd tests that readResultCmd can properly unmarshall the
// returned []byte that contains a json reply for both known and unknown
// messages.
func TestReadResultCmd(t *testing.T) {
for i, tt := range resulttests {
result, err := btcjson.ReadResultCmd(tt.cmd, tt.msg)
if tt.pass {
if err != nil {
t.Errorf("Should read result: %d %v", i, err)
}
// Due to the pointer for the Error and other structs,
// we can't always guarantee byte for byte comparison.
if tt.comp {
msg2, err := json.Marshal(result)
if err != nil {
t.Errorf("Should unmarshal result: %d %v", i, err)
}
if !bytes.Equal(tt.msg, msg2) {
t.Errorf("json byte arrays differ. %d %v %v", i, tt.msg, msg2)
}
}
} else {
if err == nil {
t.Errorf("Should fail: %d, %s", i, tt.msg)
}
}
}
return
}

View file

@ -9,7 +9,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestIsValidIDType ensures the IsValidIDType function behaves as expected.

View file

@ -9,7 +9,7 @@ import (
"sort"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestUsageFlagStringer tests the stringized output for the UsageFlag type.

View file

@ -1,87 +0,0 @@
btcjson
=======
[![Build Status](https://travis-ci.org/btcsuite/btcd.png?branch=master)]
(https://travis-ci.org/btcsuite/btcd) [![ISC License]
(http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
Package btcjson implements concrete types for marshalling to and from the
bitcoin JSON-RPC API. A comprehensive suite of tests is provided to ensure
proper functionality.
Although this package was primarily written for the btcsuite, it has
intentionally been designed so it can be used as a standalone package for any
projects needing to marshal to and from bitcoin JSON-RPC requests and responses.
Note that although it's possible to use this package directly to implement an
RPC client, it is not recommended since it is only intended as an infrastructure
package. Instead, RPC clients should use the
[btcrpcclient](https://github.com/btcsuite/btcrpcclient) package which provides
a full blown RPC client with many features such as automatic connection
management, websocket support, automatic notification re-registration on
reconnect, and conversion from the raw underlying RPC types (strings, floats,
ints, etc) to higher-level types with many nice and useful properties.
## Documentation
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)]
(http://godoc.org/github.com/btcsuite/btcd/btcjson/v2/btcjson)
Full `go doc` style documentation for the project can be viewed online without
installing this package by using the GoDoc site
[here](http://godoc.org/github.com/btcsuite/btcd/btcjson/v2/btcjson).
You can also view the documentation locally once the package is installed with
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
http://localhost:6060/pkg/github.com/btcsuite/btcd/btcjson/v2/btcjson
## Installation
```bash
$ go get github.com/btcsuite/btcd/btcjson/v2/btcjson
```
## Examples
* [Marshal Command]
(http://godoc.org/github.com/btcsuite/btcd/btcjson/v2/btcjson#example-MarshalCmd)
Demonstrates how to create and marshal a command into a JSON-RPC request.
* [Unmarshal Command]
(http://godoc.org/github.com/btcsuite/btcd/btcjson/v2/btcjson#example-UnmarshalCmd)
Demonstrates how to unmarshal a JSON-RPC request and then unmarshal the
concrete request into a concrete command.
* [Marshal Response]
(http://godoc.org/github.com/btcsuite/btcd/btcjson/v2/btcjson#example-MarshalResponse)
Demonstrates how to marshal a JSON-RPC response.
* [Unmarshal Response]
(http://godoc.org/github.com/btcsuite/btcd/btcjson/v2/btcjson#example-package--UnmarshalResponse)
Demonstrates how to unmarshal a JSON-RPC response and then unmarshal the
result field in the response to a concrete type.
## GPG Verification Key
All official release tags are signed by Conformal so users can ensure the code
has not been tampered with and is coming from Conformal. To verify the
signature perform the following:
- Download the public key from the Conformal website at
https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt
- Import the public key into your GPG keyring:
```bash
gpg --import GIT-GPG-KEY-conformal.txt
```
- Verify the release tag with the following command where `TAG_NAME` is a
placeholder for the specific tag:
```bash
git tag -v TAG_NAME
```
## License
Package btcjson is licensed under the [copyfree](http://copyfree.org) ISC
License.

View file

@ -1,146 +0,0 @@
// Copyright (c) 2015 Conformal Systems LLC.
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
/*
Package btcjson provides primitives for working with the bitcoin JSON-RPC API.
Overview
When communicating via the JSON-RPC protocol, all of the commands need to be
marshalled to and from the the wire in the appropriate format. This package
provides data structures and primitives to ease this process.
In addition, it also provides some additional features such as custom command
registration, command categorization, and reflection-based help generation.
JSON-RPC Protocol Overview
This information is not necessary in order to use this package, but it does
provide some intuition into what the marshalling and unmarshalling that is
discussed below is doing under the hood.
As defined by the JSON-RPC spec, there are effectively two forms of messages on
the wire:
- Request Objects
{"jsonrpc":"1.0","id":"SOMEID","method":"SOMEMETHOD","params":[SOMEPARAMS]}
NOTE: Notifications are the same format except the id field is null.
- Response Objects
{"result":SOMETHING,"error":null,"id":"SOMEID"}
{"result":null,"error":{"code":SOMEINT,"message":SOMESTRING},"id":"SOMEID"}
For requests, the params field can vary in what it contains depending on the
method (a.k.a. command) being sent. Each parameter can be as simple as an int
or a complex structure containing many nested fields. The id field is used to
identify a request and will be included in the associated response.
When working with asynchronous transports, such as websockets, spontaneous
notifications are also possible. As indicated, they are the same as a request
object, except they have the id field set to null. Therefore, servers will
ignore requests with the id field set to null, while clients can choose to
consume or ignore them.
Unfortunately, the original Bitcoin JSON-RPC API (and hence anything compatible
with it) doesn't always follow the spec and will sometimes return an error
string in the result field with a null error for certain commands. However,
for the most part, the error field will be set as described on failure.
Marshalling and Unmarshalling
Based upon the discussion above, it should be easy to see how the types of this
package map into the required parts of the protocol
- Request Objects (type Request)
- Commands (type <Foo>Cmd)
- Notifications (type <Foo>Ntfn)
- Response Objects (type Response)
- Result (type <Foo>Result)
To simplify the marshalling of the requests and responses, the MarshalCmd and
MarshalResponse functions are provided. They return the raw bytes ready to be
sent across the wire.
Unmarshalling a received Request object is a two step process:
1) Unmarshal the raw bytes into a Request struct instance via json.Unmarshal
2) Use UnmarshalCmd on the Result field of the unmarshalled Request to create
a concrete command or notification instance with all struct fields set
accordingly
This approach is used since it provides the caller with access to the additional
fields in the request that are not part of the command such as the ID.
Unmarshalling a received Response object is also a two step process:
1) Unmarshal the raw bytes into a Response struct instance via json.Unmarshal
2) Depending on the ID, unmarshal the Result field of the unmarshalled
Response to create a concrete type instance
As above, this approach is used since it provides the caller with access to the
fields in the response such as the ID and Error.
Command Creation
This package provides two approaches for creating a new command. This first,
and preferred, method is to use one of the New<Foo>Cmd functions. This allows
static compile-time checking to help ensure the parameters stay in sync with
the struct definitions.
The second approach is the NewCmd function which takes a method (command) name
and variable arguments. The function includes full checking to ensure the
parameters are accurate according to provided method, however these checks are,
obviously, run-time which means any mistakes won't be found until the code is
actually executed. However, it is quite useful for user-supplied commands
that are intentionally dynamic.
Custom Command Registration
The command handling of this package is built around the concept of registered
commands. This is true for the wide variety of commands already provided by the
package, but it also means caller can easily provide custom commands with all
of the same functionality as the built-in commands. Use the RegisterCmd
function for this purpose.
A list of all registered methods can be obtained with the RegisteredCmdMethods
function.
Command Inspection
All registered commands are registered with flags that identify information such
as whether the command applies to a chain server, wallet server, or is a
notification along with the method name to use. These flags can be obtained
with the MethodUsageFlags flags, and the method can be obtained with the
CmdMethod function.
Help Generation
To facilitate providing consistent help to users of the RPC server, this package
exposes the GenerateHelp and function which uses reflection on registered
commands or notifications, as well as the provided expected result types, to
generate the final help text.
In addition, the MethodUsageText function is provided to generate consistent
one-line usage for registered commands and notifications using reflection.
Errors
There are 2 distinct type of errors supported by this package:
- General errors related to marshalling or unmarshalling or improper use of
the package (type Error)
- RPC errors which are intended to be returned across the wire as a part of
the JSON-RPC response (type RPCError)
The first category of errors (type Error) typically indicates a programmer error
and can be avoided by properly using the API. Errors of this type will be
returned from the various functions available in this package. They identify
issues such as unsupported field types, attempts to register malformed commands,
and attempting to create a new command with an improper number of parameters.
The specific reason for the error can be detected by type asserting it to a
*btcjson.Error and accessing the ErrorCode field.
The second category of errors (type RPCError), on the other hand, are useful for
returning errors to RPC clients. Consequently, they are used in the previously
described Response type.
*/
package btcjson

View file

@ -11,7 +11,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestWalletSvrCmds tests all of the wallet server commands marshal and

View file

@ -11,7 +11,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestWalletSvrWsCmds tests all of the wallet server websocket-specific

View file

@ -11,7 +11,7 @@ import (
"reflect"
"testing"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// TestWalletSvrWsNtfns tests all of the chain server websocket-specific

View file

@ -10,7 +10,7 @@ import (
"path/filepath"
"strings"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
const (

View file

@ -11,7 +11,7 @@ import (
"path/filepath"
"strings"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcutil"
flags "github.com/btcsuite/go-flags"
)

View file

@ -10,7 +10,7 @@ import (
"net"
"net/http"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/go-socks/socks"
)

View file

@ -29,8 +29,7 @@ import (
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/btcjson/btcws"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/database"
"github.com/btcsuite/btcd/txscript"
@ -880,7 +879,7 @@ func handleGetBestBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}
}
}
result := &btcws.GetBestBlockResult{
result := &btcjson.GetBestBlockResult{
Hash: sha.String(),
Height: int32(height),
}

View file

@ -10,7 +10,7 @@ import (
"strings"
"sync"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
)
// helpDescsEnUS defines the English descriptions used for the help strings.

View file

@ -17,7 +17,7 @@ import (
"sync"
"time"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/database"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"

View file

@ -21,7 +21,7 @@ import (
"github.com/btcsuite/btcd/addrmgr"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcjson/v2/btcjson"
"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/database"
"github.com/btcsuite/btcd/wire"