Initial implementation.
This commit is contained in:
parent
d5f4e5e989
commit
d0d58c54db
9 changed files with 1321 additions and 1 deletions
13
LICENSE
Normal file
13
LICENSE
Normal file
|
@ -0,0 +1,13 @@
|
|||
Copyright (c) 2013 Conformal Systems LLC.
|
||||
|
||||
Permission to use, copy, modify, and distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
83
README.md
83
README.md
|
@ -1,4 +1,85 @@
|
|||
btcjson
|
||||
=======
|
||||
|
||||
Package btcjson implements the bitcoin JSON-RPC API.
|
||||
Package btcjson implements the bitcoin JSON-RPC API. There is a test
|
||||
suite which is aiming to reach 100% code coverage. See
|
||||
`test_coverage.txt` for the current coverage (using gocov). On a
|
||||
UNIX-like OS, the script `cov_report.sh` can be used to generate the
|
||||
report. Package btcjson is licensed under the liberal ISC license.
|
||||
|
||||
This package is one of the core packages from btcd, an alternative full-node
|
||||
implementation of bitcoin which is under active development by Conformal.
|
||||
Although it was primarily written for btcd, this package has intentionally been
|
||||
designed so it can be used as a standalone package for any projects needing to
|
||||
communicate with a bitcoin client using the json rpc interface.
|
||||
[BlockSafari](http://blocksafari.com) is one such program that uses
|
||||
btcjson to communicate with btcd (or bitcoind to help test btcd).
|
||||
|
||||
## JSON RPC
|
||||
|
||||
Bitcoin provides an extensive API call list to control bitcoind or
|
||||
bitcoin-qt through json-rpc. These can be used to get information
|
||||
from the client or to cause the client 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
|
||||
msg, err := btcjson.CreateMessage("getinfo")
|
||||
reply, err := btcjson.RpcCommand(user, password, server, msg)
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
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/conformal/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/conformal/btcjson
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
$ go get github.com/conformal/btcjson
|
||||
```
|
||||
|
||||
## TODO
|
||||
|
||||
- Add data structures for remaining commands.
|
||||
- Increase test coverage to 100%
|
||||
|
||||
## 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 liberal ISC License.
|
||||
|
|
17
cov_report.sh
Normal file
17
cov_report.sh
Normal file
|
@ -0,0 +1,17 @@
|
|||
#!/bin/sh
|
||||
|
||||
# This script uses gocov to generate a test coverage report.
|
||||
# The gocov tool my be obtained with the following command:
|
||||
# go get github.com/axw/gocov/gocov
|
||||
#
|
||||
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||
|
||||
# Check for gocov.
|
||||
type gocov >/dev/null 2>&1
|
||||
if [ $? -ne 0 ]; then
|
||||
echo >&2 "This script requires the gocov tool."
|
||||
echo >&2 "You may obtain it with the following command:"
|
||||
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||
exit 1
|
||||
fi
|
||||
gocov test | gocov report
|
88
doc.go
Normal file
88
doc.go
Normal file
|
@ -0,0 +1,88 @@
|
|||
// 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 btcjson implements 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.
|
||||
|
||||
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
|
||||
message. 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.
|
||||
|
||||
Protocol
|
||||
|
||||
All messages to bitcoin are of the form:
|
||||
|
||||
{"jsonrpc":"1.0","id":"SOMEID","method":"SOMEMETHOD","params":SOMEPARAMS}
|
||||
|
||||
The params field can vary in what it contains depending on the
|
||||
different method (or command) being sent.
|
||||
|
||||
Replies will vary in form for the different commands. The basic form is:
|
||||
|
||||
{"result":SOMETHING,"error":null,"id":"btcd"}
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
id is simply the id of the requester.
|
||||
|
||||
Usage
|
||||
|
||||
To use this package, check it out from github:
|
||||
|
||||
go get github.com/conformal/btcjson
|
||||
|
||||
Import it as usual:
|
||||
|
||||
import "github.com/conformal/btcjson"
|
||||
|
||||
Generate the message you want (see the full list on the official bitcoin wiki):
|
||||
|
||||
msg, err := btcjson.CreateMessage("getinfo")
|
||||
|
||||
And then send the message:
|
||||
|
||||
reply, err := btcjson.RpcCommand(user, password, server, msg)
|
||||
|
||||
Since rpc calls must be authenticated, RpcCommand requires a
|
||||
username and password along with the address of the server. For
|
||||
details, see the documentation for your bitcoin implementation.
|
||||
|
||||
For convenience, this can be set for bitcoind by setting rpcuser and
|
||||
rpcpassword in the file ~/.bitcoin/bitcoin.conf with a default local
|
||||
address of: 127.0.0.1:8332
|
||||
|
||||
For commands where the reply structure is known (such as getblock),
|
||||
one can directly access the fields in the Reply structure. For other
|
||||
commands, the reply uses an interface so one can access individual
|
||||
items like:
|
||||
|
||||
if reply.Result != nil {
|
||||
info := reply.Result.(map[string]interface{})
|
||||
balance, ok := info["balance"].(float64)
|
||||
}
|
||||
|
||||
(with appropriate error checking at all steps of course).
|
||||
|
||||
*/
|
||||
package btcjson
|
87
internal_test.go
Normal file
87
internal_test.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
// 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 btcjson
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"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.
|
||||
*/
|
||||
|
||||
// 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) {
|
||||
// Generate a fake message to make sure we can encode and decode it and
|
||||
// get the same thing back.
|
||||
msg := []byte(`{"result":226790,"error":{"code":1,"message":"No Error"},"id":"btcd"}`)
|
||||
result, err := readResultCmd("getblockcount", msg)
|
||||
if err != nil {
|
||||
t.Errorf("Reading json reply to struct failed. %v", err)
|
||||
}
|
||||
msg2, err := json.Marshal(result)
|
||||
if err != nil {
|
||||
t.Errorf("Converting struct back to json bytes failed. %v", err)
|
||||
}
|
||||
if bytes.Compare(msg, msg2) != 0 {
|
||||
t.Errorf("json byte arrays differ.")
|
||||
}
|
||||
// Generate a fake message to make sure we don't make a command from it.
|
||||
msg = []byte(`{"result":"test","id":1}`)
|
||||
_, err = readResultCmd("anycommand", msg)
|
||||
if err == nil {
|
||||
t.Errorf("Incorrect json accepted.")
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// 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, args)
|
||||
if err != nil {
|
||||
t.Errorf("Could not make json with no args. %v", err)
|
||||
}
|
||||
|
||||
channel := make(chan int)
|
||||
_, err = jsonWithArgs(cmd, channel)
|
||||
if _, ok := err.(*json.UnsupportedTypeError); !ok {
|
||||
t.Errorf("Message with channel should fail. %v", err)
|
||||
}
|
||||
|
||||
var comp complex128
|
||||
_, err = jsonWithArgs(cmd, 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)
|
||||
if err == nil {
|
||||
t.Errorf("Should fail when it cannot connect.")
|
||||
}
|
||||
return
|
||||
}
|
725
jsonapi.go
Normal file
725
jsonapi.go
Normal file
|
@ -0,0 +1,725 @@
|
|||
// 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 btcjson
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Message contains a message to be sent to the bitcoin client.
|
||||
type Message struct {
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
Id string `json:"id"`
|
||||
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 implimented 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 *string `json:"id"`
|
||||
}
|
||||
|
||||
// InfoResult contains the data returned by the getinfo command.
|
||||
type InfoResult struct {
|
||||
Version int `json:"version,omitempty"`
|
||||
ProtocolVersion int `json:"protocolversion,omitempty"`
|
||||
WalletVersion int `json:"walletversion,omitempty"`
|
||||
Balance float64 `json:"balance,omitempty"`
|
||||
Blocks int `json:"blocks,omitempty"`
|
||||
Connections int `json:"connections,omitempty"`
|
||||
Proxy string `json:"proxy,omitempty"`
|
||||
Difficulty float64 `json:"difficulty,omitempty"`
|
||||
TestNet bool `json:"testnet,omitempty"`
|
||||
KeypoolOldest int64 `json:"keypoololdest,omitempty"`
|
||||
KeypoolSize int `json:"keypoolsize,omitempty"`
|
||||
PaytxFee float64 `json:"paytxfee,omitempty"`
|
||||
Errors string `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
// BlockResult models the data from the getblock command.
|
||||
type BlockResult struct {
|
||||
Hash string `json:"hash"`
|
||||
Confirmations uint64 `json:"confirmations"`
|
||||
Size int `json:"size"`
|
||||
Height int64 `json:"height"`
|
||||
Version uint32 `json:"version"`
|
||||
MerkleRoot string `json:"merkleroot"`
|
||||
Tx []string `json:"tx"`
|
||||
Time int64 `json:"time"`
|
||||
Nonce uint32 `json:"nonce"`
|
||||
Bits string `json:"bits"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
PreviousHash string `json:"previousblockhash"`
|
||||
NextHash string `json:"nextblockhash"`
|
||||
}
|
||||
|
||||
// TxRawResult models the data from the getrawtransaction command.
|
||||
type TxRawResult struct {
|
||||
Hex string `json:"hex"`
|
||||
Txid string `json:"txid"`
|
||||
Version uint32 `json:"version"`
|
||||
LockTime uint32 `json:"locktime"`
|
||||
Vin []Vin `json:"vin"`
|
||||
Vout []Vout `json:"vout"`
|
||||
BlockHash string `json:"blockhash"`
|
||||
Confirmations uint64 `json:"confirmations"`
|
||||
Time int64 `json:"time"`
|
||||
Blocktime int64 `json:"blocktime"`
|
||||
}
|
||||
|
||||
// TxRawDecodeResult models the data from the decoderawtransaction command.
|
||||
type TxRawDecodeResult struct {
|
||||
Txid string `json:"txid"`
|
||||
Version uint32 `json:"version"`
|
||||
Locktime int `json:"locktime"`
|
||||
Vin []Vin `json:"vin"`
|
||||
Vout []Vout `json:"vout"`
|
||||
}
|
||||
|
||||
// Vin models parts of the tx data. It is defined seperately since both
|
||||
// getrawtransaction and decoderawtransaction use the same structure.
|
||||
type Vin struct {
|
||||
Coinbase string `json:"coinbase,omitempty"`
|
||||
Vout int `json:"vout,omitempty"`
|
||||
ScriptSig struct {
|
||||
Txid string `json:"txid"`
|
||||
Asm string `json:"asm"`
|
||||
Hex string `json:"hex"`
|
||||
} `json:"scriptSig,omitempty"`
|
||||
Sequence float64 `json:"sequence"`
|
||||
}
|
||||
|
||||
// Vout models parts of the tx data. It is defined seperately since both
|
||||
// getrawtransaction and decoderawtransaction use the same structure.
|
||||
type Vout struct {
|
||||
Value float64 `json:"value"`
|
||||
N int `json:"n"`
|
||||
ScriptPubKey struct {
|
||||
Asm string `json:"asm"`
|
||||
Hex string `json:"hex"`
|
||||
ReqSig int `json:"reqSig"`
|
||||
Type string `json:"type"`
|
||||
Addresses []string `json:"addresses"`
|
||||
} `json:"scriptPubKey"`
|
||||
}
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
// jsonWithArgs takes a command 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, args interface{}) ([]byte, error) {
|
||||
rawMessage := Message{"1.0", "btcd", 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
|
||||
func CreateMessage(message string, 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":
|
||||
if len(args) > 0 {
|
||||
err = fmt.Errorf("Too many arguments for %s", message)
|
||||
return finalMessage, err
|
||||
}
|
||||
finalMessage, err = jsonWithArgs(message, 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, args)
|
||||
// One required int
|
||||
case "getblockhash":
|
||||
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, 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, args)
|
||||
// One optional string
|
||||
case "getmemorypool", "getnewaddress", "getwork", "help":
|
||||
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, args)
|
||||
// One required string
|
||||
case "backupwallet", "decoderawtransaction", "dumpprivkey",
|
||||
"encryptwallet", "getaccount", "getaccountaddress",
|
||||
"getaddressbyaccount", "getblock",
|
||||
"gettransaction", "sendrawtransaction", "validateaddress":
|
||||
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, args)
|
||||
// Two required strings
|
||||
case "listsinceblock", "setaccount", "signmessage", "walletpassphrase",
|
||||
"walletpassphrasechange":
|
||||
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, 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, 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, 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, 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, args)
|
||||
// One required string, one optional int
|
||||
case "addnode", "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, 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, 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, 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, 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, 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, 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, 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, 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, args)
|
||||
// Must be a set of 3 strings and a 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 {
|
||||
Vin string `json:"vin"`
|
||||
Vout string `json:"vout"`
|
||||
}
|
||||
vList := make([]vlist, len(args)/4)
|
||||
addresses := make(map[string]float64)
|
||||
for i := 0; i < len(args)/4; i += 1 {
|
||||
vin, ok1 := args[(i*4)+0].(string)
|
||||
vout, ok2 := args[(i*4)+1].(string)
|
||||
add, ok3 := args[(i*4)+2].(string)
|
||||
amt, ok4 := args[(i*4)+3].(float64)
|
||||
if !ok1 || !ok2 || !ok3 || !ok4 {
|
||||
err = fmt.Errorf("Incorrect arguement types.")
|
||||
return finalMessage, err
|
||||
}
|
||||
vList[i].Vin = vin
|
||||
vList[i].Vout = vout
|
||||
addresses[add] = amt
|
||||
}
|
||||
finalMessage, err = jsonWithArgs(message, []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 {
|
||||
err = fmt.Errorf("Incorrect arguement types.")
|
||||
return finalMessage, err
|
||||
}
|
||||
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 {
|
||||
err = fmt.Errorf("Incorrect arguement types.")
|
||||
return finalMessage, err
|
||||
}
|
||||
// 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, []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 {
|
||||
err = fmt.Errorf("Incorrect arguement types.")
|
||||
return finalMessage, err
|
||||
}
|
||||
finalMessage, err = jsonWithArgs(message, args)
|
||||
// one required string (hex) and at least one set of 4 other strings.
|
||||
case "signrawtransaction":
|
||||
if (len(args)-1)%4 != 0 || len(args) < 5 {
|
||||
err = fmt.Errorf("Wrong number of arguments for %s", message)
|
||||
return finalMessage, err
|
||||
}
|
||||
_, ok1 := args[0].(string)
|
||||
if !ok1 {
|
||||
err = fmt.Errorf("Incorrect arguement types.")
|
||||
return finalMessage, err
|
||||
}
|
||||
type txlist struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout string `json:"vout"`
|
||||
ScriptPubKey string `json:"scriptPubKey"`
|
||||
}
|
||||
txList := make([]txlist, (len(args)-1)/4)
|
||||
pkeyList := make([]string, (len(args)-1)/4)
|
||||
for i := 0; i < len(args)/4; i += 1 {
|
||||
txid, ok1 := args[(i*4)+0].(string)
|
||||
vout, ok2 := args[(i*4)+1].(string)
|
||||
spkey, ok3 := args[(i*4)+2].(string)
|
||||
pkey, ok4 := args[(i*4)+3].(string)
|
||||
if !ok1 || !ok2 || !ok3 || !ok4 {
|
||||
err = fmt.Errorf("Incorrect arguement types.")
|
||||
return finalMessage, err
|
||||
}
|
||||
txList[i].Txid = txid
|
||||
txList[i].Vout = vout
|
||||
txList[i].ScriptPubKey = spkey
|
||||
pkeyList[i] = pkey
|
||||
}
|
||||
finalMessage, err = jsonWithArgs(message, []interface{}{args[0].(string), txList, pkeyList})
|
||||
// Any other message
|
||||
default:
|
||||
err = fmt.Errorf("Not a valid command: %s", message)
|
||||
}
|
||||
return finalMessage, err
|
||||
}
|
||||
|
||||
// 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 {
|
||||
err = fmt.Errorf("Error unmarshalling json reply: %v", err)
|
||||
return result, err
|
||||
}
|
||||
// Take care of the parts that are the same for all replies.
|
||||
var jsonErr Error
|
||||
var id string
|
||||
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.
|
||||
switch cmd {
|
||||
case "getinfo":
|
||||
var res InfoResult
|
||||
err = json.Unmarshal(objmap["result"], &res)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error unmarshalling json reply: %v", err)
|
||||
return result, err
|
||||
}
|
||||
result.Result = res
|
||||
case "getblock":
|
||||
var res BlockResult
|
||||
err = json.Unmarshal(objmap["result"], &res)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error unmarshalling json reply: %v", err)
|
||||
return result, err
|
||||
}
|
||||
result.Result = res
|
||||
case "getrawtransaction":
|
||||
var res TxRawResult
|
||||
err = json.Unmarshal(objmap["result"], &res)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error unmarshalling json reply: %v", err)
|
||||
return result, err
|
||||
}
|
||||
result.Result = res
|
||||
case "decoderawtransaction":
|
||||
var res TxRawDecodeResult
|
||||
err = json.Unmarshal(objmap["result"], &res)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error unmarshalling json reply: %v", err)
|
||||
return result, err
|
||||
}
|
||||
result.Result = res
|
||||
// For anything else put it in an interface. All the data is still
|
||||
// there, just a little less convenient to deal with.
|
||||
default:
|
||||
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
|
||||
}
|
||||
|
||||
// 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) {
|
||||
var result Reply
|
||||
// Need this so we can tell what kind of message we are sending
|
||||
// so we can unmarshal it properly.
|
||||
var method string
|
||||
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
|
||||
}
|
||||
m := msg.(map[string]interface{})
|
||||
for k, v := range m {
|
||||
if k == "method" {
|
||||
method = v.(string)
|
||||
}
|
||||
}
|
||||
if method == "" {
|
||||
err := fmt.Errorf("Error, no method specified.")
|
||||
return result, err
|
||||
}
|
||||
resp, err := jsonRpcSend(user, password, server, message)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Error Sending json message.")
|
||||
return result, err
|
||||
}
|
||||
body, err := GetRaw(resp.Body)
|
||||
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
|
||||
}
|
245
jsonapi_test.go
Normal file
245
jsonapi_test.go
Normal file
|
@ -0,0 +1,245 @@
|
|||
// 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 btcjson_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/conformal/btcjson"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// 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
|
||||
}{
|
||||
{"getinfo", nil, true},
|
||||
{"getinfo", []interface{}{1}, false},
|
||||
{"listaccounts", nil, true},
|
||||
{"listaccounts", []interface{}{1}, true},
|
||||
{"listaccounts", []interface{}{"test"}, false},
|
||||
{"listaccounts", []interface{}{1, 2}, 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},
|
||||
{"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"}, true},
|
||||
{"addnode", []interface{}{1}, false},
|
||||
{"addnode", []interface{}{"test", 1}, true},
|
||||
{"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", "out1", "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", "test2", "test3", "test4"}, true},
|
||||
{"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", 2, "test3", "test4"}, false},
|
||||
{"signrawtransaction", []interface{}{"hexstring", "test", "test2", 3, "test4"}, 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
|
||||
}
|
54
jsonfxns.go
Normal file
54
jsonfxns.go
Normal file
|
@ -0,0 +1,54 @@
|
|||
// 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 btcjson
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// MarshallAndSend takes the reply structure, marshalls it to json, and
|
||||
// sends it back to the http writer, returning a log message and an error if
|
||||
// there is one.
|
||||
func MarshallAndSend(rawReply Reply, w http.ResponseWriter) (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) (*http.Response, error) {
|
||||
resp, err := http.Post("http://"+user+":"+password+"@"+server,
|
||||
"application/json", bytes.NewBuffer(message))
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Error Sending json message.")
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// GetRaw should be called after JsonRpcSend. It reads and returns
|
||||
// the reply (which you can then call readResult() 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
|
||||
}
|
10
test_coverage.txt
Normal file
10
test_coverage.txt
Normal file
|
@ -0,0 +1,10 @@
|
|||
|
||||
github.com/conformal/btcjson/jsonapi.go JsonCreateMessage 100.00% (310/310)
|
||||
github.com/conformal/btcjson/jsonfxns.go JsonGetRaw 100.00% (6/6)
|
||||
github.com/conformal/btcjson/jsonapi.go jsonWithArgs 100.00% (5/5)
|
||||
github.com/conformal/btcjson/jsonfxns.go jsonRpcSend 100.00% (4/4)
|
||||
github.com/conformal/btcjson/jsonapi.go JsonRpcCommand 66.67% (18/27)
|
||||
github.com/conformal/btcjson/jsonapi.go readResultCmd 40.00% (20/50)
|
||||
github.com/conformal/btcjson/jsonfxns.go JsonMarshallAndSend 0.00% (0/7)
|
||||
github.com/conformal/btcjson ------------------- 88.75% (363/409)
|
||||
|
Loading…
Reference in a new issue