91a19dda85
Changed TxRawResult to omitempty block info for mempool tx as well as moved Txid to Vin.TxId from Vin.ScriptSig.Txid (both match bitcoind output) Added support for new bitcoind [verbose=true] and added non-standard optional verboseTx to return TxRawResults intead Txids
1006 lines
32 KiB
Go
1006 lines
32 KiB
Go
// 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"
|
|
"strings"
|
|
)
|
|
|
|
// Message contains a message to be sent to the bitcoin client.
|
|
type Message struct {
|
|
Jsonrpc string `json:"jsonrpc"`
|
|
Id interface{} `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 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"`
|
|
}
|
|
|
|
// 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 {
|
|
Hex string `json:"hex,omitempty"`
|
|
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,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"`
|
|
}
|
|
|
|
// 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,omitempty"`
|
|
Confirmations uint64 `json:"confirmations,omitempty"`
|
|
Time int64 `json:"time,omitempty"`
|
|
Blocktime int64 `json:"blocktime,omitempty"`
|
|
}
|
|
|
|
// TxRawDecodeResult models the data from the decoderawtransaction command.
|
|
type TxRawDecodeResult struct {
|
|
Txid string `json:"txid"`
|
|
Version 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"`
|
|
Txid string `json:"txid,omitempty"`
|
|
Vout int `json:"vout,omitempty"`
|
|
ScriptSig struct {
|
|
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"`
|
|
ReqSigs int `json:"reqSigs"`
|
|
Type string `json:"type"`
|
|
Addresses []string `json:"addresses"`
|
|
} `json:"scriptPubKey"`
|
|
}
|
|
|
|
// GetMiningInfoResult models the data from the getmininginfo command.
|
|
type GetMiningInfoResult struct {
|
|
CurrentBlockSize float64 `json:"currentblocksize"`
|
|
Difficulty float64 `json:"difficulty"`
|
|
Errors string `json:"errors"`
|
|
Generate bool `json:"generate"`
|
|
GenProcLimit float64 `json:"genproclimit"`
|
|
PooledTx float64 `json:"pooledtx"`
|
|
Testnet bool `json:"testnet"`
|
|
Blocks float64 `json:"blocks"`
|
|
CurrentBlockTx float64 `json:"currentblocktx"`
|
|
HashesPerSec float64 `json:"hashespersec"`
|
|
}
|
|
|
|
// 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"`
|
|
IsScript bool `json:"isscript,omitempty"`
|
|
PubKey string `json:"pubkey,omitempty"`
|
|
IsCompressed bool `json:"iscompressed,omitempty"`
|
|
Account string `json:"account,omitempty"`
|
|
}
|
|
|
|
type SignRawTransactionResult struct {
|
|
Hex string `json:"hex"`
|
|
Complete bool `json:"complete"`
|
|
}
|
|
|
|
type ListUnSpentResult struct {
|
|
TxId string `json:"txid"`
|
|
Vout float64 `json:"vout"`
|
|
Address string `json:"address"`
|
|
Account string `json:"account"`
|
|
ScriptPubKey string `json:"scriptPubKey"`
|
|
Amount float64 `json:"amount"`
|
|
Confirmations float64 `json:"confirmations"`
|
|
}
|
|
|
|
// 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
|
|
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
|
|
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":
|
|
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":
|
|
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":
|
|
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 int `json:"vout"`
|
|
}
|
|
vList := make([]vlist, len(args)/4)
|
|
addresses := make(map[string]float64)
|
|
for i := 0; i < len(args)/4; i += 1 {
|
|
txid, ok1 := args[(i*4)+0].(string)
|
|
vout, ok2 := args[(i*4)+1].(int)
|
|
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].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 {
|
|
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, 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 {
|
|
err = fmt.Errorf("Incorrect arguement types.")
|
|
return finalMessage, err
|
|
}
|
|
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 {
|
|
err = fmt.Errorf("Incorrect arguement types.")
|
|
return finalMessage, err
|
|
}
|
|
type txlist struct {
|
|
Txid string `json:"txid"`
|
|
Vout int `json:"vout"`
|
|
ScriptPubKey string `json:"scriptPubKey"`
|
|
}
|
|
txList := make([]txlist, 1)
|
|
|
|
if len(args) > 1 {
|
|
txid, ok2 := args[1].(string)
|
|
vout, ok3 := args[2].(int)
|
|
spkey, ok4 := args[3].(string)
|
|
if !ok1 || !ok2 || !ok3 || !ok4 {
|
|
err = fmt.Errorf("Incorrect arguement types.")
|
|
return finalMessage, err
|
|
}
|
|
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 {
|
|
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, id, []interface{}{args[0].(string), txList})
|
|
// 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 {
|
|
if strings.Contains(string(message), "401 Unauthorized.") {
|
|
err = fmt.Errorf("Authentication error.")
|
|
} else {
|
|
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 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 "getinfo":
|
|
var res InfoResult
|
|
err = json.Unmarshal(objmap["result"], &res)
|
|
if err == nil {
|
|
result.Result = res
|
|
}
|
|
case "getblock":
|
|
var res BlockResult
|
|
err = json.Unmarshal(objmap["result"], &res)
|
|
if err == nil {
|
|
result.Result = res
|
|
}
|
|
case "getrawtransaction":
|
|
var res TxRawResult
|
|
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", "getrawmempool":
|
|
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 "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 "listunspent":
|
|
var res []ListUnSpentResult
|
|
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", "getblocknumber", "getgenerate",
|
|
"getconnetioncount", "getdifficulty", "gethashespersec",
|
|
"setgenerate", "stop", "settxfee", "getaccount",
|
|
"getnewaddress", "sendtoaddress", "createrawtransaction",
|
|
"sendrawtransaction", "getbestblockhash", "getrawchangeaddress":
|
|
err = json.Unmarshal(message, &result)
|
|
// 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
|
|
}
|
|
|
|
// 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) {
|
|
// 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 method, 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 method, err
|
|
}
|
|
return 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
|
|
}
|
|
result, err = GetRaw(resp.Body)
|
|
if err != nil {
|
|
err := fmt.Errorf("Error getting json reply: %v", err)
|
|
return result, err
|
|
}
|
|
return result, err
|
|
}
|
|
|
|
// 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
|
|
}
|