mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00

Add new RpcRawCommand to get []byte represtation of reply instead of btcjson.Result. Export ReadResultCommand to allow caller of RpcRawCommand to deal with the reply in the same way RpcCommand does. Adjust test code to for the above changes.
888 lines
27 KiB
Go
888 lines
27 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"
|
|
)
|
|
|
|
// 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 {
|
|
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"`
|
|
}
|
|
|
|
// 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"`
|
|
}
|
|
|
|
// 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",
|
|
"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, args)
|
|
// Two required strings
|
|
case "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 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, 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 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, []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 sets of 4 optional strings.
|
|
case "signrawtransaction":
|
|
if len(args) < 1 || (len(args)-1)%4 != 0 {
|
|
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 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":
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
body, err := RpcRawCommand(user, password, server, message)
|
|
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
|
|
}
|
|
|
|
// 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) {
|
|
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)
|
|
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
|
|
}
|